view vixm/ui.py @ 20:f39963e96ca1

Rework of the help string code Instead of having all the help strings in the command table, we just shove them into the __doc__ for each of the function handlers. This means that no one function can handle two different functionalities as their help string space would collide. This is fine most of the time anyway. However, the considerably cleaner command table is a major plus. The only other "negative" side-effect is the fact that we need to make sure that the first two lines of the function handler are in the right format. First line: the command along with any optional or mandatory arguments Second line: "\t - " followed by a one line description of what the command does. Subsequent lines: All subsequent lines are currently ignored, however it would be nice to display the entire __doc__ string content when something like "h <help string>"-type functionality is implemented.
author Josef "Jeff" Sipek <jeffpc@josefsipek.net>
date Sun, 20 Aug 2006 01:00:29 -0400
parents a3385f616b53
children dfcf1a46fc56
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, control

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()

	last = None
	while not ui.shutdown:
		# check which song we are playing now
		pos = xmms.control.get_playlist_pos()
		current = lists[playlist.LIST_DEFAULT][pos]

		# if it is different from what we played last time we
		# checked...
		if current != last:
			try:
				# pop song off the PRIO queue
				next = lists[playlist.LIST_PRIO].pop()

				# if successful, play the popped song
				idx = lists[playlist.LIST_DEFAULT].index(next)
				xmms.control.set_playlist_pos(idx)
				current = next
			except IndexError:
				# no song to pop
				pass
			except ValueError:
				print "WTF is going on?!"

		# update last played song
		last = current

		# sleep
		time.sleep(0.5)

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

		# 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	'[range]d'
		#		remove songs in range from LIST_PRIO. The
		#		range is 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, help str)
		self.cmdtable = {
			"q([!]){,1}":
				(control.cmd_quit,
				 False),
			"n( *([0-9]+)){,1}":
				(control.cmd_number,
				 True),
			"l( *([0-9]+)){,1}":
				(control.cmd_list,
				 True),
			"a *([0-9]+)":
				(control.cmd_enqueue,
				 False),
			"d":
				(control.cmd_dequeue,
				 True),
			"h":
				(control.cmd_help,
				 False),
			# the following commands are there do allow some,
			# more direct control over xmms
			"z":
				(control.cmd_prev,
				 False),
			"x":
				(control.cmd_play,
				 False),
			"c":
				(control.cmd_pause,
				 False),
			"v":
				(control.cmd_stop,
				 False),
			"b":
				(control.cmd_next,
				 False),
			# the following commands are there to make the
			# experience more like ed/ex/vi/vim
			"!(.*)":
				(control.cmd_shell,
				 False),
		}

	def __cmd(self, txt):
		range_str = "(%|\\$|(\\$|[0-9]+){,1}(,(\\$|[0-9]+)){,1}){,1}"

		cmdtable = self.cmdtable
		
		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 = util.special2int(start)
				end = util.special2int(end)

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

			gr = list(gr)
			gr.append(self.cmdtable)

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

	def search(self, lid, regexp):
		print "Seaching list %d for '%s':" % (lid, regexp)
		for song in self.lists[lid].search(regexp):
			idx = self.lists[lid].index(song)+1
			print "%d. %s" % (idx, str(song))

	def run(self):
		while not self.shutdown:
			tmp = sys.stdin.readline().strip()
			
			if tmp.startswith("/"):
				# '/ABC' - searching
				self.search(playlist.LIST_PRIO, tmp[1:])
				self.search(playlist.LIST_DEFAULT, tmp[1:])
			else:
				# 'ABC' - commands
				self.__cmd(tmp)