view vixm/ui.py @ 21:dfcf1a46fc56 v0.20

Added GPLv2 file & copyright headers
author Josef "Jeff" Sipek <jeffpc@josefsipek.net>
date Sun, 20 Aug 2006 12:30:19 -0400
parents f39963e96ca1
children 2efd6bbb1694
line wrap: on
line source

# ui.py - user interface
#
# Copyright (C) 2006  Josef "Jeff" Sipek <jeffpc@josefsipek.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.

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)