view vixm/ui.py @ 8:79340745c952

Reimplemented enqueuing & quit properly, implemented list and number commands
author Josef "Jeff" Sipek <jeffpc@josefsipek.net>
date Sat, 19 Aug 2006 19:41:06 -0400
parents f87b969fa973
children eaa800169f5b
line wrap: on
line source

# all the user interface related bits

import time, sys, re
from threading import Thread
import xmms

import playlist, song, util

def run():
	""" this is where we start execution """

	# first, let's create the two playlists
	print "Creating playlists..."
	lists = {}
	lists[playlist.LIST_PRIO]	= playlist.playlist()
	lists[playlist.LIST_DEFAULT]	= playlist.playlist(allowrandom=True)

	# read in the info for all the songs in XMMS's playlist
	print "Loading songs from XMMS's playlist..."
	songs = []
	listlength = xmms.control.get_playlist_length()
	for i in range(0,listlength):
		s = song.song(i)
		lists[playlist.LIST_DEFAULT].enqueue(s)
	
	print "Instanciating ui thread..."
	ui = uiThread(lists)
	ui.start()

	while not ui.shutdown:
		time.sleep(1)

class uiThread(Thread):
	""" This is the main ui thread class, it does all the magic
	necessary to have a vi-like interface """
	def __init__(self, lists):
		Thread.__init__(self)

		self.lists = lists
		self.shutdown = False

	def __enqueue(self, id):
		s = self.lists[playlist.LIST_DEFAULT][id]
		print "Enqueuing song: %s (%s)" % (s["title"],
				util.strtime(s["time"]))
		self.lists[playlist.LIST_PRIO].enqueue(s)


	def __cmd(self, txt):
		# commad list:
		#	quit	'q[!]'
		#		quit fails if there are list changes in
		#		memory that haven't been saved. The optional
		#		'!' forces the quit
		#
		#	number	'[range]n [playlistid]'
		#		prints playlist [playlistid] (default is
		#		LIST_PRIO) with each entry being numbered.
		#		If optional range is supplied, only the
		#		songs in that range are printed. The range
		#		string is standard ed-like line range (see
		#		below for details)
		#
		#	list	'[range]l [playlistid]'
		#		virtually identical to the number command
		#		above, however the lines are not numbered.
		#		The same rules apply to the range and
		#		playlistid arguments
		#
		#	enqueue	'a songid'
		#		enqueue a song songid from LIST_DEFAULT onto
		#		LIST_PRIO. The enqueued song is added to the
		#		end of LIST_PRIO
		#
		#	dequeue	'd songid'
		#		remove songid from LIST_PRIO. The songid is
		#		the id in LIST_PRIO, NOT LIST_DEFAULT
		#
		# range:
		#	''	first entry; shortcut for '1'
		#	'%'	entire list; shortcut for '1,$'
		#	'$'	last entry
		#	'n'	entry on line n
		#	'm,n'	range of entries starting on line m and
		#		ending on line n. Both m and n are included
		#		in the list. m or n can both be an integer,
		#		'' or '$'.

		# command table format:
		#	"command regexp":
		#		(function to call, range allowed)
		def __cmd_quit(ui, start, stop, args):
			ui.shutdown = True

		def __cmd_number(ui, start, stop, args):
			__do_list(ui, start, stop, True, args)

		def __cmd_list(ui, start, stop, args):
			__do_list(ui, start, stop, False, args)

		def __cmd_enqueue(ui, start, stop, args):
			try:
				id = int(args[0])-1

				if (id < 0) or \
				   (id >= len(ui.lists[playlist.LIST_DEFAULT])):
					raise ValueError

			except ValueError:
				print "Invalid song id"
				return

			ui.__enqueue(id)

		def __cmd_dequeue(ui, start, stop, args):
			print "not implemented yet"

		def __do_list(ui, start, stop, number, args):
			# get the list id from the argument or default to
			# LIST_PRIO
			try:
				if args[1]:
					listid = int(args[1])
				else:
					listid = playlist.LIST_PRIO

				if listid < 0 or listid >= len(ui.lists):
					raise ValueError
			except ValueError, e:
				print "Invalid list number"
				raise e
	
			max = len(ui.lists[listid])
			if not max:
				return

			start = fixupint(start, max)
			stop = fixupint(stop, max)

			# starting number should be less than the ending
			# number, as well as positive; ending number should
			# be less than or equal to the size of the playlist
			if start > stop or \
			   start < 1 or \
			   stop > max:
				raise ValueError

			i = 1
			pfx = ""
			for s in ui.lists[listid]:
				if i < start or i > stop:
					i += 1
					continue

				if number:
					pfx = "%d. " % (i,)

				print "%s%s (%s)" % (pfx, s["title"],
						util.strtime(s["time"]))
				i += 1

		cmdtable = {
			"q([!]){,1}":
				(__cmd_quit, False),
			"n( ([0-9]+)){,1}":
				(__cmd_number, True),
			"l( ([0-9]+)){,1}":
				(__cmd_list, True),
			"a ([0-9]+)":
				(__cmd_enqueue, False),
			"d ([0-9]+)":
				(__cmd_dequeue, False),
		}

		def special2int(s):
			if s == '$':
				return -1
			if s == '':
				return 1
			return int(s)

		def fixupint(i, m):
			if i == -1:
				return m
			return i

		range_str = "(%|\\$|(\\$|[0-9]+){,1}(,(\\$|[0-9]+)){,1}){,1}"
		
		for c in cmdtable:
			rstr  = "^"
			rstr += (cmdtable[c][1] and range_str or "")
			rstr += c
			rstr += "$"

			m = re.search(rstr, txt)
			if not m:
				continue

			gr = m.groups()

			start = end = -1
			if cmdtable[c][1]:
				# parse range info

				if gr[3]:
					# we got a 'm,n'
					start = (gr[1] or '')
					end = gr[3]
				elif gr[1]:
					# we got a 'n'
					start = gr[1]
					end = gr[1]
				elif gr[0]:
					# we got a '$' or '%'
					start = (gr[0] == '%' and '1' or '$')
					end = '$'
				else:
					# no range specified
					start = '1'
					end = '1'
				
				start = special2int(start)
				end = special2int(end)

				gr = list(gr)
				gr.pop(0)
				gr.pop(0)
				gr.pop(0)
				gr.pop(0)
				gr = tuple(gr)

			try:
				cmdtable[c][0](self, start, end, gr)
			except ValueError:
				print "Invalid argument/value"
			return
		
		print "Invalid command \"%s\"" % (txt,)

	def run(self):
		while not self.shutdown:
			tmp = sys.stdin.readline().strip()
			
			# FIXME: we should use regexps for this
			# 
			# search:	'^\/(.+)'
			# enqueue:	'^([0-9]+)'
			# commands:	'^([A-Za-z]+)(([^ ]*) *)*'

			if tmp.startswith("/"):
				# '/ABC' - searching
				print "Searching not yet implemented"
			else:
				# 'ABC' - commands
				self.__cmd(tmp)