summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--clMonty.py560
-rw-r--r--clPlugin.py414
-rw-r--r--clSettings.py108
-rw-r--r--clSong.py116
-rw-r--r--format.py200
-rw-r--r--jmpc.py154
-rw-r--r--jmpd.py386
-rw-r--r--log.py36
-rw-r--r--misc.py274
-rwxr-xr-xmontypc.py12
-rw-r--r--mpd.py8
-rw-r--r--plugins/AlbumCover.py462
-rw-r--r--plugins/Library.py120
-rw-r--r--plugins/Logger.py110
-rw-r--r--plugins/Lyrics.py494
-rw-r--r--plugins/MPD.py34
-rw-r--r--plugins/Notify.py288
-rw-r--r--plugins/PlayControl.py682
-rw-r--r--plugins/Playlist.py136
-rw-r--r--plugins/Scrobbler.py698
-rw-r--r--plugins/Shortcuts.py150
-rw-r--r--plugins/SongStatus.py136
-rw-r--r--plugins/Systray.py202
-rw-r--r--plugins/Tabs.py276
-rw-r--r--plugins/__init__.py112
-rw-r--r--wgPlaylist.py242
-rw-r--r--wgSongList.py1860
-rw-r--r--winConnect.py134
-rw-r--r--winMain.py496
-rw-r--r--winSettings.py182
30 files changed, 4541 insertions, 4541 deletions
diff --git a/clMonty.py b/clMonty.py
index 7fdc924..7427394 100644
--- a/clMonty.py
+++ b/clMonty.py
@@ -8,314 +8,314 @@ import mpd
from threading import Thread
class Monty(QtCore.QObject):
- """The Monty class offers another layer above pympd, with usefull events."""
- _client=None # MPD client
- _listeners=None # array of listeners: { event: (listeners)* }
+ """The Monty class offers another layer above pympd, with usefull events."""
+ _client=None # MPD client
+ _listeners=None # array of listeners: { event: (listeners)* }
- # cached objects
- _curLib=None
- _curPlaylist=None
- _curSong=None
+ # cached objects
+ _curLib=None
+ _curPlaylist=None
+ _curSong=None
- # objects used for comparison with previous value
- _curSongID=None
- _curTime=None
- _curState=None
- _curVolume=None
- _updatings_db=None
+ # objects used for comparison with previous value
+ _curSongID=None
+ _curTime=None
+ _curState=None
+ _curVolume=None
+ _updatings_db=None
- _timerID=None
+ _timerID=None
- events={
- 'beforeSongChange':'curSongID',
- 'onSongChange':'oldSongID, newSongID',
- 'onTimeChange':'oldTime, newTime',
- 'onStateChange':'oldState, newState',
- 'onVolumeChange':'oldVolume, newVolume',
- 'onConnect':'',
- 'onDisconnect':'',
- 'onReady':'', # when connected, and initialisation is ready
- 'onUpdateDBStart':'', # start updating database
- 'onUpdateDBFinish':'', # when updating database has finished
- }
+ events={
+ 'beforeSongChange':'curSongID',
+ 'onSongChange':'oldSongID, newSongID',
+ 'onTimeChange':'oldTime, newTime',
+ 'onStateChange':'oldState, newState',
+ 'onVolumeChange':'oldVolume, newVolume',
+ 'onConnect':'',
+ 'onDisconnect':'',
+ 'onReady':'', # when connected, and initialisation is ready
+ 'onUpdateDBStart':'', # start updating database
+ 'onUpdateDBFinish':'', # when updating database has finished
+ }
- def __init__(self):
- QtCore.QObject.__init__(self)
- self._client=None
- self._listeners={}
-
- self._curSongID=-1
- self._curTime=-1
- self._curState=-1
- self._curVolume=-1
- self._curLib=[]
- self._curPlaylist=[]
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+ self._client=None
+ self._listeners={}
+
+ self._curSongID=-1
+ self._curTime=-1
+ self._curState=-1
+ self._curVolume=-1
+ self._curLib=[]
+ self._curPlaylist=[]
- for event in self.events:
- self._listeners[event]=[]
+ for event in self.events:
+ self._listeners[event]=[]
- def connect(self, host, port):
- """Connect to MPD@$host:$port. Returns true at success, false otherwise."""
- if self._client:
- return
- try:
- #self._client = jmpc.jmpc()
- #self._client = mpdclient2.connect()
- self._client = mpd.MPDClient()
- self._client.connect(host, port)
- except:
- self._client=None
- return False
-
- self._raiseEvent('onConnect', None)
- try:
- self._updateLib()
- self._updatePlaylist()
- self._updateCurrentSong()
- self._timerID=self.startTimer(500)
- except Exception:
- print_exc()
- self._raiseEvent('onStateChange', {'oldState':'stop', 'newState':self.getStatus()['state']})
- self._raiseEvent('onReady', None)
- doEvents()
- return True
+ def connect(self, host, port):
+ """Connect to MPD@$host:$port. Returns true at success, false otherwise."""
+ if self._client:
+ return
+ try:
+ #self._client = jmpc.jmpc()
+ #self._client = mpdclient2.connect()
+ self._client = mpd.MPDClient()
+ self._client.connect(host, port)
+ except:
+ self._client=None
+ return False
+
+ self._raiseEvent('onConnect', None)
+ try:
+ self._updateLib()
+ self._updatePlaylist()
+ self._updateCurrentSong()
+ self._timerID=self.startTimer(500)
+ except Exception:
+ print_exc()
+ self._raiseEvent('onStateChange', {'oldState':'stop', 'newState':self.getStatus()['state']})
+ self._raiseEvent('onReady', None)
+ doEvents()
+ return True
- def disconnect(self):
- """Disconnect from MPD."""
- if self._client:
- self._client.close()
- self._client.disconnect()
- self._client=None
- # don't kill timer, as it'll happen in timerEvent
-
- def isConnected(self):
- """Returns true if we're connected to MPD, false otherwise."""
- return self._client!=None
+ def disconnect(self):
+ """Disconnect from MPD."""
+ if self._client:
+ self._client.close()
+ self._client.disconnect()
+ self._client=None
+ # don't kill timer, as it'll happen in timerEvent
+
+ def isConnected(self):
+ """Returns true if we're connected to MPD, false otherwise."""
+ return self._client!=None
- def listPlaylist(self):
- """Returns the current playlist."""
- if self.isConnected()==False:
- return None
- return self._curPlaylist
+ def listPlaylist(self):
+ """Returns the current playlist."""
+ if self.isConnected()==False:
+ return None
+ return self._curPlaylist
- def listLibrary(self):
- """Returns the library."""
- if self.isConnected()==False:
- return None
- return self._curLib
+ def listLibrary(self):
+ """Returns the library."""
+ if self.isConnected()==False:
+ return None
+ return self._curLib
- def getCurrentSong(self):
- """Returns the current playing song."""
- if self.isConnected()==False:
- return None
- return self._curSong
-
- def updateDB(self, paths):
- self._client.command_list_ok_begin()
- for path in paths:
- self._client.update(path)
- self._client.command_list_end()
+ def getCurrentSong(self):
+ """Returns the current playing song."""
+ if self.isConnected()==False:
+ return None
+ return self._curSong
+
+ def updateDB(self, paths):
+ self._client.command_list_ok_begin()
+ for path in paths:
+ self._client.update(path)
+ self._client.command_list_end()
- def getStatus(self):
- """Returns the status."""
- try:
- if self.isConnected()==False:
- return None
- ret=self._retrieve(self._client.status)
- if 'time' in ret:
- len=int(ret['time'][ret['time'].find(':')+1:])
- cur=int(ret['time'][:ret['time'].find(':')])
- ret['length']=len
- ret['time']=cur
- return ret
- except Exception, d:
- print_exc()
- return None
-
- def repeat(self,val):
- self._client.repeat(val)
- def random(self,val):
- self._client.random(val)
+ def getStatus(self):
+ """Returns the status."""
+ try:
+ if self.isConnected()==False:
+ return None
+ ret=self._retrieve(self._client.status)
+ if 'time' in ret:
+ len=int(ret['time'][ret['time'].find(':')+1:])
+ cur=int(ret['time'][:ret['time'].find(':')])
+ ret['length']=len
+ ret['time']=cur
+ return ret
+ except Exception, d:
+ print_exc()
+ return None
+
+ def repeat(self,val):
+ self._client.repeat(val)
+ def random(self,val):
+ self._client.random(val)
- _retrMutex=QtCore.QMutex()
- def _retrieve(self, method):
- """Makes sure only one call is made at a time to MPD."""
- self._retrMutex.lock()
- try:
- ret=method()
- except:
- self._retrMutex.unlock()
- raise
+ _retrMutex=QtCore.QMutex()
+ def _retrieve(self, method):
+ """Makes sure only one call is made at a time to MPD."""
+ self._retrMutex.lock()
+ try:
+ ret=method()
+ except:
+ self._retrMutex.unlock()
+ raise
- self._retrMutex.unlock()
- return ret
+ self._retrMutex.unlock()
+ return ret
- def isPlaying(self):
- return self.getStatus()['state']=='play'
+ def isPlaying(self):
+ return self.getStatus()['state']=='play'
- def play(self, id):
- """Play song with ID $id."""
- self._playCalled=True
- if id!=None:
- self._client.playid(id)
- else:
- self._client.playid()
-
- def pause(self):
- """Pause playing."""
- self._client.pause(1)
- def resume(self):
- """Resume playing."""
- self._client.pause(0)
- def next(self):
- """Move on to the next song in the playlist."""
- self._playCalled=False
- self._raiseEvent('beforeSongChange', {'curSongID': self._curSongID})
- # we only switch to the next song, if some of beforeSongChange's listeners
- # didn't explicitly call play. If it did, then it ain't good to immediatly
- # skip to the next song!
- if not self._playCalled:
- self._client.next()
- def previous(self):
- """Move back to the previous song in the playlist."""
- self._client.previous()
- def stop(self):
- """Stop playing."""
- self._client.stop()
+ def play(self, id):
+ """Play song with ID $id."""
+ self._playCalled=True
+ if id!=None:
+ self._client.playid(id)
+ else:
+ self._client.playid()
+
+ def pause(self):
+ """Pause playing."""
+ self._client.pause(1)
+ def resume(self):
+ """Resume playing."""
+ self._client.pause(0)
+ def next(self):
+ """Move on to the next song in the playlist."""
+ self._playCalled=False
+ self._raiseEvent('beforeSongChange', {'curSongID': self._curSongID})
+ # we only switch to the next song, if some of beforeSongChange's listeners
+ # didn't explicitly call play. If it did, then it ain't good to immediatly
+ # skip to the next song!
+ if not self._playCalled:
+ self._client.next()
+ def previous(self):
+ """Move back to the previous song in the playlist."""
+ self._client.previous()
+ def stop(self):
+ """Stop playing."""
+ self._client.stop()
- def seek(self, time):
- """Move the current playing time to $time."""
- self._client.seekid(self._curSongID, time)
-
- def deleteFromPlaylist(self, list):
- """Remove all songIDs in $list from the playlist."""
- self._client.command_list_ok_begin()
- for id in list:
- self._client.deleteid(id)
- self._client.command_list_end()
- self._updatePlaylist()
-
- def addToPlaylist(self, paths):
- """Add all files in $paths to the current playlist."""
- self._client.command_list_ok_begin()
- for path in paths:
- self._client.add(path)
- self._client.command_list_end()
- self._updatePlaylist()
+ def seek(self, time):
+ """Move the current playing time to $time."""
+ self._client.seekid(self._curSongID, time)
+
+ def deleteFromPlaylist(self, list):
+ """Remove all songIDs in $list from the playlist."""
+ self._client.command_list_ok_begin()
+ for id in list:
+ self._client.deleteid(id)
+ self._client.command_list_end()
+ self._updatePlaylist()
+
+ def addToPlaylist(self, paths):
+ """Add all files in $paths to the current playlist."""
+ self._client.command_list_ok_begin()
+ for path in paths:
+ self._client.add(path)
+ self._client.command_list_end()
+ self._updatePlaylist()
- def setVolume(self, volume):
- """Set volumne to $volume."""
- volume=min(100, max(0, volume))
- self._client.setvol(volume)
- def getVolume(self):
- return int(self.getStatus()['volume'])
-
- def addListener(self, event, callback):
- """Add $callback to the listeners for $event."""
- if not(event in self.events):
- raise Exception("Unknown event "+event)
- self._listeners[event].append(callback)
- def removeListener(self, event, callback):
- if not(event in self.events):
- raise Exception("Unknown event "+event)
- self._listeners[event].remove(callback)
+ def setVolume(self, volume):
+ """Set volumne to $volume."""
+ volume=min(100, max(0, volume))
+ self._client.setvol(volume)
+ def getVolume(self):
+ return int(self.getStatus()['volume'])
+
+ def addListener(self, event, callback):
+ """Add $callback to the listeners for $event."""
+ if not(event in self.events):
+ raise Exception("Unknown event "+event)
+ self._listeners[event].append(callback)
+ def removeListener(self, event, callback):
+ if not(event in self.events):
+ raise Exception("Unknown event "+event)
+ self._listeners[event].remove(callback)
- def _updateLib(self):
- """Update the library."""
- self._curLib=self._arrayToSongArray(self._retrieve(self._client.listallinfo))
- id=0
- for song in self._curLib:
- song._data['id']=id
- id+=1
- def _updatePlaylist(self):
- """Update the playlist."""
- self._curPlaylist=self._arrayToSongArray(self._retrieve(self._client.playlistinfo))
- def _arrayToSongArray(self, array):
- """Convert an array to an array of Songs."""
- return map(lambda entry: Song(entry)
- , filter(lambda entry: not('directory' in entry), array)
- )
- def _updateCurrentSong(self):
- """Update the current song."""
- self._curSong=self._retrieve(self._client.currentsong)
- if self._curSong==None:
- return
- self._curSong=Song(self._curSong)
+ def _updateLib(self):
+ """Update the library."""
+ self._curLib=self._arrayToSongArray(self._retrieve(self._client.listallinfo))
+ id=0
+ for song in self._curLib:
+ song._data['id']=id
+ id+=1
+ def _updatePlaylist(self):
+ """Update the playlist."""
+ self._curPlaylist=self._arrayToSongArray(self._retrieve(self._client.playlistinfo))
+ def _arrayToSongArray(self, array):
+ """Convert an array to an array of Songs."""
+ return map(lambda entry: Song(entry)
+ , filter(lambda entry: not('directory' in entry), array)
+ )
+ def _updateCurrentSong(self):
+ """Update the current song."""
+ self._curSong=self._retrieve(self._client.currentsong)
+ if self._curSong==None:
+ return
+ self._curSong=Song(self._curSong)
- class simpleThread(Thread):
- callback=None
- params=None
- def __init__(self,callback,params):
- Thread.__init__(self)
- self.callback=callback
- self.params=params
- def run(self):
- self.callback(self.params)
+ class simpleThread(Thread):
+ callback=None
+ params=None
+ def __init__(self,callback,params):
+ Thread.__init__(self)
+ self.callback=callback
+ self.params=params
+ def run(self):
+ self.callback(self.params)
- def _raiseEvent(self, event, params):
- """Call all listeners for $event with parameters $params."""
- if not(event in self.events):
- raise Exception("Unknown raised event "+event)
+ def _raiseEvent(self, event, params):
+ """Call all listeners for $event with parameters $params."""
+ if not(event in self.events):
+ raise Exception("Unknown raised event "+event)
- for listener in self._listeners[event]:
- try:
- self.simpleThread(listener, params).run()
- except:
- print_exc()
-
- def timerEvent(self, event):
- "Check for changes since last check."
- try:
- self._updateCurrentSong()
- status=self.getStatus()
- except:
- self._curSong=None
+ for listener in self._listeners[event]:
+ try:
+ self.simpleThread(listener, params).run()
+ except:
+ print_exc()
+
+ def timerEvent(self, event):
+ "Check for changes since last check."
+ try:
+ self._updateCurrentSong()
+ status=self.getStatus()
+ except:
+ self._curSong=None
- song=self._curSong
- if song==None or status==None:
- self._client=None
- self._raiseEvent('onDisconnect', None)
- self.killTimer(self._timerID)
- return
-
- " check if song has changed"
- if song.getID()>=0:
- curID=song.getID()
- if curID!=self._curSongID:
- self._raiseEvent('onSongChange', {'oldSongID':self._curSongID, 'newSongID':curID})
- self._curSongID=curID
+ song=self._curSong
+ if song==None or status==None:
+ self._client=None
+ self._raiseEvent('onDisconnect', None)
+ self.killTimer(self._timerID)
+ return
+
+ " check if song has changed"
+ if song.getID()>=0:
+ curID=song.getID()
+ if curID!=self._curSongID:
+ self._raiseEvent('onSongChange', {'oldSongID':self._curSongID, 'newSongID':curID})
+ self._curSongID=curID
- " check if the time has changed"
- if 'time' in status:
- curTime=status['time']
- if curTime!=self._curTime:
- self._raiseEvent('onTimeChange', {'oldTime':self._curTime, 'newTime':curTime})
- self._curTime=curTime
- if curTime>=status['length']-1:
- self._raiseEvent('beforeSongChange', {'curSongID':curID})
+ " check if the time has changed"
+ if 'time' in status:
+ curTime=status['time']
+ if curTime!=self._curTime:
+ self._raiseEvent('onTimeChange', {'oldTime':self._curTime, 'newTime':curTime})
+ self._curTime=curTime
+ if curTime>=status['length']-1:
+ self._raiseEvent('beforeSongChange', {'curSongID':curID})
- " check if the playing state has changed"
- if 'state' in status:
- curState=status['state']
- if curState!=self._curState:
- self._raiseEvent('onStateChange', {'oldState':self._curState, 'newState':curState})
- self._curState=curState
+ " check if the playing state has changed"
+ if 'state' in status:
+ curState=status['state']
+ if curState!=self._curState:
+ self._raiseEvent('onStateChange', {'oldState':self._curState, 'newState':curState})
+ self._curState=curState
- " check if the volume has changed"
- if 'volume' in status:
- curVolume=int(status['volume'])
- if curVolume!=self._curVolume:
- self._raiseEvent('onVolumeChange', {'oldVolume':self._curVolume, 'newVolume':curVolume})
- self._curVolume=curVolume
+ " check if the volume has changed"
+ if 'volume' in status:
+ curVolume=int(status['volume'])
+ if curVolume!=self._curVolume:
+ self._raiseEvent('onVolumeChange', {'oldVolume':self._curVolume, 'newVolume':curVolume})
+ self._curVolume=curVolume
- " update has started"
- if 'updatings_db' in status and self._updatings_db==None:
- self._updatings_db=status['updatings_db']
- self._raiseEvent('onUpdateDBStart', {})
- if not('updatings_db' in status) and self._updatings_db:
- self._updatings_db=None
- self._raiseEvent('onUpdateDBFinish')
+ " update has started"
+ if 'updatings_db' in status and self._updatings_db==None:
+ self._updatings_db=status['updatings_db']
+ self._raiseEvent('onUpdateDBStart', {})
+ if not('updatings_db' in status) and self._updatings_db:
+ self._updatings_db=None
+ self._raiseEvent('onUpdateDBFinish')
monty=Monty()
diff --git a/clPlugin.py b/clPlugin.py
index fb26768..f825458 100644
--- a/clPlugin.py
+++ b/clPlugin.py
@@ -7,221 +7,221 @@ from misc import *
import log
class Plugin:
- name=None
- dockWidget=None
- settingsWidget=None
- settings=None
- winMain=None
- loaded=None
- listeners=[]
-
- def __init__(self, winMain, name):
- self.name=name
- self.winMain=winMain
- self.loaded=False
- self.listeners=[]
+ name=None
+ dockWidget=None
+ settingsWidget=None
+ settings=None
+ winMain=None
+ loaded=None
+ listeners=[]
+
+ def __init__(self, winMain, name):
+ self.name=name
+ self.winMain=winMain
+ self.loaded=False
+ self.listeners=[]
- def getName(self, lower=False):
- if lower:
- return self.name.lower()
- return self.name
- def getInfo(self):
- return ''
- def getExtInfo(self):
- return ''
- def getWinMain(self):
- return self.winMain
- def setStatus(self, status):
- self.winMain.setStatus(status)
-
- def log(self, msg, level=log.LOG_NORMAL):
- log.log("%s: %s"%(self.getName(), msg), level)
- def normal(self, msg):
- self.log(msg)
- def important(self, msg):
- self.log(msg, log.LOG_IMPORTANT)
- def extended(self, msg):
- self.log(msg, log.LOG_EXTENDED)
- def debug(self, msg):
- self.log(msg, log.LOG_DEBUG)
-
- def load(self):
- self.extended("loading")
- if len(self.listeners):
- self.debug("adding %s listeners"%(len(self.listeners)))
- for listener in self.listeners:
- monty.addListener(listener[0], listener[1])
+ def getName(self, lower=False):
+ if lower:
+ return self.name.lower()
+ return self.name
+ def getInfo(self):
+ return ''
+ def getExtInfo(self):
+ return ''
+ def getWinMain(self):
+ return self.winMain
+ def setStatus(self, status):
+ self.winMain.setStatus(status)
+
+ def log(self, msg, level=log.LOG_NORMAL):
+ log.log("%s: %s"%(self.getName(), msg), level)
+ def normal(self, msg):
+ self.log(msg)
+ def important(self, msg):
+ self.log(msg, log.LOG_IMPORTANT)
+ def extended(self, msg):
+ self.log(msg, log.LOG_EXTENDED)
+ def debug(self, msg):
+ self.log(msg, log.LOG_DEBUG)
+
+ def load(self):
+ self.extended("loading")
+ if len(self.listeners):
+ self.debug("adding %s listeners"%(len(self.listeners)))
+ for listener in self.listeners:
+ monty.addListener(listener[0], listener[1])
- self._load()
- opts=QtGui.QDockWidget.DockWidgetClosable|QtGui.QDockWidget.DockWidgetMovable
- self.winMain.addDock(self.getDockWidget(opts))
- self.loaded=True
- def unload(self):
- if self.loaded==False:
- return
- self.extended("unloading")
- if len(self.listeners):
- self.debug("removing %s listeners"%(len(self.listeners)))
- for listener in self.listeners:
- monty.removeListener(listener[0], listener[1])
+ self._load()
+ opts=QtGui.QDockWidget.DockWidgetClosable|QtGui.QDockWidget.DockWidgetMovable
+ self.winMain.addDock(self.getDockWidget(opts))
+ self.loaded=True
+ def unload(self):
+ if self.loaded==False:
+ return
+ self.extended("unloading")
+ if len(self.listeners):
+ self.debug("removing %s listeners"%(len(self.listeners)))
+ for listener in self.listeners:
+ monty.removeListener(listener[0], listener[1])
- self._unload()
- self.winMain.removeDock(self.getDockWidget())
- self.dockWidget=None
- self.settingsWidget=None
- self.loaded=False
- def isLoaded(self):
- return self.loaded
-
- def addMontyListener(self, event, callback):
- self.listeners.append([event, callback])
-
- def getDockWidget(self, opts=None):
- try:
- if not self.dockWidget:
- self.dockWidget=self._getDockWidget()
- self.dockWidget.setFeatures(opts)
- self.dockWidget.setAllowedAreas(QtCore.Qt.AllDockWidgetAreas)
- except:
- pass
- return self.dockWidget
+ self._unload()
+ self.winMain.removeDock(self.getDockWidget())
+ self.dockWidget=None
+ self.settingsWidget=None
+ self.loaded=False
+ def isLoaded(self):
+ return self.loaded
+
+ def addMontyListener(self, event, callback):
+ self.listeners.append([event, callback])
+
+ def getDockWidget(self, opts=None):
+ try:
+ if not self.dockWidget:
+ self.dockWidget=self._getDockWidget()
+ self.dockWidget.setFeatures(opts)
+ self.dockWidget.setAllowedAreas(QtCore.Qt.AllDockWidgetAreas)
+ except:
+ pass
+ return self.dockWidget
- def getSettingsWidget(self):
- """Get the widget containing all settings."""
- # is the widget constructed yet?
- if not self.settingsWidget:
- self.settings=self._getSettings()
- # do we have any settings?
- if len(self.settings):
- # create a widget
- self.settingsWidget=QtGui.QWidget(None)
- layout=QtGui.QGridLayout()
- self.settingsWidget.setLayout(layout)
- # add for every setting a new entry
- for i in xrange(len(self.settings)):
- setting=self.settings[i]
- try:
- setting[4]
- except:
- # if setting[0] doesn't contain a dot, it means it
- # is for this one, else it might be a setting from
- # another plugin. But this only applies when using
- # the default settings-manager
- if setting[0] and setting[0].count('.')==0:
- setting[0]="%s.%s"%(self._getPluginClassname(self.__class__), setting[0])
- setting.append(settings)
- tooltip="%s\n\n(%s)"%(setting[2],setting[0])
- label=QtGui.QLabel(setting[1])
- wg=setting[3]
- label.setToolTip(tooltip)
- wg.setToolTip(tooltip)
- layout.addWidget(label)
- layout.addWidget(wg, i, 1)
+ def getSettingsWidget(self):
+ """Get the widget containing all settings."""
+ # is the widget constructed yet?
+ if not self.settingsWidget:
+ self.settings=self._getSettings()
+ # do we have any settings?
+ if len(self.settings):
+ # create a widget
+ self.settingsWidget=QtGui.QWidget(None)
+ layout=QtGui.QGridLayout()
+ self.settingsWidget.setLayout(layout)
+ # add for every setting a new entry
+ for i in xrange(len(self.settings)):
+ setting=self.settings[i]
+ try:
+ setting[4]
+ except:
+ # if setting[0] doesn't contain a dot, it means it
+ # is for this one, else it might be a setting from
+ # another plugin. But this only applies when using
+ # the default settings-manager
+ if setting[0] and setting[0].count('.')==0:
+ setting[0]="%s.%s"%(self._getPluginClassname(self.__class__), setting[0])
+ setting.append(settings)
+ tooltip="%s\n\n(%s)"%(setting[2],setting[0])
+ label=QtGui.QLabel(setting[1])
+ wg=setting[3]
+ label.setToolTip(tooltip)
+ wg.setToolTip(tooltip)
+ layout.addWidget(label)
+ layout.addWidget(wg, i, 1)
- layout.addWidget(Button('Save settings for '+self.getName(), self.saveSettings), i+1,1)
+ layout.addWidget(Button('Save settings for '+self.getName(), self.saveSettings), i+1,1)
- return self.settingsWidget
-
- def resetSettingCache(self):
- self.settings=None
- self.settingsWidget=None
+ return self.settingsWidget
+
+ def resetSettingCache(self):
+ self.settings=None
+ self.settingsWidget=None
- def saveSettings(self):
- if not self.settings:
- self.important('Plugin::saveSettings - no settings to save?')
- return
- for i in xrange(len(self.settings)):
- setting=self.settings[i]
- name=setting[0]
- obj=setting[3]
- settingsMgr=setting[4]
- if isinstance(obj,QtGui.QLineEdit):
- settingsMgr.set(setting[0], setting[3].text())
- elif isinstance(obj,QtGui.QComboBox):
- settingsMgr.set(setting[0], setting[3].currentIndex())
- elif isinstance(obj,QtGui.QTextEdit):
- settingsMgr.set(setting[0], setting[3].toPlainText())
- elif isinstance(obj,QtGui.QCheckBox):
- settingsMgr.set(setting[0], "1" if setting[3].checkState()==QtCore.Qt.Checked else "0")
- elif isinstance(obj,QtGui.QPushButton):
- # what the *#$ should we do with this? Just skip!
- print "I don't know what to do with my object!"
- pass
- else:
- print "Can't handle %s"%(obj.__class__)
- if self.isLoaded():
- self.afterSaveSettings()
-
- def getSettingWidget(self, name):
- for i in xrange(len(self.settings)):
- setting=self.settings[i]
- if name==setting[0]:
- return setting[3]
- return None
-
- def _getPluginClassname(self, cl):
- """Returns the name of a plugin (without 'plugin'-prefix)"""
- return str(cl).split('.')[-1].lower()[len('plugin'):]
-
-
- def getSetting(self, setting, default=None, pluginClass=None):
- if pluginClass==None:
- pluginClass=self.__class__
+ def saveSettings(self):
+ if not self.settings:
+ self.important('Plugin::saveSettings - no settings to save?')
+ return
+ for i in xrange(len(self.settings)):
+ setting=self.settings[i]
+ name=setting[0]
+ obj=setting[3]
+ settingsMgr=setting[4]
+ if isinstance(obj,QtGui.QLineEdit):
+ settingsMgr.set(setting[0], setting[3].text())
+ elif isinstance(obj,QtGui.QComboBox):
+ settingsMgr.set(setting[0], setting[3].currentIndex())
+ elif isinstance(obj,QtGui.QTextEdit):
+ settingsMgr.set(setting[0], setting[3].toPlainText())
+ elif isinstance(obj,QtGui.QCheckBox):
+ settingsMgr.set(setting[0], "1" if setting[3].checkState()==QtCore.Qt.Checked else "0")
+ elif isinstance(obj,QtGui.QPushButton):
+ # what the *#$ should we do with this? Just skip!
+ print "I don't know what to do with my object!"
+ pass
+ else:
+ print "Can't handle %s"%(obj.__class__)
+ if self.isLoaded():
+ self.afterSaveSettings()
+
+ def getSettingWidget(self, name):
+ for i in xrange(len(self.settings)):
+ setting=self.settings[i]
+ if name==setting[0]:
+ return setting[3]
+ return None
+
+ def _getPluginClassname(self, cl):
+ """Returns the name of a plugin (without 'plugin'-prefix)"""
+ return str(cl).split('.')[-1].lower()[len('plugin'):]
+
+
+ def getSetting(self, setting, default=None, pluginClass=None):
+ if pluginClass==None:
+ pluginClass=self.__class__
- # fetch the name
- pluginClass=self._getPluginClassname(pluginClass)
-
- if default==None:
- # what module is this class in?
- module='.'.join(str(self.__class__).split('.')[0:2])
- # import the module
- __import__(module, globals(), locals(), [], -1)
- # set the default
- default=eval("%s.%s_%s_DEFAULT"%(module, pluginClass.upper(), setting.upper()))
-
- return settings.get("%s.%s"%(str(pluginClass).lower(), setting), default)
-
- def setSetting(self, setting, value, pluginClass=None):
- if pluginClass==None:
- pluginClass=self.__class__
- # fetch the name
- pluginClass=self._getPluginClassname(pluginClass)
- settings.set("%s.%s"%(str(pluginClass).lower(), setting), value)
+ # fetch the name
+ pluginClass=self._getPluginClassname(pluginClass)
+
+ if default==None:
+ # what module is this class in?
+ module='.'.join(str(self.__class__).split('.')[0:2])
+ # import the module
+ __import__(module, globals(), locals(), [], -1)
+ # set the default
+ default=eval("%s.%s_%s_DEFAULT"%(module, pluginClass.upper(), setting.upper()))
+
+ return settings.get("%s.%s"%(str(pluginClass).lower(), setting), default)
+
+ def setSetting(self, setting, value, pluginClass=None):
+ if pluginClass==None:
+ pluginClass=self.__class__
+ # fetch the name
+ pluginClass=self._getPluginClassname(pluginClass)
+ settings.set("%s.%s"%(str(pluginClass).lower(), setting), value)
-
-
- def afterSaveSettings(self):
- """Override this one."""
- pass
- def _getDockWidget(self):
- """Override this one."""
- return None
- def _getSettings(self):
- """Override this one.
+
+
+ def afterSaveSettings(self):
+ """Override this one."""
+ pass
+ def _getDockWidget(self):
+ """Override this one."""
+ return None
+ def _getSettings(self):
+ """Override this one.
- This method must return a list of arrays.
- Format: [ [setting, title, tooltip, widget, opt settingsmgr]+ ]"""
- # setting[0] == setting. If setting contains a dot (.), setting is absolute, i.e.
- # it belongs to a plugin. If it doesn't contain a dot, it belongs to the
- # current class. E.g. 'lyrics.engine' and 'engine' are equal, if we are in
- # the class pluginLyrics. Note that this only applies when setting[4] is
- # not set.
- # setting[1] == caption
- # setting[2] == help message
- # setting[3] == widget containing the value
- # setting[4] == setting-class to use
- return []
- def _createDock(self, widget):
- """Creates a QDockWidget with parent $parent containing widget $widget."""
- dock=QtGui.QDockWidget(self.name, self.winMain)
- dock.setObjectName(self.name)
- dock.setWidget(widget)
-
- return dock
- def _load(self):
- """Override this one."""
- return
- def _unload(self):
- """Override this one."""
- return
+ This method must return a list of arrays.
+ Format: [ [setting, title, tooltip, widget, opt settingsmgr]+ ]"""
+ # setting[0] == setting. If setting contains a dot (.), setting is absolute, i.e.
+ # it belongs to a plugin. If it doesn't contain a dot, it belongs to the
+ # current class. E.g. 'lyrics.engine' and 'engine' are equal, if we are in
+ # the class pluginLyrics. Note that this only applies when setting[4] is
+ # not set.
+ # setting[1] == caption
+ # setting[2] == help message
+ # setting[3] == widget containing the value
+ # setting[4] == setting-class to use
+ return []
+ def _createDock(self, widget):
+ """Creates a QDockWidget with parent $parent containing widget $widget."""
+ dock=QtGui.QDockWidget(self.name, self.winMain)
+ dock.setObjectName(self.name)
+ dock.setWidget(widget)
+
+ return dock
+ def _load(self):
+ """Override this one."""
+ return
+ def _unload(self):
+ """Override this one."""
+ return
diff --git a/clSettings.py b/clSettings.py
index 832ef08..36f6a16 100644
--- a/clSettings.py
+++ b/clSettings.py
@@ -3,60 +3,60 @@ from traceback import print_exc
from PyQt4 import QtCore
class Settings:
- fileName=None
- lines=[]
-
- def __init__(self, file='settings.txt'):
- self.fileName=file
- self.read()
-
- def __del__(self):
- self.write()
-
- def get(self, name, default=None):
- for line in self.lines:
- if line[0:len(name)]==name:
- return line[len(name):].strip()
- return default
-
- def set(self, name, value):
- newvalue="%s\t%s"%(name,value)
- for i in xrange(len(self.lines)):
- line=self.lines[i]
- if line[0:len(name)]==name:
- self.lines[i]=newvalue
- return
- # new value to write
- self.lines.append(newvalue)
-
- def getIntTuple(self, name):
- """Note this might return an exception!"""
- val=self.get(name)
- i=val.find(' ')
- x,y=int(val[0:i]), int(val[i:])
- return x,y
-
- def setIntTuple(self, name, val1, val2):
- self.set(name, "%i %i"%(val1, val2))
-
- def read(self):
- try:
- self.lines=[]
- f=open(self.fileName)
- while True:
- line=f.readline()
- if line=='': break
- self.lines.append(line.strip().replace('$NEWLINE', '\n'))
- f.close()
- except IOError:
- pass
- except:
- print_exc()
- def write(self):
- f=open(self.fileName, 'wb')
- for line in self.lines:
- f.write("%s\n"%(line.replace('\n', '$NEWLINE')))
- f.close()
+ fileName=None
+ lines=[]
+
+ def __init__(self, file='settings.txt'):
+ self.fileName=file
+ self.read()
+
+ def __del__(self):
+ self.write()
+
+ def get(self, name, default=None):
+ for line in self.lines:
+ if line[0:len(name)]==name:
+ return line[len(name):].strip()
+ return default
+
+ def set(self, name, value):
+ newvalue="%s\t%s"%(name,value)
+ for i in xrange(len(self.lines)):
+ line=self.lines[i]
+ if line[0:len(name)]==name:
+ self.lines[i]=newvalue
+ return
+ # new value to write
+ self.lines.append(newvalue)
+
+ def getIntTuple(self, name):
+ """Note this might return an exception!"""
+ val=self.get(name)
+ i=val.find(' ')
+ x,y=int(val[0:i]), int(val[i:])
+ return x,y
+
+ def setIntTuple(self, name, val1, val2):
+ self.set(name, "%i %i"%(val1, val2))
+
+ def read(self):
+ try:
+ self.lines=[]
+ f=open(self.fileName)
+ while True:
+ line=f.readline()
+ if line=='': break
+ self.lines.append(line.strip().replace('$NEWLINE', '\n'))
+ f.close()
+ except IOError:
+ pass
+ except:
+ print_exc()
+ def write(self):
+ f=open(self.fileName, 'wb')
+ for line in self.lines:
+ f.write("%s\n"%(line.replace('\n', '$NEWLINE')))
+ f.close()
settings=Settings()
diff --git a/clSong.py b/clSong.py
index 85de8d0..9c1925f 100644
--- a/clSong.py
+++ b/clSong.py
@@ -2,74 +2,74 @@ from PyQt4 import QtCore
# compare two songs with respect to their album
def isSameAlbum(song1, song2):
- return song1.getAlbum()==song2.getAlbum() \
- and song1.getTag('date')==song2.getTag('date')
+ return song1.getAlbum()==song2.getAlbum() \
+ and song1.getTag('date')==song2.getTag('date')
class Song:
- """The Song class offers an abstraction of a song."""
- _data=None
+ """The Song class offers an abstraction of a song."""
+ _data=None
- def __init__(self, data):
- self._data=data
- if 'id' in self._data:
- self._data['id']=int(self._data['id'])
- if 'track' in self._data:
- # make sure the track is a valid number!
- t=self._data['track']
- for i in xrange(len(t)):
- if ord(t[i])<ord('0') or ord(t[i])>ord('9'):
- try:
- self._data['track']=int(t[0:i])
- except:
- self._data['track']=-1
- break
- self._data['track']=int(self._data['track'])
- for tag in self._data.keys():
- if isinstance(self._data[tag], str):
- self._data[tag]=unicode(self._data[tag], "utf-8")
- try:
- self._data['time']=int(self._data['time'])
- self._data['timems']='%i:%i'%(self._data['time'] / 60, self._data['time'] % 60)
- except:
- pass
+ def __init__(self, data):
+ self._data=data
+ if 'id' in self._data:
+ self._data['id']=int(self._data['id'])
+ if 'track' in self._data:
+ # make sure the track is a valid number!
+ t=self._data['track']
+ for i in xrange(len(t)):
+ if ord(t[i])<ord('0') or ord(t[i])>ord('9'):
+ try:
+ self._data['track']=int(t[0:i])
+ except:
+ self._data['track']=-1
+ break
+ self._data['track']=int(self._data['track'])
+ for tag in self._data.keys():
+ if isinstance(self._data[tag], str):
+ self._data[tag]=unicode(self._data[tag], "utf-8")
+ try:
+ self._data['time']=int(self._data['time'])
+ self._data['timems']='%i:%i'%(self._data['time'] / 60, self._data['time'] % 60)
+ except:
+ pass
- def getID(self):
- """Get the ID."""
- return self.getTag('id', -1)
-
- def getTitle(self):
- """Get the title."""
- return self.getTag('title', self._data['file'])
+ def getID(self):
+ """Get the ID."""
+ return self.getTag('id', -1)
+
+ def getTitle(self):
+ """Get the title."""
+ return self.getTag('title', self._data['file'])
- def getArtist(self):
- """Get the artist."""
- return self.getTag('artist', self._data['file'])
+ def getArtist(self):
+ """Get the artist."""
+ return self.getTag('artist', self._data['file'])
- def getTrack(self):
- """Get the track."""
- return self.getTag('track')
+ def getTrack(self):
+ """Get the track."""
+ return self.getTag('track')
- def getAlbum(self):
- """Get the album."""
- return self.getTag('album')
+ def getAlbum(self):
+ """Get the album."""
+ return self.getTag('album')
- def getFilepath(self):
- """Get the filepath."""
- return self._data['file']
+ def getFilepath(self):
+ """Get the filepath."""
+ return self._data['file']
- def match(self, str):
- """Checks if the string str matches this song. Assumes str is lowercase."""
- return str in self.__str__().lower()
+ def match(self, str):
+ """Checks if the string str matches this song. Assumes str is lowercase."""
+ return str in self.__str__().lower()
- def __str__(self):
- return "%s - %s [%s]" % (self.getTag('artist'), self.getTag('title'), self.getTag('album'))
+ def __str__(self):
+ return "%s - %s [%s]" % (self.getTag('artist'), self.getTag('title'), self.getTag('album'))
- def getTag(self, tag, default=''):
- """Get a tag. If it doesn't exist, return $default."""
- if tag in self._data:
- return self._data[tag]
- if tag=='song':
- return self.__str__()
+ def getTag(self, tag, default=''):
+ """Get a tag. If it doesn't exist, return $default."""
+ if tag in self._data:
+ return self._data[tag]
+ if tag=='song':
+ return self.__str__()
- return default
+ return default
diff --git a/format.py b/format.py
index a06d463..9007025 100644
--- a/format.py
+++ b/format.py
@@ -2,115 +2,115 @@ import re
import os
def compile(string):
- """Compile a string into a function which fills in correct values."""
-
- # This function will extract all variables and functions from $string
- # and convert these to functions. That way, it is much faster to run
- # than parse everytime $string.
-
- # we will split the string into sections; a section is either a string
- # or a variable.
-
- str, params=format_parse_rec(string.replace("%", "%%"), 0)
- ps=merge(params)
- func='lambda params: "%s" %% (%s)' % (str.replace('"', '\\"').replace("\n", ""), ps)
- return eval(func)
+ """Compile a string into a function which fills in correct values."""
+
+ # This function will extract all variables and functions from $string
+ # and convert these to functions. That way, it is much faster to run
+ # than parse everytime $string.
+
+ # we will split the string into sections; a section is either a string
+ # or a variable.
+
+ str, params=format_parse_rec(string.replace("%", "%%"), 0)
+ ps=merge(params)
+ func='lambda params: "%s" %% (%s)' % (str.replace('"', '\\"').replace("\n", ""), ps)
+ return eval(func)
def merge(list):
- ret=""
- for l in list:
- ret="%s%s, "%(ret, l)
- return ret[:-2]
+ ret=""
+ for l in list:
+ ret="%s%s, "%(ret, l)
+ return ret[:-2]
func=re.compile('\$[a-z_]+', re.IGNORECASE)
def format_parse_rec(string, start):
- """Compiles a function. Returns [special str, params]."""
- match=func.search(string, start)
- if not match:
- return string[start:], []
+ """Compiles a function. Returns [special str, params]."""
+ match=func.search(string, start)
+ if not match:
+ return string[start:], []
- ret1="" # string
- ret2=[] # params
- # set fixed string
- ret1+=string[start:match.start()]
- name=string[match.start()+1:match.end()]
-
- # here are all functions defined
- if name in ['if', 'ifn', 'dirname']:
- try:
- end=match_bracket(string, string.find("(", match.start()))
- except:
- end=-1
- if end<0:
- raise Exception("No matching brackets")
-
- if name=='if' or name=='ifn':
- # print if something is true/false
- # $if($expr-true, $output)
- # $ifn($expr-false, $output)
- body=string[match.end():end]
- comma=body.find(',')
- s1,p1=format_parse_rec(body[1:comma], 0) # get the if-part
- s2,p2=format_parse_rec(body[comma+1:-1], 0) # if true!
- ret1+="%s"
- if name=='if':
- ret2.append("('%s' %% (%s) if (%s) else '')"%(s2, merge(p2), merge(p1)))
- elif name=='ifn':
- ret2.append("('%s' %% (%s) if not(%s) else '')"%(s2, merge(p2), merge(p1)))
- elif name=='dirname':
- # get the dir of a path
- # $dirname(path)
- s,p=format_parse_rec(string[match.end()+1:end-1], 0)
- ret1+="%s"
- ret2.append('os.path.dirname("%s"%%(%s))'%(s, merge(p)))
- else:
- end=match.end()
- # variable
- ret1+="%s"
- ret2.append('params["%s"]'%(name))
-
- ret=format_parse_rec(string, end)
- ret1+=ret[0]
- if len(ret[1]):
- ret2.extend(ret[1])
- return ret1, ret2
+ ret1="" # string
+ ret2=[] # params
+ # set fixed string
+ ret1+=string[start:match.start()]
+ name=string[match.start()+1:match.end()]
+
+ # here are all functions defined
+ if name in ['if', 'ifn', 'dirname']:
+ try:
+ end=match_bracket(string, string.find("(", match.start()))
+ except:
+ end=-1
+ if end<0:
+ raise Exception("No matching brackets")
+
+ if name=='if' or name=='ifn':
+ # print if something is true/false
+ # $if($expr-true, $output)
+ # $ifn($expr-false, $output)
+ body=string[match.end():end]
+ comma=body.find(',')
+ s1,p1=format_parse_rec(body[1:comma], 0) # get the if-part
+ s2,p2=format_parse_rec(body[comma+1:-1], 0) # if true!
+ ret1+="%s"
+ if name=='if':
+ ret2.append("('%s' %% (%s) if (%s) else '')"%(s2, merge(p2), merge(p1)))
+ elif name=='ifn':
+ ret2.append("('%s' %% (%s) if not(%s) else '')"%(s2, merge(p2), merge(p1)))
+ elif name=='dirname':
+ # get the dir of a path
+ # $dirname(path)
+ s,p=format_parse_rec(string[match.end()+1:end-1], 0)
+ ret1+="%s"
+ ret2.append('os.path.dirname("%s"%%(%s))'%(s, merge(p)))
+ else:
+ end=match.end()
+ # variable
+ ret1+="%s"
+ ret2.append('params["%s"]'%(name))
+
+ ret=format_parse_rec(string, end)
+ ret1+=ret[0]
+ if len(ret[1]):
+ ret2.extend(ret[1])
+ return ret1, ret2
def match_bracket(string, start):
- """Returns position of matching bracket."""
- assert(string[start:start+1]=="(")
- balance=1
- start+=1
- while balance>0:
- if start<=0:
- raise Exception("No matching bracket found")
- l=string.find("(", start)
- r=string.find(")", start)
-
- if l<0:
- if r>0: balance-=1
- else: return -1
- start=r+1
- else:
- if l<r: balance+=1
- elif l>r: balance-=1
- start=min(l,r)+1
-
- if balance==0:
- return r+1
- return max(l,r)+1
-
+ """Returns position of matching bracket."""
+ assert(string[start:start+1]=="(")
+ balance=1
+ start+=1
+ while balance>0:
+ if start<=0:
+ raise Exception("No matching bracket found")
+ l=string.find("(", start)
+ r=string.find(")", start)
+
+ if l<0:
+ if r>0: balance-=1
+ else: return -1
+ start=r+1
+ else:
+ if l<r: balance+=1
+ elif l>r: balance-=1
+ start=min(l,r)+1
+
+ if balance==0:
+ return r+1
+ return max(l,r)+1
+
def params(song, overwrite={}, ensure={"title":"", "artist":""\
- , "album":"", "genre":"", "track":"", "length":"" \
- , "date":"", "time":""}):
- params={}
- for param in ensure:
- params[param]=ensure[param]
- if song:
- for param in song._data:
- params[param]=song._data[param]
- for param in overwrite:
- params[param]=overwrite[param]
- return params
+ , "album":"", "genre":"", "track":"", "length":"" \
+ , "date":"", "time":""}):
+ params={}
+ for param in ensure:
+ params[param]=ensure[param]
+ if song:
+ for param in song._data:
+ params[param]=song._data[param]
+ for param in overwrite:
+ params[param]=overwrite[param]
+ return params
#f=compile("$if($date, ($date) )")
#f=compile("a$if($title,$title)b$if($artist,$artist)c")
diff --git a/jmpc.py b/jmpc.py
index 31d2190..02a7592 100644
--- a/jmpc.py
+++ b/jmpc.py
@@ -13,83 +13,83 @@ class CommandListError(MPDError):
class client:
- _csock=None
- _rfile=None
- _wfile=None
- def __init__(self):
- pass
-
- def connect(self, host, port, type=socket.SOCK_STREAM):
- if self._csock:
- raise ConnectionError("Already connected")
-
- self._csock=socket.socket(socket.AF_INET, type)
- self._csock.connect((host, port))
- self._file = self._csock.makefile("rwb")
- handshake=self._read()
- if handshake[0:len("hello jmpd")]!="hello jmpd":
- print("Failed to handshake: %s" % (handshake))
- self.disconnect()
- return
- self._write("ack")
-
-
- def _read(self):
- return self._file.readline().strip()
- def _write(self, line):
- self._file.write("%s\n" % (line))
-
- def disconnect(self):
- self._csock.close()
- self._csock=None
-
- def command_list_ok_begin(self):
- pass
-
- def command_list_end(self):
- pass
-
- def update(self):
- pass
-
- def status(self):
- pass
-
- def playid(id):
- pass
- def stop(self):
- pass
-
- def pause(self):
- pass
- def resume(self):
- pass
-
- def next(self):
- pass
- def previous(self):
- pass
-
- def seekid(time):
- pass
-
- # Playlist management
- def add(path):
- pass
- def deleteid(path):
- pass
-
- # volume
- def setvol(newvolume):
- pass
-
- # info
- def listallinfo(self):
- pass
- def listplaylistinfo(self):
- pass
- def currentsong(self):
- pass
+ _csock=None
+ _rfile=None
+ _wfile=None
+ def __init__(self):
+ pass
+
+ def connect(self, host, port, type=socket.SOCK_STREAM):
+ if self._csock:
+ raise ConnectionError("Already connected")
+
+ self._csock=socket.socket(socket.AF_INET, type)
+ self._csock.connect((host, port))
+ self._file = self._csock.makefile("rwb")
+ handshake=self._read()
+ if handshake[0:len("hello jmpd")]!="hello jmpd":
+ print("Failed to handshake: %s" % (handshake))
+ self.disconnect()
+ return
+ self._write("ack")
+
+
+ def _read(self):
+ return self._file.readline().strip()
+ def _write(self, line):
+ self._file.write("%s\n" % (line))
+
+ def disconnect(self):
+ self._csock.close()
+ self._csock=None
+
+ def command_list_ok_begin(self):
+ pass
+
+ def command_list_end(self):
+ pass
+
+ def update(self):
+ pass
+
+ def status(self):
+ pass
+
+ def playid(id):
+ pass
+ def stop(self):
+ pass
+
+ def pause(self):
+ pass
+ def resume(self):
+ pass
+
+ def next(self):
+ pass
+ def previous(self):
+ pass
+
+ def seekid(time):
+ pass
+
+ # Playlist management
+ def add(path):
+ pass
+ def deleteid(path):
+ pass
+
+ # volume
+ def setvol(newvolume):
+ pass
+
+ # info
+ def listallinfo(self):
+ pass
+ def listplaylistinfo(self):
+ pass
+ def currentsong(self):
+ pass
from test import port
diff --git a/jmpd.py b/jmpd.py
index 0d36ca0..4b4462d 100644
--- a/jmpd.py
+++ b/jmpd.py
@@ -13,205 +13,205 @@ MUSIC_DIRECTORY="music_directory"
ENCODING="UTF-8"
class server:
- ssocket=None
- _sockets=[]
-
- def __init__(self):
- self._jmpd=jmpd()
- self._sockets=[]
-
- def start(self, port, type=socket.SOCK_STREAM):
- try:
- self.ssocket=socket.socket(socket.AF_INET, type)
- self._start_server(port)
- except:
- print_exc()
- return
-
- def _start_server(self, port):
- self.ssocket.bind(('localhost', port))
- self.ssocket.listen(5)
- self.ssocket.settimeout(None)
-
- print("Started jmpd v%s" % (VERSION))
- while True:
- csock,caddr=self.ssocket.accept()
- self._sockets.append(csock)
- #start_new_thread(self.handle, (csock, caddr))
- self.handle(csock, caddr)
-
- def _read(self, file):
- line=""
- while line=="":
- line=file.readline()
- return line.strip()
- def _write(self, file, line):
- file.write("%s\n" % (line))
+ ssocket=None
+ _sockets=[]
+
+ def __init__(self):
+ self._jmpd=jmpd()
+ self._sockets=[]
+
+ def start(self, port, type=socket.SOCK_STREAM):
+ try:
+ self.ssocket=socket.socket(socket.AF_INET, type)
+ self._start_server(port)
+ except:
+ print_exc()
+ return
+
+ def _start_server(self, port):
+ self.ssocket.bind(('localhost', port))
+ self.ssocket.listen(5)
+ self.ssocket.settimeout(None)
+
+ print("Started jmpd v%s" % (VERSION))
+ while True:
+ csock,caddr=self.ssocket.accept()
+ self._sockets.append(csock)
+ #start_new_thread(self.handle, (csock, caddr))
+ self.handle(csock, caddr)
+
+ def _read(self, file):
+ line=""
+ while line=="":
+ line=file.readline()
+ return line.strip()
+ def _write(self, file, line):
+ file.write("%s\n" % (line))
- def handle(self, csocket, caddr):
- print("Incoming connection from %s" % (str(caddr)))
- file=csocket.makefile('rw', 0)
- self._write(file, 'hello jmpd %s'%(VERSION))
-
- try:
- while True:
- line=self._read(file)
- print line
- except:
- print_exc()
-
- def closeAll(self):
- for socket in self._sockets:
- socket.close()
+ def handle(self, csocket, caddr):
+ print("Incoming connection from %s" % (str(caddr)))
+ file=csocket.makefile('rw', 0)
+ self._write(file, 'hello jmpd %s'%(VERSION))
+
+ try:
+ while True:
+ line=self._read(file)
+ print line
+ except:
+ print_exc()
+
+ def closeAll(self):
+ for socket in self._sockets:
+ socket.close()
def _getName(line):
- return line[0:line.find(': ')]
+ return line[0:line.find(': ')]
def _getValue(line):
- return line[line.find(': ')+2:]
-
+ return line[line.find(': ')+2:]
+
tags=['title', 'artist', 'album', 'tracknumber', 'genre']
class jmpd:
- _lib=None # library
- _settings=None
-
- def __init__(self):
- self._lib=[]
- self._settings=Settings("mpdconf")
- self.readLib()
-
- def writeLib(self):
- all_tags=tags
- encoding=ENCODING
- tags.append('filename')
-
- fout=codecs.open(self._settings.get("db_file"), mode='w', encoding=encoding)
- fout.write("# jmpd\n")
- fout.write("begin info\n")
- fout.write("encoding: %s\n" % (encoding))
- fout.write("jmpd: %s\n" % (VERSION))
- fout.write("end info\n")
- fout.write("\n")
-
- fout.write("begin library\n")
- cur_path=""
- for entry in self._lib:
- if entry['path']!=cur_path:
- fout.write("set path: %s\n" % entry['path'])
- cur_path=entry['path']
-
- fout.write("begin file\n")
- for tag in all_tags:
- if tag in entry:
- fout.write("%s: %s\n" % (tag, entry[tag]))
- fout.write("end file\n")
-
- fout.write("end library\n")
- fout.close()
-
- def readLib(self):
- """Restore the library from file"""
- db_file=self._settings.get("db_file")
- encoding=ENCODING
- if os.path.exists(db_file)==False:
- self._lib=[]
- return
-
- # we first open the file in ASCII-mode, and read on till the real encoding
- # then we reopen it in that encoding
- fin=open(db_file, 'r')
- fin.close()
- fin=codecs.open(db_file, mode='r', encoding=encoding)
-
- self._readLine(fin) # begin info
- encoding=_getValue(self._readLine(fin))
- version=_getValue(self._readLine(fin))
- if version!=VERSION:
- raise Exception("Invalid database file")
- self._readNext(fin, 'end info')
-
- self._readNext(fin, 'begin library')
- cur_path=""
- while True:
- line=self._readLine(fin)
- if line=="end library":
- break
- if _getName(line)=="set path: ":
- cur_path=_getValue(line)
- elif line=="begin file":
- self._readFile(fin, cur_path, encoding)
-
- fin.close()
-
- def _readFile(self, fin, cur_path, encoding):
- """Read the data for a file"""
- entry={'filepath': cur_path}
- line=""
- while line!="end file":
- line=self._readLine(fin)
- if line.find(': ')>0:
- entry[_getName(line)]=_getValue(line)
-
- self._lib.append(entry)
- def _readEnd(self, fin, section):
- """Read the next line containing an end section"""
- line=""
- while line!="end %s"%(section):
- line=self._readLine(fin)
- return line
- def _readBegin(self, fin, section):
- """Read the next line containing a begin section"""
- line=""
- while line!="begin %s"%(section):
- line=self._readLine(fin)
- return line
-
- def _readNext(self, fin, find):
- line=""
- while line!=find:
- line=self._readLine(fin)
- return line
- def _readLine(self, fin):
- """Reads the next line having characters and not being a commented line"""
- ret="\n"
- try:
- while ret=="\n" or ret[0]=='#':
- ret=fin.readline()
- except:
- pass
- if len(ret)==0:
- raise Exception("EOF")
- return ret.strip()
-
-
- def update(self):
- """Look for songs in the music_directory"""
- self._lib=[]
- for dir in self._settings.get(MUSIC_DIRECTORY).split(":"):
- self._update_rec(dir)
-
- self.writeLib()
-
- def _update_rec(self, path):
- subdirs=[]
- for filename in os.listdir(path):
- filepath="%s/%s"%(path,filename)
- if os.path.isdir(filepath):
- subdirs.append(filepath)
- if filename[-4:]==".mp3":
- audio=EasyID3(filepath)
- entry = {'path': path.decode(ENCODING)
- , 'filename': filename.decode(ENCODING)
- }
- for tag in tags:
- try:
- value=str(audio[tag])
- entry[tag]=value[3:-2]
- except:
- entry[tag]=''
- self._lib.append(entry)
-
- for subdir in subdirs:
- self._update_rec(subdir)
+ _lib=None # library
+ _settings=None
+
+ def __init__(self):
+ self._lib=[]
+ self._settings=Settings("mpdconf")
+ self.readLib()
+
+ def writeLib(self):
+ all_tags=tags
+ encoding=ENCODING
+ tags.append('filename')
+
+ fout=codecs.open(self._settings.get("db_file"), mode='w', encoding=encoding)
+ fout.write("# jmpd\n")
+ fout.write("begin info\n")
+ fout.write("encoding: %s\n" % (encoding))
+ fout.write("jmpd: %s\n" % (VERSION))
+ fout.write("end info\n")
+ fout.write("\n")
+
+ fout.write("begin library\n")
+ cur_path=""
+ for entry in self._lib:
+ if entry['path']!=cur_path:
+ fout.write("set path: %s\n" % entry['path'])
+ cur_path=entry['path']
+
+ fout.write("begin file\n")
+ for tag in all_tags:
+ if tag in entry:
+ fout.write("%s: %s\n" % (tag, entry[tag]))
+ fout.write("end file\n")
+
+ fout.write("end library\n")
+ fout.close()
+
+ def readLib(self):
+ """Restore the library from file"""
+ db_file=self._settings.get("db_file")
+ encoding=ENCODING
+ if os.path.exists(db_file)==False:
+ self._lib=[]
+ return
+
+ # we first open the file in ASCII-mode, and read on till the real encoding
+ # then we reopen it in that encoding
+ fin=open(db_file, 'r')
+ fin.close()
+ fin=codecs.open(db_file, mode='r', encoding=encoding)
+
+ self._readLine(fin) # begin info
+ encoding=_getValue(self._readLine(fin))
+ version=_getValue(self._readLine(fin))
+ if version!=VERSION:
+ raise Exception("Invalid database file")
+ self._readNext(fin, 'end info')
+
+ self._readNext(fin, 'begin library')
+ cur_path=""
+ while True:
+ line=self._readLine(fin)
+ if line=="end library":
+ break
+ if _getName(line)=="set path: ":
+ cur_path=_getValue(line)
+ elif line=="begin file":
+ self._readFile(fin, cur_path, encoding)
+
+ fin.close()
+
+ def _readFile(self, fin, cur_path, encoding):
+ """Read the data for a file"""
+ entry={'filepath': cur_path}
+ line=""
+ while line!="end file":
+ line=self._readLine(fin)
+ if line.find(': ')>0:
+ entry[_getName(line)]=_getValue(line)
+
+ self._lib.append(entry)
+ def _readEnd(self, fin, section):
+ """Read the next line containing an end section"""
+ line=""
+ while line!="end %s"%(section):
+ line=self._readLine(fin)
+ return line
+ def _readBegin(self, fin, section):
+ """Read the next line containing a begin section"""
+ line=""
+ while line!="begin %s"%(section):
+ line=self._readLine(fin)
+ return line
+
+ def _readNext(self, fin, find):
+ line=""
+ while line!=find:
+ line=self._readLine(fin)
+ return line
+ def _readLine(self, fin):
+ """Reads the next line having characters and not being a commented line"""
+ ret="\n"
+ try:
+ while ret=="\n" or ret[0]=='#':
+ ret=fin.readline()
+ except:
+ pass
+ if len(ret)==0:
+ raise Exception("EOF")
+ return ret.strip()
+
+
+ def update(self):
+ """Look for songs in the music_directory"""
+ self._lib=[]
+ for dir in self._settings.get(MUSIC_DIRECTORY).split(":"):
+ self._update_rec(dir)
+
+ self.writeLib()
+
+ def _update_rec(self, path):
+ subdirs=[]
+ for filename in os.listdir(path):
+ filepath="%s/%s"%(path,filename)
+ if os.path.isdir(filepath):
+ subdirs.append(filepath)
+ if filename[-4:]==".mp3":
+ audio=EasyID3(filepath)
+ entry = {'path': path.decode(ENCODING)
+ , 'filename': filename.decode(ENCODING)
+ }
+ for tag in tags:
+ try:
+ value=str(audio[tag])
+ entry[tag]=value[3:-2]
+ except:
+ entry[tag]=''
+ self._lib.append(entry)
+
+ for subdir in subdirs:
+ self._update_rec(subdir)
from test import port
s=server()
diff --git a/log.py b/log.py
index 889c804..88a9781 100644
--- a/log.py
+++ b/log.py
@@ -1,36 +1,36 @@
# what is there to log?
-LOG_NONE=0 # nothing
-LOG_IMPORTANT=1 # important things like errors
-LOG_NORMAL=2 # LOG_IMPORTANT + some events, like connect
-LOG_EXTENDED=3 # LOG_NORMAL + more log information
-LOG_DEBUG=4 # LOG_EXTENDED + debug information
+LOG_NONE=0 # nothing
+LOG_IMPORTANT=1 # important things like errors
+LOG_NORMAL=2 # LOG_IMPORTANT + some events, like connect
+LOG_EXTENDED=3 # LOG_NORMAL + more log information
+LOG_DEBUG=4 # LOG_EXTENDED + debug information
def setLevel(newLevel=LOG_NORMAL):
- """Set the current level to log."""
- global curLevel
- curLevel=newLevel
+ """Set the current level to log."""
+ global curLevel
+ curLevel=newLevel
def setWriter(writer):
- """Set the writer to use for logging. This method should have two params: item, level"""
- global curOutput
- curOutput=writer
+ """Set the writer to use for logging. This method should have two params: item, level"""
+ global curOutput
+ curOutput=writer
def _writer(item, level):
- print "%s %s"%(str(level), item)
+ print "%s %s"%(str(level), item)
curLevel=LOG_NORMAL
curOutput=_writer
def normal(item):
- log(item, LOG_NORMAL)
+ log(item, LOG_NORMAL)
def important(item):
- log(item, LOG_IMPORTANT)
+ log(item, LOG_IMPORTANT)
def extended(item):
- log(item, LOG_EXTENDED)
+ log(item, LOG_EXTENDED)
def debug(item):
- log(item, LOG_DEBUG)
+ log(item, LOG_DEBUG)
def log(item, level=LOG_NORMAL):
- if level<=curLevel and item!="":
- curOutput(item, level)
+ if level<=curLevel and item!="":
+ curOutput(item, level)
diff --git a/misc.py b/misc.py
index 0781146..b85cab2 100644
--- a/misc.py
+++ b/misc.py
@@ -15,156 +15,156 @@ appIcon=QtGui.QIcon('gfx/icon.png')
eventLoop=QtCore.QEventLoop()
def doEvents():
- """Make some time for necessary events."""
- eventLoop.processEvents(QtCore.QEventLoop.AllEvents)
+ """Make some time for necessary events."""
+ eventLoop.processEvents(QtCore.QEventLoop.AllEvents)
def sec2min(secs):
- """Converts seconds to min:sec."""
- min=int(secs/60)
- sec=secs%60
- if sec<10:sec='0'+str(sec)
- return str(min)+':'+str(sec)
+ """Converts seconds to min:sec."""
+ min=int(secs/60)
+ sec=secs%60
+ if sec<10:sec='0'+str(sec)
+ return str(min)+':'+str(sec)
def numeric_compare(x, y):
- if x>y:
- return 1
- elif x==y:
- return 0
- return -1
+ if x>y:
+ return 1
+ elif x==y:
+ return 0
+ return -1
def unique(seq):
- """Retrieve list of unique elements."""
- seen = []
- return t(c for c in seq if not (c in seen or seen.append(c)))
+ """Retrieve list of unique elements."""
+ seen = []
+ return t(c for c in seq if not (c in seen or seen.append(c)))
def toAscii(ustr):
- if type(ustr)==str:
- return ustr
- return unicodedata.normalize('NFKD', ustr).encode('ascii', 'ignore')
+ if type(ustr)==str:
+ return ustr
+ return unicodedata.normalize('NFKD', ustr).encode('ascii', 'ignore')
def fetch(SE, sites, song=None, xtra_tags={}, stripHTML=True):
- """Returns None when nothing found, or [site,source-url]."""
- # compose the search-engine URL
- f=format.compile(SE)
- SE_url=toAscii(f(format.params(song, xtra_tags)))
- SE_url=SE_url.replace(' ', '+')
-
- # fetch the page from the search-engine with the results
- request=urllib2.Request(SE_url)
- request.add_header('User-Agent', 'montypc')
- opener=urllib2.build_opener()
- data=opener.open(request).read()
-
- # look for urls on the search page!
- regex=re.compile('<a href="(.*?)".*?>.*?<\/a>')
- urls=regex.findall(data)
- log.debug("all urls")
- for url in urls:
- log.debug(" %s"%(url))
-
- # look for urls which are defined in $sites.
- # The first matching URL is taken
- finalRegex=None
- log.debug("Checking %i URLs on %s"%(len(sites), SE_url))
- # loop over all sites which may have what we're interested in
- for site in sites:
- finalURL=None
- finalRegex=None
- # check if on the results-page there is a link to $site
- for url in urls:
- if url.find(site)>=0:
- log.debug(" Found site %s in results: %s"%(site, url))
- finalURL=url
- finalRegex=sites[site]
- break
-
- if finalURL:
- match=None
- # open the url
- cj = cookielib.CookieJar()
- opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
- log.debug(" Reading URL %s"%(finalURL))
- try:
- # read the page
- r = opener.open(finalURL)
- data=r.read()
- # perform the regular expression
- regex=re.compile(finalRegex, re.IGNORECASE|re.MULTILINE|re.DOTALL)
- match=regex.search(data)
- except Exception, e:
- log.debug(" Failed to open site %s"%(finalURL))
- continue
-
- if match:
- # if the regex matches, then we arrive here
- # we assume the content we want is in the first group
- log.debug(" Regex succeeded!")
- try:
- charset=re.compile('charset=["\']?([\w-]+)').search(data).group(1)
- log.debug(" charset=%s"%(charset))
- except:
- charset='iso-8859-1'
- log.debug(" charset not found. Assuming %s"%(charset))
- data=match.group(1)
- data=re.sub(chr(13), '', data) # replace ^M aka \r
- data=unicode(data, charset)
- if stripHTML:
- # do we want HTML?
- data=re.sub('<br.*?>', '\n', data) # replace <br />'s with newline
- data=re.sub('\n\n', '\n', data)
- data=re.sub('<[^>]*?>', '', data) # strip all other HTML
- data=decodeHTMLEntities(data) # convert HTML entities
- data=data.strip()
- log.debug("Succeeded fetching.")
- return [data,finalURL]
- else:
- log.debug(" Regex for %s%s failed"%(site, (" (%s)"%(finalURL) if finalURL else "")))
- else:
- log.debug(" Site %s not found on results-page"%(site))
-
-
- log.debug("Failed fetching.")
- return None
+ """Returns None when nothing found, or [site,source-url]."""
+ # compose the search-engine URL
+ f=format.compile(SE)
+ SE_url=toAscii(f(format.params(song, xtra_tags)))
+ SE_url=SE_url.replace(' ', '+')
+
+ # fetch the page from the search-engine with the results
+ request=urllib2.Request(SE_url)
+ request.add_header('User-Agent', 'montypc')
+ opener=urllib2.build_opener()
+ data=opener.open(request).read()
+
+ # look for urls on the search page!
+ regex=re.compile('<a href="(.*?)".*?>.*?<\/a>')
+ urls=regex.findall(data)
+ log.debug("all urls")
+ for url in urls:
+ log.debug(" %s"%(url))
+
+ # look for urls which are defined in $sites.
+ # The first matching URL is taken
+ finalRegex=None
+ log.debug("Checking %i URLs on %s"%(len(sites), SE_url))
+ # loop over all sites which may have what we're interested in
+ for site in sites:
+ finalURL=None
+ finalRegex=None
+ # check if on the results-page there is a link to $site
+ for url in urls:
+ if url.find(site)>=0:
+ log.debug(" Found site %s in results: %s"%(site, url))
+ finalURL=url
+ finalRegex=sites[site]
+ break
+
+ if finalURL:
+ match=None
+ # open the url
+ cj = cookielib.CookieJar()
+ opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
+ log.debug(" Reading URL %s"%(finalURL))
+ try:
+ # read the page
+ r = opener.open(finalURL)
+ data=r.read()
+ # perform the regular expression
+ regex=re.compile(finalRegex, re.IGNORECASE|re.MULTILINE|re.DOTALL)
+ match=regex.search(data)
+ except Exception, e:
+ log.debug(" Failed to open site %s"%(finalURL))
+ continue
+
+ if match:
+ # if the regex matches, then we arrive here
+ # we assume the content we want is in the first group
+ log.debug(" Regex succeeded!")
+ try:
+ charset=re.compile('charset=["\']?([\w-]+)').search(data).group(1)
+ log.debug(" charset=%s"%(charset))
+ except:
+ charset='iso-8859-1'
+ log.debug(" charset not found. Assuming %s"%(charset))
+ data=match.group(1)
+ data=re.sub(chr(13), '', data) # replace ^M aka \r
+ data=unicode(data, charset)
+ if stripHTML:
+ # do we want HTML?
+ data=re.sub('<br.*?>', '\n', data) # replace <br />'s with newline
+ data=re.sub('\n\n', '\n', data)
+ data=re.sub('<[^>]*?>', '', data) # strip all other HTML
+ data=decodeHTMLEntities(data) # convert HTML entities
+ data=data.strip()
+ log.debug("Succeeded fetching.")
+ return [data,finalURL]
+ else:
+ log.debug(" Regex for %s%s failed"%(site, (" (%s)"%(finalURL) if finalURL else "")))
+ else:
+ log.debug(" Site %s not found on results-page"%(site))
+
+
+ log.debug("Failed fetching.")
+ return None
def substEntity(match):
- ent = match.group(2)
- if match.group(1) == "#":
- return unichr(int(ent))
- else:
- cp = n2cp.get(ent)
+ ent = match.group(2)
+ if match.group(1) == "#":
+ return unichr(int(ent))
+ else:
+ cp = n2cp.get(ent)
- if cp:
- return unichr(cp)
- else:
- return match.group()
+ if cp:
+ return unichr(cp)
+ else:
+ return match.group()
def decodeHTMLEntities(string):
- # replace entities with their UTF-counterpart
- entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8});")
- return entity_re.subn(substEntity, string)[0]
+ # replace entities with their UTF-counterpart
+ entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8});")
+ return entity_re.subn(substEntity, string)[0]
-
+
class Button(QtGui.QPushButton):
- iconSize=32
- """A simple Button class which calls $onClick when clicked."""
- def __init__(self, caption, onClick=None, iconPath=None, iconOnly=False, parent=None):
- QtGui.QPushButton.__init__(self, parent)
-
- if onClick:
- self.connect(self, QtCore.SIGNAL('clicked(bool)'), onClick)
- if iconPath:
- self.changeIcon(iconPath)
-
- if not(iconPath and iconOnly):
- QtGui.QPushButton.setText(self, caption)
-
- self.setToolTip(caption)
-
- def setText(self, caption):
- self.setToolTip(caption)
- if self.icon()==None:
- self.setText(caption)
-
- def changeIcon(self, iconPath):
- icon=QtGui.QIcon()
- icon.addFile(iconPath, QtCore.QSize(self.iconSize, self.iconSize))
- self.setIcon(icon)
+ iconSize=32
+ """A simple Button class which calls $onClick when clicked."""
+ def __init__(self, caption, onClick=None, iconPath=None, iconOnly=False, parent=None):
+ QtGui.QPushButton.__init__(self, parent)
+
+ if onClick:
+ self.connect(self, QtCore.SIGNAL('clicked(bool)'), onClick)
+ if iconPath:
+ self.changeIcon(iconPath)
+
+ if not(iconPath and iconOnly):
+ QtGui.QPushButton.setText(self, caption)
+
+ self.setToolTip(caption)
+
+ def setText(self, caption):
+ self.setToolTip(caption)
+ if self.icon()==None:
+ self.setText(caption)
+
+ def changeIcon(self, iconPath):
+ icon=QtGui.QIcon()
+ icon.addFile(iconPath, QtCore.QSize(self.iconSize, self.iconSize))
+ self.setIcon(icon)
diff --git a/montypc.py b/montypc.py
index 5d410ab..3b26401 100755
--- a/montypc.py
+++ b/montypc.py
@@ -18,7 +18,7 @@ homePage = "jerous.psboard.org/montypc"
bugEmail = "jerous@gmail.com"
aboutData = KAboutData (appName, catalog, programName, version, description,
- license, copyright, text, homePage, bugEmail)
+ license, copyright, text, homePage, bugEmail)
KCmdLineArgs.init (sys.argv, aboutData)
app = KApplication()
@@ -29,10 +29,10 @@ from clMonty import monty
from traceback import print_exc
try:
- wMain = winMain()
- wMain.show()
- app.exec_()
- wMain.quit()
+ wMain = winMain()
+ wMain.show()
+ app.exec_()
+ wMain.quit()
except Exception, e:
- print_exc()
+ print_exc()
diff --git a/mpd.py b/mpd.py
index d7eea05..0f9b88a 100644
--- a/mpd.py
+++ b/mpd.py
@@ -147,10 +147,10 @@ class MPDClient(object):
def _writecommand(self, command, args=[]):
parts = [command]
for arg in args:
- if type(arg)==int:
- parts.append('"%i"' % arg)
- else:
- parts.append(u'"%s"' % escape(arg))
+ if type(arg)==int:
+ parts.append('"%i"' % arg)
+ else:
+ parts.append(u'"%s"' % escape(arg))
self._writeline(u" ".join(parts))
def _readline(self):
diff --git a/plugins/AlbumCover.py b/plugins/AlbumCover.py
index cb67f51..d2952ca 100644
--- a/plugins/AlbumCover.py
+++ b/plugins/AlbumCover.py
@@ -17,207 +17,207 @@ ALBUMCOVER_FETCH0_DEFAULT=1
ALBUMCOVER_FETCH1_DEFAULT=1
class wgAlbumCover(QtGui.QWidget):
- " container for the image"
- img=None
- imgLoaded=False
- p=None # plugin
- acFormat=None
- def __init__(self, p, parent=None):
- QtGui.QWidget.__init__(self,parent)
- self.p=p
- self.img=QtGui.QImage()
- self.setMinimumSize(64,64)
-
- def mousePressEvent(self, event):
- if event.button()==QtCore.Qt.RightButton:
- song=monty.getCurrentSong()
- file=QtGui.QFileDialog.getOpenFileName(self
- , "Select album cover for %s - %s"%(song.getArtist(), song.getAlbum())
- , ""
- , "Images ("+" ".join(map(lambda ext: "*.%s"%(ext), ALBUMCOVER_GFX_EXTS_DEFAULT.split(',')))+")"
- )
- if file:
- cur=self.getLocalACPath(song, probe=True)
- # remove the previous cover
- try:
- if cur:
- self.p.extended("removing %s"%(cur))
- os.remove(cur)
- else:
- cur=self.getLocalACPath(song, probe=False)
- self.p.extended("copy %s to %s"%(file, cur))
- shutil.copy(file, cur)
- except Exception, e:
- self.p.normal("failed to set new cover: %s"%(str(e)))
- else:
- return
- self.refresh()
-
- def getIMG(self):
- if self.imgLoaded:
- return self.img
- return None
+ " container for the image"
+ img=None
+ imgLoaded=False
+ p=None # plugin
+ acFormat=None
+ def __init__(self, p, parent=None):
+ QtGui.QWidget.__init__(self,parent)
+ self.p=p
+ self.img=QtGui.QImage()
+ self.setMinimumSize(64,64)
+
+ def mousePressEvent(self, event):
+ if event.button()==QtCore.Qt.RightButton:
+ song=monty.getCurrentSong()
+ file=QtGui.QFileDialog.getOpenFileName(self
+ , "Select album cover for %s - %s"%(song.getArtist(), song.getAlbum())
+ , ""
+ , "Images ("+" ".join(map(lambda ext: "*.%s"%(ext), ALBUMCOVER_GFX_EXTS_DEFAULT.split(',')))+")"
+ )
+ if file:
+ cur=self.getLocalACPath(song, probe=True)
+ # remove the previous cover
+ try:
+ if cur:
+ self.p.extended("removing %s"%(cur))
+ os.remove(cur)
+ else:
+ cur=self.getLocalACPath(song, probe=False)
+ self.p.extended("copy %s to %s"%(file, cur))
+ shutil.copy(file, cur)
+ except Exception, e:
+ self.p.normal("failed to set new cover: %s"%(str(e)))
+ else:
+ return
+ self.refresh()
+
+ def getIMG(self):
+ if self.imgLoaded:
+ return self.img
+ return None
- def paintEvent(self, event):
- l=min(self.width(),self.height())
- p=QtGui.QPainter(self)
- rect=QtCore.QRectF((self.width()-l)/2,0,l,l)
- p.drawImage(rect,self.img)
+ def paintEvent(self, event):
+ l=min(self.width(),self.height())
+ p=QtGui.QPainter(self)
+ rect=QtCore.QRectF((self.width()-l)/2,0,l,l)
+ p.drawImage(rect,self.img)
- def refresh(self):
- self.p.extended("refreshing cover")
- song=monty.getCurrentSong()
- try:
- song._data['file']
- except:
- self.img.load('')
- self.update()
- return
- start_new_thread(self.fetchCover, (song,))
-
- def fetchCover(self, song):
- """Fetch cover (from internet or local dir)"""
- # set default cover
- self.imgLoaded=False
- self.img.load('gfx/no-cd-cover.png')
- self.acFormat=format.compile(self.p.getSetting('downloadto'))
- for i in [0, 1]:
- src=self.p.getSetting('fetch%i'%(i))
- if src!=AC_NO_FETCH and self.fetchCoverSrc(song, src):
- # ACK!
- self.imgLoaded=True
- break
- self.update()
-
- def getLocalACPath(self, song, probe):
- """Get the local path of an albumcover. If $probe, then try covers*exts for existing file."""
- # fetch gfx extensions
- exts=self.p.getSetting('gfx_exts').split(',')
- exts=map(lambda ext: ext.strip(), exts)
-
- # fetch cover album titles
- covers=self.p.getSetting('files').split(',')
- covers=map(lambda title: title.strip(), covers)
-
- params={'music_dir': mpdSettings.get('music_directory'), 'cover':'%s.%s'%(covers[0], exts[0])}
- if probe:
- self.p.debug("probing ...")
- for cover in covers:
- for ext in exts:
- params['cover']='%s.%s'%(cover, ext)
- path=self.acFormat(format.params(song, params))
- self.p.debug(" path: %s"%(path))
- fInfo=QtCore.QFileInfo(path)
- if fInfo.exists():
- self.p.debug(" OK!")
- return path
- self.p.debug("done probing: no matching albumcover found")
- return None
- else:
- self.p.debug("no probing")
- path=self.acFormat(format.params(song, params))
- self.p.debug(" path: %s"%(path))
- return path
+ def refresh(self):
+ self.p.extended("refreshing cover")
+ song=monty.getCurrentSong()
+ try:
+ song._data['file']
+ except:
+ self.img.load('')
+ self.update()
+ return
+ start_new_thread(self.fetchCover, (song,))
+
+ def fetchCover(self, song):
+ """Fetch cover (from internet or local dir)"""
+ # set default cover
+ self.imgLoaded=False
+ self.img.load('gfx/no-cd-cover.png')
+ self.acFormat=format.compile(self.p.getSetting('downloadto'))
+ for i in [0, 1]:
+ src=self.p.getSetting('fetch%i'%(i))
+ if src!=AC_NO_FETCH and self.fetchCoverSrc(song, src):
+ # ACK!
+ self.imgLoaded=True
+ break
+ self.update()
+
+ def getLocalACPath(self, song, probe):
+ """Get the local path of an albumcover. If $probe, then try covers*exts for existing file."""
+ # fetch gfx extensions
+ exts=self.p.getSetting('gfx_exts').split(',')
+ exts=map(lambda ext: ext.strip(), exts)
+
+ # fetch cover album titles
+ covers=self.p.getSetting('files').split(',')
+ covers=map(lambda title: title.strip(), covers)
+
+ params={'music_dir': mpdSettings.get('music_directory'), 'cover':'%s.%s'%(covers[0], exts[0])}
+ if probe:
+ self.p.debug("probing ...")
+ for cover in covers:
+ for ext in exts:
+ params['cover']='%s.%s'%(cover, ext)
+ path=self.acFormat(format.params(song, params))
+ self.p.debug(" path: %s"%(path))
+ fInfo=QtCore.QFileInfo(path)
+ if fInfo.exists():
+ self.p.debug(" OK!")
+ return path
+ self.p.debug("done probing: no matching albumcover found")
+ return None
+ else:
+ self.p.debug("no probing")
+ path=self.acFormat(format.params(song, params))
+ self.p.debug(" path: %s"%(path))
+ return path
-
- def fetchCoverSrc(self, song, src):
- """Fetch the album cover for $song from $src."""
- if not src in [AC_FETCH_INTERNET, AC_FETCH_LOCAL_DIR]:
- print "wgAlbumCover::fetchCover - invalid source "+str(src)
- return False
-
- if src==AC_FETCH_INTERNET:
- # look on the internetz!
- try:
- if not song.getArtist() or not song.getAlbum():
- return False
- # get the url from amazon WS
- coverURL=AmazonAlbumImage(song.getArtist(), song.getAlbum()).fetch()
- self.p.extended("fetch from Amazon")
- if not coverURL:
- self.p.normal("not found on Amazon")
- return False
- # read the url, i.e. retrieve image data
- img=urllib.urlopen(coverURL)
- # where do we save to?
- file=self.getLocalACPath(song, False)
- # open file, and write the read of img!
- f=open(file,'wb')
- f.write(img.read())
- f.close()
+
+ def fetchCoverSrc(self, song, src):
+ """Fetch the album cover for $song from $src."""
+ if not src in [AC_FETCH_INTERNET, AC_FETCH_LOCAL_DIR]:
+ print "wgAlbumCover::fetchCover - invalid source "+str(src)
+ return False
+
+ if src==AC_FETCH_INTERNET:
+ # look on the internetz!
+ try:
+ if not song.getArtist() or not song.getAlbum():
+ return False
+ # get the url from amazon WS
+ coverURL=AmazonAlbumImage(song.getArtist(), song.getAlbum()).fetch()
+ self.p.extended("fetch from Amazon")
+ if not coverURL:
+ self.p.normal("not found on Amazon")
+ return False
+ # read the url, i.e. retrieve image data
+ img=urllib.urlopen(coverURL)
+ # where do we save to?
+ file=self.getLocalACPath(song, False)
+ # open file, and write the read of img!
+ f=open(file,'wb')
+ f.write(img.read())
+ f.close()
- # load image; should work now!
- self.img.load(file)
- return True
- except:
- self.p.normal("failed to download cover from Amazon")
- print_exc()
- return False
-
- file=self.getLocalACPath(song, True)
- if file:
- try:
- self.img.load(file)
- self.p.extended("cover set!")
- return True
- except:
- self.p.normal("failed to load %s"%(path))
+ # load image; should work now!
+ self.img.load(file)
+ return True
+ except:
+ self.p.normal("failed to download cover from Amazon")
+ print_exc()
+ return False
+
+ file=self.getLocalACPath(song, True)
+ if file:
+ try:
+ self.img.load(file)
+ self.p.extended("cover set!")
+ return True
+ except:
+ self.p.normal("failed to load %s"%(path))
class pluginAlbumCover(Plugin):
- o=None
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'AlbumCover')
- self.addMontyListener('onSongChange', self.onEvent)
- self.addMontyListener('onReady', self.onEvent)
- self.addMontyListener('onDisconnect', self.onEvent)
- self.addMontyListener('onStateChange', self.onEvent)
-
- def _load(self):
- self.o=wgAlbumCover(self, None)
- self.o.refresh()
- def _unload(self):
- self.o=None
- def getInfo(self):
- return "Display the album cover of the currently playing album."
- def getExtInfo(self):
- return "Displays the album cover of the currently playing album in a widget.\n" \
- "This album cover can be fetched from various locations:\n" \
- " local dir: the directory in which the album is located;\n" \
- " internet: look on amazon for the album and corresponding cover\n" \
- "Settings:\n" \
- " albumcover.fetch$i: what source to fetch from on step $i. If step $i fails, move on to step $i+1;\n" \
- " albumcover.downloadto: where to download album covers from internet to. This string can contain the normal tags of the current playing song, plus $music_dir and $cover.\n" \
- " albumcover.files: comma separated list of filenames (without extension)to be considered an album cover. Extensions jpg, jpeg, png, gif and bmp are used.\n"
+ o=None
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'AlbumCover')
+ self.addMontyListener('onSongChange', self.onEvent)
+ self.addMontyListener('onReady', self.onEvent)
+ self.addMontyListener('onDisconnect', self.onEvent)
+ self.addMontyListener('onStateChange', self.onEvent)
+
+ def _load(self):
+ self.o=wgAlbumCover(self, None)
+ self.o.refresh()
+ def _unload(self):
+ self.o=None
+ def getInfo(self):
+ return "Display the album cover of the currently playing album."
+ def getExtInfo(self):
+ return "Displays the album cover of the currently playing album in a widget.\n" \
+ "This album cover can be fetched from various locations:\n" \
+ " local dir: the directory in which the album is located;\n" \
+ " internet: look on amazon for the album and corresponding cover\n" \
+ "Settings:\n" \
+ " albumcover.fetch$i: what source to fetch from on step $i. If step $i fails, move on to step $i+1;\n" \
+ " albumcover.downloadto: where to download album covers from internet to. This string can contain the normal tags of the current playing song, plus $music_dir and $cover.\n" \
+ " albumcover.files: comma separated list of filenames (without extension)to be considered an album cover. Extensions jpg, jpeg, png, gif and bmp are used.\n"
- def getWidget(self):
- return self.o
+ def getWidget(self):
+ return self.o
- def _getDockWidget(self):
- return self._createDock(self.o)
+ def _getDockWidget(self):
+ return self._createDock(self.o)
- def onEvent(self, params):
- self.o.refresh()
+ def onEvent(self, params):
+ self.o.refresh()
- def _getSettings(self):
- ret=[]
- nums=['first', 'second']
- actions=[QtGui.QComboBox(), QtGui.QComboBox()]
- for i,action in enumerate(actions):
- setting='fetch%i'%(i)
- action.addItem("On the %s action, Monty rested."%(nums[i]))
- action.addItem("Local dir")
- action.addItem("Internet")
- action.setCurrentIndex(int(self.getSetting(setting, str(i+1))))
- ret.append([setting, 'Action %i'%(i+1), 'What to do on the %s step.'%(nums[i]), action])
+ def _getSettings(self):
+ ret=[]
+ nums=['first', 'second']
+ actions=[QtGui.QComboBox(), QtGui.QComboBox()]
+ for i,action in enumerate(actions):
+ setting='fetch%i'%(i)
+ action.addItem("On the %s action, Monty rested."%(nums[i]))
+ action.addItem("Local dir")
+ action.addItem("Internet")
+ action.setCurrentIndex(int(self.getSetting(setting, str(i+1))))
+ ret.append([setting, 'Action %i'%(i+1), 'What to do on the %s step.'%(nums[i]), action])
- ret.append(['downloadto', 'Local dir', 'Specifies where to save album covers fetched from internet to.\nPossible tags: music_dir, cover, file, artist, title, album', QtGui.QLineEdit(self.getSetting('downloadto'))])
- ret.append(['files', 'Album cover files', 'Comma separated list of titles that are to be considered album covers. E.g. cover,album.', QtGui.QLineEdit(self.getSetting('files'))])
-
- return ret
-
- def afterSaveSettings(self):
- self.o.refresh()
+ ret.append(['downloadto', 'Local dir', 'Specifies where to save album covers fetched from internet to.\nPossible tags: music_dir, cover, file, artist, title, album', QtGui.QLineEdit(self.getSetting('downloadto'))])
+ ret.append(['files', 'Album cover files', 'Comma separated list of titles that are to be considered album covers. E.g. cover,album.', QtGui.QLineEdit(self.getSetting('files'))])
+
+ return ret
+
+ def afterSaveSettings(self):
+ self.o.refresh()
# This is the amazon cover fetcher using their webservice api
@@ -228,48 +228,48 @@ import urllib
AMAZON_AWS_ID = "0K4RZZKHSB5N2XYJWF02"
class AmazonAlbumImage(object):
- awsurl = "http://ecs.amazonaws.com/onca/xml"
- def __init__(self, artist, album):
- self.artist = artist
- self.album = album
-
- def fetch(self):
- url = self._GetResultURL(self._SearchAmazon())
- if not url:
- return None
- img_re = re.compile(r'''registerImage\("original_image", "([^"]+)"''')
- try:
- prod_data = urllib.urlopen(url).read()
- except:
- self.important("timeout opening %s"%(url))
- return None
- m = img_re.search(prod_data)
- if not m:
- return None
- img_url = m.group(1)
- return img_url
+ awsurl = "http://ecs.amazonaws.com/onca/xml"
+ def __init__(self, artist, album):
+ self.artist = artist
+ self.album = album
+
+ def fetch(self):
+ url = self._GetResultURL(self._SearchAmazon())
+ if not url:
+ return None
+ img_re = re.compile(r'''registerImage\("original_image", "([^"]+)"''')
+ try:
+ prod_data = urllib.urlopen(url).read()
+ except:
+ self.important("timeout opening %s"%(url))
+ return None
+ m = img_re.search(prod_data)
+ if not m:
+ return None
+ img_url = m.group(1)
+ return img_url
- def _SearchAmazon(self):
- data = {
- "Service": "AWSECommerceService",
- "Version": "2005-03-23",
- "Operation": "ItemSearch",
- "ContentType": "text/xml",
- "SubscriptionId": AMAZON_AWS_ID,
- "SearchIndex": "Music",
- "ResponseGroup": "Small",
- }
-
- data["Artist"] = self.artist
- data["Keywords"] = self.album
-
- fd = urllib.urlopen("%s?%s" % (self.awsurl, urllib.urlencode(data)))
- return fd.read()
+ def _SearchAmazon(self):
+ data = {
+ "Service": "AWSECommerceService",
+ "Version": "2005-03-23",
+ "Operation": "ItemSearch",
+ "ContentType": "text/xml",
+ "SubscriptionId": AMAZON_AWS_ID,
+ "SearchIndex": "Music",
+ "ResponseGroup": "Small",
+ }
+
+ data["Artist"] = self.artist
+ data["Keywords"] = self.album
+
+ fd = urllib.urlopen("%s?%s" % (self.awsurl, urllib.urlencode(data)))
+ return fd.read()
- def _GetResultURL(self, xmldata):
- if not xmldata:
- return None
- url_re = re.compile(r"<DetailPageURL>([^<]+)</DetailPageURL>")
- m = url_re.search(xmldata)
- return m and m.group(1)
+ def _GetResultURL(self, xmldata):
+ if not xmldata:
+ return None
+ url_re = re.compile(r"<DetailPageURL>([^<]+)</DetailPageURL>")
+ m = url_re.search(xmldata)
+ return m and m.group(1)
diff --git a/plugins/Library.py b/plugins/Library.py
index b1d86e9..bcd4ae7 100644
--- a/plugins/Library.py
+++ b/plugins/Library.py
@@ -6,69 +6,69 @@ from wgPlaylist import Playlist
from wgSongList import clrRowSel
LIBRARY_MODES_DEFAULT='$artist\n'\
- '$artist/$date - $album\n'\
- '$artist - $album\n'\
- '$album ($artist)\n'\
- '$genre\n'\
- '$genre/$artist\n'\
- '$genre/$artist - $album\n'
+ '$artist/$date - $album\n'\
+ '$artist - $album\n'\
+ '$album ($artist)\n'\
+ '$genre\n'\
+ '$genre/$artist\n'\
+ '$genre/$artist - $album\n'
class pluginLibrary(Plugin):
- o=None
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'Library')
- def _load(self):
- self.o=Playlist(self.winMain, self, ['song'], 'Library'
- , self.onDoubleClick, self.onKeyPress, self.getSetting('modes').split('\n'))
- def _unload(self):
- self.o=None
- def getInfo(self):
- return "List showing all the songs allowing filtering and grouping."
-
- def getList(self):
- return self.o
-
- def _getDockWidget(self):
- return self._createDock(self.o)
-
- def onDoubleClick(self):
- self.addLibrarySelToPlaylist()
+ o=None
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Library')
+ def _load(self):
+ self.o=Playlist(self.winMain, self, ['song'], 'Library'
+ , self.onDoubleClick, self.onKeyPress, self.getSetting('modes').split('\n'))
+ def _unload(self):
+ self.o=None
+ def getInfo(self):
+ return "List showing all the songs allowing filtering and grouping."
+
+ def getList(self):
+ return self.o
+
+ def _getDockWidget(self):
+ return self._createDock(self.o)
+
+ def onDoubleClick(self):
+ self.addLibrarySelToPlaylist()
- def onKeyPress(self, event):
- # Add selection, or entire library to playlist using ENTER-key.
- if event.key()==QtCore.Qt.Key_Enter or event.key()==QtCore.Qt.Key_Return:
- self.addLibrarySelToPlaylist()
- return QtGui.QWidget.keyPressEvent(self.o, event)
+ def onKeyPress(self, event):
+ # Add selection, or entire library to playlist using ENTER-key.
+ if event.key()==QtCore.Qt.Key_Enter or event.key()==QtCore.Qt.Key_Return:
+ self.addLibrarySelToPlaylist()
+ return QtGui.QWidget.keyPressEvent(self.o, event)
- def addLibrarySelToPlaylist(self):
- """Add the library selection to the playlist."""
- songs=self.o.selectedSongs()
- self.setStatus('Adding '+str(len(songs))+' songs to library ...')
- doEvents()
+ def addLibrarySelToPlaylist(self):
+ """Add the library selection to the playlist."""
+ songs=self.o.selectedSongs()
+ self.setStatus('Adding '+str(len(songs))+' songs to library ...')
+ doEvents()
- # add filepaths of selected songs to path
- paths=map(lambda song: unicode(song.getFilepath()), songs)
- # add in chunks of 1000
- CHUNK_SIZE=1000
- start=0
- while start<len(paths):
- if start+CHUNK_SIZE<len(paths):
- end=start+CHUNK_SIZE
- else:
- end=len(paths)
- self.setStatus('Adding '+str(len(songs))+' songs to library: %i%%'%(int(100*start/len(paths))))
- doEvents()
- monty.addToPlaylist(paths[start:end])
- start+=CHUNK_SIZE
+ # add filepaths of selected songs to path
+ paths=map(lambda song: unicode(song.getFilepath()), songs)
+ # add in chunks of 1000
+ CHUNK_SIZE=1000
+ start=0
+ while start<len(paths):
+ if start+CHUNK_SIZE<len(paths):
+ end=start+CHUNK_SIZE
+ else:
+ end=len(paths)
+ self.setStatus('Adding '+str(len(songs))+' songs to library: %i%%'%(int(100*start/len(paths))))
+ doEvents()
+ monty.addToPlaylist(paths[start:end])
+ start+=CHUNK_SIZE
- self.setStatus('')
- doEvents()
- self.getWinMain().fillPlaylist()
+ self.setStatus('')
+ doEvents()
+ self.getWinMain().fillPlaylist()
- def _getSettings(self):
- modes=QtGui.QTextEdit()
- modes.insertPlainText(self.getSetting('modes'))
- return [
- ['modes', 'Modes', 'Sets the available modes.', modes],
- ]
- def afterSaveSettings(self):
- self.o.setModes(self.getSetting('modes').split('\n'))
+ def _getSettings(self):
+ modes=QtGui.QTextEdit()
+ modes.insertPlainText(self.getSetting('modes'))
+ return [
+ ['modes', 'Modes', 'Sets the available modes.', modes],
+ ]
+ def afterSaveSettings(self):
+ self.o.setModes(self.getSetting('modes').split('\n'))
diff --git a/plugins/Logger.py b/plugins/Logger.py
index 575fd5b..4e8ce6b 100644
--- a/plugins/Logger.py
+++ b/plugins/Logger.py
@@ -6,62 +6,62 @@ from misc import Button
LOGGER_LEVEL_DEFAULT=log.LOG_NORMAL
class wgLogger(QtGui.QWidget):
- """Shows log information"""
- " list containing the log"
- log=None
- btnClear=None
- p=None
- def __init__(self, p, parent=None):
- QtGui.QWidget.__init__(self, parent)
- self.p=p
-
- self.log=QtGui.QListWidget(self)
- self.btnClear=Button("Clear", self.clear)
- self.cmbLevel=QtGui.QComboBox(self)
-
- self.cmbLevel.addItem("Nothing")
- self.cmbLevel.addItem("Important")
- self.cmbLevel.addItem("Normal")
- self.cmbLevel.addItem("Extended")
- self.cmbLevel.addItem("Debug")
-
- self.cmbLevel.setCurrentIndex(int(self.p.getSetting("level")))
- self.onCmbLevelChanged(self.cmbLevel.currentIndex())
+ """Shows log information"""
+ " list containing the log"
+ log=None
+ btnClear=None
+ p=None
+ def __init__(self, p, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.p=p
+
+ self.log=QtGui.QListWidget(self)
+ self.btnClear=Button("Clear", self.clear)
+ self.cmbLevel=QtGui.QComboBox(self)
+
+ self.cmbLevel.addItem("Nothing")
+ self.cmbLevel.addItem("Important")
+ self.cmbLevel.addItem("Normal")
+ self.cmbLevel.addItem("Extended")
+ self.cmbLevel.addItem("Debug")
+
+ self.cmbLevel.setCurrentIndex(int(self.p.getSetting("level")))
+ self.onCmbLevelChanged(self.cmbLevel.currentIndex())
- layout=QtGui.QVBoxLayout()
- layout2=QtGui.QHBoxLayout()
- self.setLayout(layout)
+ layout=QtGui.QVBoxLayout()
+ layout2=QtGui.QHBoxLayout()
+ self.setLayout(layout)
- layout2.addWidget(self.cmbLevel)
- layout2.addWidget(self.btnClear)
- layout.addLayout(layout2)
- layout.addWidget(self.log)
-
- self.connect(self.cmbLevel, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbLevelChanged)
-
- def onCmbLevelChanged(self, newval):
- log.setLevel(newval)
- log.extended("Switching loglevel to %s"%(str(newval)))
- settings.set("logger.level", newval)
- def writer(self, item, level):
- self.log.addItem(item)
- self.log.setCurrentRow(self.log.count()-1)
- #self.log.scrollToItem(self.log.item(self.log.count()))
- def clear(self):
- self.log.clear()
+ layout2.addWidget(self.cmbLevel)
+ layout2.addWidget(self.btnClear)
+ layout.addLayout(layout2)
+ layout.addWidget(self.log)
+
+ self.connect(self.cmbLevel, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbLevelChanged)
+
+ def onCmbLevelChanged(self, newval):
+ log.setLevel(newval)
+ log.extended("Switching loglevel to %s"%(str(newval)))
+ settings.set("logger.level", newval)
+ def writer(self, item, level):
+ self.log.addItem(item)
+ self.log.setCurrentRow(self.log.count()-1)
+ #self.log.scrollToItem(self.log.item(self.log.count()))
+ def clear(self):
+ self.log.clear()
class pluginLogger(Plugin):
- o=None
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'Logger')
- def _load(self):
- self.o=wgLogger(self, None)
- log.setWriter(self.o.writer)
- def _unload(self):
- self.o=None
- log.setWriter(log._writer)
- def getInfo(self):
- return "Shows information from the log."
-
- def _getDockWidget(self):
- return self._createDock(self.o)
+ o=None
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Logger')
+ def _load(self):
+ self.o=wgLogger(self, None)
+ log.setWriter(self.o.writer)
+ def _unload(self):
+ self.o=None
+ log.setWriter(log._writer)
+ def getInfo(self):
+ return "Shows information from the log."
+
+ def _getDockWidget(self):
+ return self._createDock(self.o)
diff --git a/plugins/Lyrics.py b/plugins/Lyrics.py
index 2e4d31c..840ec06 100644
--- a/plugins/Lyrics.py
+++ b/plugins/Lyrics.py
@@ -13,273 +13,273 @@ from clMonty import monty
from clPlugin import *
class ResetEvent(QtCore.QEvent):
- song=None
- def __init__(self, song=None):
- QtCore.QEvent.__init__(self,QtCore.QEvent.User)
- self.song=song
+ song=None
+ def __init__(self, song=None):
+ QtCore.QEvent.__init__(self,QtCore.QEvent.User)
+ self.song=song
class AddHtmlEvent(QtCore.QEvent):
- html=None
- def __init__(self,html):
- QtCore.QEvent.__init__(self,QtCore.QEvent.User)
- self.html=html
+ html=None
+ def __init__(self,html):
+ QtCore.QEvent.__init__(self,QtCore.QEvent.User)
+ self.html=html
class AddTextEvent(QtCore.QEvent):
- text=None
- def __init__(self,text):
- QtCore.QEvent.__init__(self,QtCore.QEvent.User)
- self.text=text
+ text=None
+ def __init__(self,text):
+ QtCore.QEvent.__init__(self,QtCore.QEvent.User)
+ self.text=text
LYRICS_ENGINE_DEFAULT='http://www.google.com/search?q=lyrics+"$artist"+"$title"'
LYRICS_SITES_DEFAULT=\
- 'absolutelyrics.com <div id="realText">(.*?)</div>\n'\
- 'azlyrics.com <br><br>.*?<br><br>(.*?)<br><br>\n'\
- 'oldielyrics.com song_in_top2.*?<br>(.*?)<script\n'\
- 'lyricstime.com phone-left.gif.*?<p>(.*?)</p>\n'\
- 'lyricsfire.com class="lyric">.*?Song.*?<br>(.*?)</pre>\n'\
- 'lyricsfreak.com <div.*?id="content".*?>(.*?)<blockquote\n'\
- 'artists.letssingit.com <pre>(.*?)</pre>\n'\
- 'gugalyrics.com </h1>(.*?)<a\n'\
- 'lyricsmania.com </strong> :(.*?)&#91;\n'\
- 'leoslyrics.com <font face=.*?size=-1>(.*?)</font>\n'\
- 'bluesforpeace.com <blockquote>(.*?)</blockquote>\n'
+ 'absolutelyrics.com <div id="realText">(.*?)</div>\n'\
+ 'azlyrics.com <br><br>.*?<br><br>(.*?)<br><br>\n'\
+ 'oldielyrics.com song_in_top2.*?<br>(.*?)<script\n'\
+ 'lyricstime.com phone-left.gif.*?<p>(.*?)</p>\n'\
+ 'lyricsfire.com class="lyric">.*?Song.*?<br>(.*?)</pre>\n'\
+ 'lyricsfreak.com <div.*?id="content".*?>(.*?)<blockquote\n'\
+ 'artists.letssingit.com <pre>(.*?)</pre>\n'\
+ 'gugalyrics.com </h1>(.*?)<a\n'\
+ 'lyricsmania.com </strong> :(.*?)&#91;\n'\
+ 'leoslyrics.com <font face=.*?size=-1>(.*?)</font>\n'\
+ 'bluesforpeace.com <blockquote>(.*?)</blockquote>\n'
LYRICS_DOWNLOADTO_DEFAULT='~/.lyrics/$artist/$artist - $title.txt'
LYRICS_AUTOSCROLL_DEFAULT='1'
class wgLyrics(QtGui.QWidget):
- " contains the lyrics"
- txtView=None # text-object
- curLyrics=None # current lyrics
- btnEdit=None
- btnRefetch=None
- btnSave=None
- btnSearch=None
- editMode=False
- lyFormat=None
- p=None # plugin
- def __init__(self, p, parent=None):
- QtGui.QWidget.__init__(self, parent)
- self.p=p
- self.curLyrics=""
- self.btnEdit=Button("Edit lyrics", self.onBtnEditClick)
- self.btnRefetch=Button("Refetch", self.onBtnRefetchClick)
- self.btnSave=Button("Save lyrics", self.onBtnSaveClick)
- self.btnSearch=Button("Search www", self.onBtnSearch)
-
- self.txtView=QtGui.QTextEdit(parent)
- self.txtView.setReadOnly(True)
-
- self.setMode(False)
-
- layout=QtGui.QVBoxLayout()
- hlayout=QtGui.QHBoxLayout()
- layout.addLayout(hlayout)
- hlayout.addWidget(self.btnEdit)
- hlayout.addWidget(self.btnSave)
- hlayout.addWidget(self.btnRefetch)
- hlayout.addWidget(self.btnSearch)
- layout.addWidget(self.txtView)
- self.setLayout(layout)
+ " contains the lyrics"
+ txtView=None # text-object
+ curLyrics=None # current lyrics
+ btnEdit=None
+ btnRefetch=None
+ btnSave=None
+ btnSearch=None
+ editMode=False
+ lyFormat=None
+ p=None # plugin
+ def __init__(self, p, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.p=p
+ self.curLyrics=""
+ self.btnEdit=Button("Edit lyrics", self.onBtnEditClick)
+ self.btnRefetch=Button("Refetch", self.onBtnRefetchClick)
+ self.btnSave=Button("Save lyrics", self.onBtnSaveClick)
+ self.btnSearch=Button("Search www", self.onBtnSearch)
+
+ self.txtView=QtGui.QTextEdit(parent)
+ self.txtView.setReadOnly(True)
+
+ self.setMode(False)
+
+ layout=QtGui.QVBoxLayout()
+ hlayout=QtGui.QHBoxLayout()
+ layout.addLayout(hlayout)
+ hlayout.addWidget(self.btnEdit)
+ hlayout.addWidget(self.btnSave)
+ hlayout.addWidget(self.btnRefetch)
+ hlayout.addWidget(self.btnSearch)
+ layout.addWidget(self.txtView)
+ self.setLayout(layout)
-
- def setMode(self, mode):
- self.editMode=mode
- self.btnSave.setVisible(mode)
- self.btnEdit.setVisible(not mode)
- self.txtView.setReadOnly(not mode)
-
- def onBtnSearch(self):
- SE=self.p.getSetting('engine')
- f=format.compile(SE)
- SE_url=toAscii(f(format.params(monty.getCurrentSong())))
- webbrowser.open(urllib.quote(SE_url, ":/+?="))
-
- def onBtnEditClick(self):
- self.setMode(True)
- self.txtView.setPlainText(self.curLyrics)
-
- def onBtnSaveClick(self):
- self.setMode(False)
- self.curLyrics=self.txtView.toPlainText()
- file=open(self.getLyricsFilePath(monty.getCurrentSong()), 'w')
- file.write(self.curLyrics)
- file.close()
-
- # just to test if everything's still fine
- self.fetchLyrics(monty.getCurrentSong())
-
- def onBtnRefetchClick(self):
- # force refetch
- lyFName=self.getLyricsFilePath(monty.getCurrentSong())
- try:
- os.remove(lyFName)
- except:
- pass
- self.refresh()
-
- def refresh(self):
- # if we're editing while song changes, too bad: we don't save, for the moment!
- if self.editMode:
- self.setMode(False)
-
- self.lyFormat=format.compile(self.p.getSetting('downloadto'))
- song=monty.getCurrentSong()
- try:
- song._data['file']
- except:
- self.resetTxt()
- return
-
- self.resetTxt(song)
- start_new_thread(self.fetchLyrics, (song,))
-
- def customEvent(self, event):
- if isinstance(event,ResetEvent):
- self.resetTxt(event.song)
- elif isinstance(event,AddTextEvent):
- self.txtView.insertPlainText(event.text)
- elif isinstance(event,AddHtmlEvent):
- self.txtView.insertHtml(event.html)
+
+ def setMode(self, mode):
+ self.editMode=mode
+ self.btnSave.setVisible(mode)
+ self.btnEdit.setVisible(not mode)
+ self.txtView.setReadOnly(not mode)
+
+ def onBtnSearch(self):
+ SE=self.p.getSetting('engine')
+ f=format.compile(SE)
+ SE_url=toAscii(f(format.params(monty.getCurrentSong())))
+ webbrowser.open(urllib.quote(SE_url, ":/+?="))
+
+ def onBtnEditClick(self):
+ self.setMode(True)
+ self.txtView.setPlainText(self.curLyrics)
+
+ def onBtnSaveClick(self):
+ self.setMode(False)
+ self.curLyrics=self.txtView.toPlainText()
+ file=open(self.getLyricsFilePath(monty.getCurrentSong()), 'w')
+ file.write(self.curLyrics)
+ file.close()
+
+ # just to test if everything's still fine
+ self.fetchLyrics(monty.getCurrentSong())
+
+ def onBtnRefetchClick(self):
+ # force refetch
+ lyFName=self.getLyricsFilePath(monty.getCurrentSong())
+ try:
+ os.remove(lyFName)
+ except:
+ pass
+ self.refresh()
+
+ def refresh(self):
+ # if we're editing while song changes, too bad: we don't save, for the moment!
+ if self.editMode:
+ self.setMode(False)
+
+ self.lyFormat=format.compile(self.p.getSetting('downloadto'))
+ song=monty.getCurrentSong()
+ try:
+ song._data['file']
+ except:
+ self.resetTxt()
+ return
+
+ self.resetTxt(song)
+ start_new_thread(self.fetchLyrics, (song,))
+
+ def customEvent(self, event):
+ if isinstance(event,ResetEvent):
+ self.resetTxt(event.song)
+ elif isinstance(event,AddTextEvent):
+ self.txtView.insertPlainText(event.text)
+ elif isinstance(event,AddHtmlEvent):
+ self.txtView.insertHtml(event.html)
- def getLyricsFilePath(self, song):
- params={'music_dir': mpdSettings.get('music_directory')}
- path=self.lyFormat(format.params(song, params))
- return toAscii(os.path.expanduser(path))
-
- _mutex=QtCore.QMutex()
- _fetchCnt=0
- def fetchLyrics(self, song):
- # only allow 1 instance to look lyrics!
- self._mutex.lock()
- if self._fetchCnt:
- self._mutex.unlock()
- return
- self._fetchCnt=1
- self._mutex.unlock()
+ def getLyricsFilePath(self, song):
+ params={'music_dir': mpdSettings.get('music_directory')}
+ path=self.lyFormat(format.params(song, params))
+ return toAscii(os.path.expanduser(path))
+
+ _mutex=QtCore.QMutex()
+ _fetchCnt=0
+ def fetchLyrics(self, song):
+ # only allow 1 instance to look lyrics!
+ self._mutex.lock()
+ if self._fetchCnt:
+ self._mutex.unlock()
+ return
+ self._fetchCnt=1
+ self._mutex.unlock()
- QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
-
- lyFName=self.getLyricsFilePath(song)
- self.p.debug("checking %s"%(lyFName))
- self.curLyrics=""
- # does the file exist? if yes, read that one!
- try:
- file=open(lyFName, 'r')
- self.curLyrics=file.read()
- file.close()
- QtCore.QCoreApplication.postEvent(self, AddTextEvent(self.curLyrics))
- self._fetchCnt=0
- self.p.extended("Fetch lyrics from file")
- return
- except Exception, e:
- self.p.debug("fail - %s"%(str(e)))
+ QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
+
+ lyFName=self.getLyricsFilePath(song)
+ self.p.debug("checking %s"%(lyFName))
+ self.curLyrics=""
+ # does the file exist? if yes, read that one!
+ try:
+ file=open(lyFName, 'r')
+ self.curLyrics=file.read()
+ file.close()
+ QtCore.QCoreApplication.postEvent(self, AddTextEvent(self.curLyrics))
+ self._fetchCnt=0
+ self.p.extended("Fetch lyrics from file")
+ return
+ except Exception, e:
+ self.p.debug("fail - %s"%(str(e)))
- # fetch from inet
- QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<i>Searching lyrics ...</i>'))
- self.p.extended("Fetch lyrics from internet")
-
- lines=self.p.getSetting('sites').split('\n')
- sites={}
- for line in lines:
- if line.strip():
- sites[line[0:line.find('\t')]]=line[line.find('\t'):].strip()
- # construct URL to search!
- SE=self.p.getSetting('engine')
- try:
- ret=fetch(SE, sites, song, {})
- QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
- if ret:
- self.p.extended("Success!")
- self.curLyrics=ret[0]
- # save for later use!
- if lyFName:
- # we can't save if the path isn't correct
- try:
- self.p.extended("Saving to %s"%(lyFName))
- try:
- # fails when dir exists
- os.makedirs(os.path.dirname(os.path.expanduser(lyFName)))
- except Exception, e:
- pass
- file=open(lyFName, 'w')
- file.write(self.curLyrics)
- file.close()
- except Exception, e:
- # probably a wrong path!
- self.p.normal("Failed to write lyrics %s"%(str(e)))
- else:
- self.curLyrics=""
- txt="No lyrics found :'("
- self.p.extended("Lyrics not found!")
- QtCore.QCoreApplication.postEvent(self, AddTextEvent(txt))
-
- QtCore.QCoreApplication.postEvent(self, AddTextEvent(self.curLyrics))
- if ret:
- QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<br /><br /><a href="%s">%s</a>'%(ret[1],ret[1])))
+ # fetch from inet
+ QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<i>Searching lyrics ...</i>'))
+ self.p.extended("Fetch lyrics from internet")
+
+ lines=self.p.getSetting('sites').split('\n')
+ sites={}
+ for line in lines:
+ if line.strip():
+ sites[line[0:line.find('\t')]]=line[line.find('\t'):].strip()
+ # construct URL to search!
+ SE=self.p.getSetting('engine')
+ try:
+ ret=fetch(SE, sites, song, {})
+ QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
+ if ret:
+ self.p.extended("Success!")
+ self.curLyrics=ret[0]
+ # save for later use!
+ if lyFName:
+ # we can't save if the path isn't correct
+ try:
+ self.p.extended("Saving to %s"%(lyFName))
+ try:
+ # fails when dir exists
+ os.makedirs(os.path.dirname(os.path.expanduser(lyFName)))
+ except Exception, e:
+ pass
+ file=open(lyFName, 'w')
+ file.write(self.curLyrics)
+ file.close()
+ except Exception, e:
+ # probably a wrong path!
+ self.p.normal("Failed to write lyrics %s"%(str(e)))
+ else:
+ self.curLyrics=""
+ txt="No lyrics found :'("
+ self.p.extended("Lyrics not found!")
+ QtCore.QCoreApplication.postEvent(self, AddTextEvent(txt))
+
+ QtCore.QCoreApplication.postEvent(self, AddTextEvent(self.curLyrics))
+ if ret:
+ QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<br /><br /><a href="%s">%s</a>'%(ret[1],ret[1])))
- except Exception, e:
- QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
- QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('Woops, error! Possible causes:'\
- '<br />no internet connection?'\
- '<br />site unavailable'\
- '<br />an error in the fetching regular expression'\
- '<br />(exception: %s)'%(str(e))))
- self._fetchCnt=0
+ except Exception, e:
+ QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
+ QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('Woops, error! Possible causes:'\
+ '<br />no internet connection?'\
+ '<br />site unavailable'\
+ '<br />an error in the fetching regular expression'\
+ '<br />(exception: %s)'%(str(e))))
+ self._fetchCnt=0
- def resetTxt(self, song=None):
- self.txtView.clear()
- if song:
- self.txtView.insertHtml('<b>%s</b>\n<br /><u>%s</u><br />'\
- '<br />\n\n'%(song.getTitle(), song.getArtist()))
-
- def autoScroll(self, time):
- t=self.txtView
- max=t.verticalScrollBar().maximum()
- if max<=0:
- return
- t.verticalScrollBar().setValue((max+t.height()/t.currentFont().pointSize())*time/monty.getCurrentSong()._data['time'])
-
+ def resetTxt(self, song=None):
+ self.txtView.clear()
+ if song:
+ self.txtView.insertHtml('<b>%s</b>\n<br /><u>%s</u><br />'\
+ '<br />\n\n'%(song.getTitle(), song.getArtist()))
+
+ def autoScroll(self, time):
+ t=self.txtView
+ max=t.verticalScrollBar().maximum()
+ if max<=0:
+ return
+ t.verticalScrollBar().setValue((max+t.height()/t.currentFont().pointSize())*time/monty.getCurrentSong()._data['time'])
+
class pluginLyrics(Plugin):
- o=None
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'Lyrics')
- self.addMontyListener('onSongChange', self.refresh)
- self.addMontyListener('onReady', self.refresh)
- self.addMontyListener('onDisconnect', self.onDisconnect)
- self.addMontyListener('onTimeChange', self.onTimeChange)
- def _load(self):
- self.o=wgLyrics(self, None)
- self.o.refresh()
- def _unload(self):
- self.o=None
- def getInfo(self):
- return "Show (and fetch) the lyrics of the currently playing song."
-
- def _getDockWidget(self):
- return self._createDock(self.o)
+ o=None
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Lyrics')
+ self.addMontyListener('onSongChange', self.refresh)
+ self.addMontyListener('onReady', self.refresh)
+ self.addMontyListener('onDisconnect', self.onDisconnect)
+ self.addMontyListener('onTimeChange', self.onTimeChange)
+ def _load(self):
+ self.o=wgLyrics(self, None)
+ self.o.refresh()
+ def _unload(self):
+ self.o=None
+ def getInfo(self):
+ return "Show (and fetch) the lyrics of the currently playing song."
+
+ def _getDockWidget(self):
+ return self._createDock(self.o)
- def refresh(self, params):
- self.o.refresh()
- def onDisconnect(self, params):
- self.o.resetTxt()
- def onTimeChange(self, params):
- if self.getSetting("autoScroll")!="0" and self.o.editMode==False:
- # could be done better, but this is just plain simple :)
- self.o.autoScroll(params['newTime'])
+ def refresh(self, params):
+ self.o.refresh()
+ def onDisconnect(self, params):
+ self.o.resetTxt()
+ def onTimeChange(self, params):
+ if self.getSetting("autoScroll")!="0" and self.o.editMode==False:
+ # could be done better, but this is just plain simple :)
+ self.o.autoScroll(params['newTime'])
- def _getSettings(self):
- sites=QtGui.QTextEdit()
- sites.insertPlainText(self.getSetting('sites'))
- autoscroll=QtGui.QCheckBox("Autoscroll")
- autoscroll.setCheckState(QtCore.Qt.Checked if self.getSetting("autoScroll")!=0 else QtCore.Qt.Unchecked)
- return [
- ['engine', 'Search engine', 'The URL that is used to search. $artist, $title and $album are replaced in the URL.', QtGui.QLineEdit(self.getSetting('engine'))],
- ['sites', 'Sites & regexes', 'This field contains all sites, together with the regex needed to fetch the lyrics.\nEvery line must look like this: $domain $regex-start(.*?)$regex-end\n$domain is the domain of the lyrics website, $regex-start is the regex indicating the start of the lyrics, $regex-end indicates the end. E.g. foolyrics.org <lyrics>(.*?)</lyrics>', sites],
- ['downloadto', 'Target', 'Specifies where to save lyrics fetched from internet to.\nPossible tags: music_dir, file, artist, title, album', QtGui.QLineEdit(self.getSetting('downloadto'))],
- ['autoScroll', 'Autoscroll', 'Check this box to scroll the contents automatically as the time advances', autoscroll],
- ]
- def afterSaveSettings(self):
- self.o.refresh()
+ def _getSettings(self):
+ sites=QtGui.QTextEdit()
+ sites.insertPlainText(self.getSetting('sites'))
+ autoscroll=QtGui.QCheckBox("Autoscroll")
+ autoscroll.setCheckState(QtCore.Qt.Checked if self.getSetting("autoScroll")!=0 else QtCore.Qt.Unchecked)
+ return [
+ ['engine', 'Search engine', 'The URL that is used to search. $artist, $title and $album are replaced in the URL.', QtGui.QLineEdit(self.getSetting('engine'))],
+ ['sites', 'Sites & regexes', 'This field contains all sites, together with the regex needed to fetch the lyrics.\nEvery line must look like this: $domain $regex-start(.*?)$regex-end\n$domain is the domain of the lyrics website, $regex-start is the regex indicating the start of the lyrics, $regex-end indicates the end. E.g. foolyrics.org <lyrics>(.*?)</lyrics>', sites],
+ ['downloadto', 'Target', 'Specifies where to save lyrics fetched from internet to.\nPossible tags: music_dir, file, artist, title, album', QtGui.QLineEdit(self.getSetting('downloadto'))],
+ ['autoScroll', 'Autoscroll', 'Check this box to scroll the contents automatically as the time advances', autoscroll],
+ ]
+ def afterSaveSettings(self):
+ self.o.refresh()
diff --git a/plugins/MPD.py b/plugins/MPD.py
index dd41230..c7e8c9c 100644
--- a/plugins/MPD.py
+++ b/plugins/MPD.py
@@ -8,21 +8,21 @@ MPD_HOST_DEFAULT='localhost'
MPD_PORT_DEFAULT='6600'
class pluginMPD(Plugin):
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'MPD')
- def getInfo(self):
- return "Provides an interface to the MPD settings."
-
-
- def _getSettings(self):
- return [
- ['host', 'Host', 'Host where mpd resides.', QtGui.QLineEdit(self.getSetting('host'))],
- ['port', 'Port', 'Port of mpd.', QtGui.QLineEdit(self.getSetting('port'))],
- ['music_directory', 'Music directory', 'Root directory where all music is located.', QtGui.QLineEdit(mpdSettings.get('music_directory')), mpdSettings],
- ['', 'Update database', 'Updates the database.\nUse this if you have changed the music_directory. Updating will save all entries on the MPD tab.', Button('Update db', self.onBtnUpdateDBClick)],
- ]
- def onBtnUpdateDBClick(self):
- self.saveSettings()
- monty.updateDB([mpdSettings.get('music_directory')])
- pass
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'MPD')
+ def getInfo(self):
+ return "Provides an interface to the MPD settings."
+
+
+ def _getSettings(self):
+ return [
+ ['host', 'Host', 'Host where mpd resides.', QtGui.QLineEdit(self.getSetting('host'))],
+ ['port', 'Port', 'Port of mpd.', QtGui.QLineEdit(self.getSetting('port'))],
+ ['music_directory', 'Music directory', 'Root directory where all music is located.', QtGui.QLineEdit(mpdSettings.get('music_directory')), mpdSettings],
+ ['', 'Update database', 'Updates the database.\nUse this if you have changed the music_directory. Updating will save all entries on the MPD tab.', Button('Update db', self.onBtnUpdateDBClick)],
+ ]
+ def onBtnUpdateDBClick(self):
+ self.saveSettings()
+ monty.updateDB([mpdSettings.get('music_directory')])
+ pass
diff --git a/plugins/Notify.py b/plugins/Notify.py
index 14e35af..4c6de63 100644
--- a/plugins/Notify.py
+++ b/plugins/Notify.py
@@ -12,156 +12,156 @@ NOTIFY_SONGFORMAT_DEFAULT='$if($artist,$artist)$if($album, - [$album #$track])\n
NOTIFY_TIMER_DEFAULT=3
class winNotify(QtGui.QWidget):
- _timerID=None
- resizeWindow=True
- winMain=None
- p=None
+ _timerID=None
+ resizeWindow=True
+ winMain=None
+ p=None
- # data used for showing off
- timer=None
- msg=None
- song=None
- xtra_tags=None
+ # data used for showing off
+ timer=None
+ msg=None
+ song=None
+ xtra_tags=None
- def __init__(self, p, winMain, parent=None):
- QtGui.QWidget.__init__(self, parent)
- self.p=p
- self.winMain=winMain
-
- self.setWindowFlags(QtCore.Qt.ToolTip)
- self.setWindowOpacity(0.7)
-
- font=QtGui.QFont()
- font.setPixelSize(30)
- font.setFamily('Comic Sans Ms')
- self.setFont(font)
-
- def mousePressEvent(self, event):
- self.hide()
+ def __init__(self, p, winMain, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.p=p
+ self.winMain=winMain
+
+ self.setWindowFlags(QtCore.Qt.ToolTip)
+ self.setWindowOpacity(0.7)
+
+ font=QtGui.QFont()
+ font.setPixelSize(30)
+ font.setFamily('Comic Sans Ms')
+ self.setFont(font)
+
+ def mousePressEvent(self, event):
+ self.hide()
- def show(self, msg, song=None, xtra_tags={}, time=3):
- if not self.isVisible():
- self.setVisible(True)
- self.resizeWindow=True
- self.msg=format.compile(msg)
- self.song=song
- self.xtra_tags=xtra_tags
- if self._timerID:
- self.killTimer(self._timerID)
- self._timerID=self.startTimer(500)
- try:
- self.timer=int(time)*2
- except:
- self.timer=3
- self.raise_()
- self.timerEvent(None)
-
- def hide(self):
- if self._timerID:
- self.killTimer(self._timerID)
- self._timerID=None
- self.setHidden(True)
+ def show(self, msg, song=None, xtra_tags={}, time=3):
+ if not self.isVisible():
+ self.setVisible(True)
+ self.resizeWindow=True
+ self.msg=format.compile(msg)
+ self.song=song
+ self.xtra_tags=xtra_tags
+ if self._timerID:
+ self.killTimer(self._timerID)
+ self._timerID=self.startTimer(500)
+ try:
+ self.timer=int(time)*2
+ except:
+ self.timer=3
+ self.raise_()
+ self.timerEvent(None)
+
+ def hide(self):
+ if self._timerID:
+ self.killTimer(self._timerID)
+ self._timerID=None
+ self.setHidden(True)
-
- def centerH(self):
- screen = QtGui.QDesktopWidget().screenGeometry()
- size = self.geometry()
- self.move((screen.width()-size.width())/2, 100)
- def paintEvent(self, event):
- p=QtGui.QPainter(self)
- margin=3 # margin in pixels
- spacing=10 # space between album cover and text
-
- # determine the Rect our message must fit in
- txt=self.msg(format.params(self.song, self.xtra_tags))
- rect=p.boundingRect(0,0,1,1, QtCore.Qt.AlignHCenter, txt)
-
- # check if 1/ albumcover plugin is loaded, and 2/ there is an
- # album cover
- width=0
- try:
- cover=plugins.getPlugin('albumcover')
- img=cover.getWidget().getIMG()
- if img:
- width=128
- else:
- spacing=0
- except:
- img=None
- pass
-
- # do we have to resize?
- if self.resizeWindow:
- self.resizeWindow=False
- self.resize(rect.width()+width+margin*3+spacing, max(width,rect.height())+margin*2)
- self.centerH()
-
- # fill up with a nice color :)
- p.fillRect(QtCore.QRect(0,0,self.width(),self.height()), QtGui.QBrush(QtGui.QColor(230,230,255)))
-
- # draw album cover if necessary
- if img:
- rImg=QtCore.QRectF(margin,margin,width,width)
- p.drawImage(rImg, img)
-
- # Pen In Black ...
- p.setPen(QtCore.Qt.black)
- rect=p.boundingRect(width+margin+spacing,margin,
- rect.width(),self.height(), QtCore.Qt.AlignHCenter|QtCore.Qt.AlignVCenter, txt)
- p.drawText(rect, QtCore.Qt.AlignHCenter, txt)
-
- def timerEvent(self, event):
- self.timer-=1
- if self.timer<=0:
- self.hide()
- self.update()
-
+
+ def centerH(self):
+ screen = QtGui.QDesktopWidget().screenGeometry()
+ size = self.geometry()
+ self.move((screen.width()-size.width())/2, 100)
+ def paintEvent(self, event):
+ p=QtGui.QPainter(self)
+ margin=3 # margin in pixels
+ spacing=10 # space between album cover and text
+
+ # determine the Rect our message must fit in
+ txt=self.msg(format.params(self.song, self.xtra_tags))
+ rect=p.boundingRect(0,0,1,1, QtCore.Qt.AlignHCenter, txt)
+
+ # check if 1/ albumcover plugin is loaded, and 2/ there is an
+ # album cover
+ width=0
+ try:
+ cover=plugins.getPlugin('albumcover')
+ img=cover.getWidget().getIMG()
+ if img:
+ width=128
+ else:
+ spacing=0
+ except:
+ img=None
+ pass
+
+ # do we have to resize?
+ if self.resizeWindow:
+ self.resizeWindow=False
+ self.resize(rect.width()+width+margin*3+spacing, max(width,rect.height())+margin*2)
+ self.centerH()
+
+ # fill up with a nice color :)
+ p.fillRect(QtCore.QRect(0,0,self.width(),self.height()), QtGui.QBrush(QtGui.QColor(230,230,255)))
+
+ # draw album cover if necessary
+ if img:
+ rImg=QtCore.QRectF(margin,margin,width,width)
+ p.drawImage(rImg, img)
+
+ # Pen In Black ...
+ p.setPen(QtCore.Qt.black)
+ rect=p.boundingRect(width+margin+spacing,margin,
+ rect.width(),self.height(), QtCore.Qt.AlignHCenter|QtCore.Qt.AlignVCenter, txt)
+ p.drawText(rect, QtCore.Qt.AlignHCenter, txt)
+
+ def timerEvent(self, event):
+ self.timer-=1
+ if self.timer<=0:
+ self.hide()
+ self.update()
+
class pluginNotify(Plugin):
- o=None
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'Notify')
- self.addMontyListener('onSongChange', self.onSongChange)
- self.addMontyListener('onReady', self.onReady)
- self.addMontyListener('onDisconnect', self.onDisconnect)
- self.addMontyListener('onStateChange', self.onStateChange)
- self.addMontyListener('onVolumeChange', self.onVolumeChange)
-
- def _load(self):
- self.o=winNotify(self, self.winMain)
- def _unload(self):
- self.o=None
- def getInfo(self):
- return "Show interesting events in a popup window."
+ o=None
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Notify')
+ self.addMontyListener('onSongChange', self.onSongChange)
+ self.addMontyListener('onReady', self.onReady)
+ self.addMontyListener('onDisconnect', self.onDisconnect)
+ self.addMontyListener('onStateChange', self.onStateChange)
+ self.addMontyListener('onVolumeChange', self.onVolumeChange)
+
+ def _load(self):
+ self.o=winNotify(self, self.winMain)
+ def _unload(self):
+ self.o=None
+ def getInfo(self):
+ return "Show interesting events in a popup window."
- def onSongChange(self, params):
- self.o.show(self.getSetting('songformat').replace("\n", "\\n"), monty.getCurrentSong()
- , time=self.getSetting('timer'))
+ def onSongChange(self, params):
+ self.o.show(self.getSetting('songformat').replace("\n", "\\n"), monty.getCurrentSong()
+ , time=self.getSetting('timer'))
- def onReady(self, params):
- self.o.show('montypc loaded!', monty.getCurrentSong(), time=self.getSetting('timer'))
-
- def onDisconnect(self, params):
- self.o.show('Disconnected!', time=self.getSetting('timer'))
-
- def onStateChange(self, params):
- self.o.show(params['newState'], monty.getCurrentSong(), time=self.getSetting('timer'))
-
- def onVolumeChange(self, params):
- self.o.show('Volume: %i%%'%(params['newVolume']), monty.getCurrentSong(), time=self.getSetting('timer'))
-
- def _getSettings(self):
- txt=QtGui.QTextEdit()
- txt.insertPlainText(self.getSetting('songformat'))
- return [
- ['songformat', 'Song format', 'How to format the current playing song.', txt],
- ['timer', 'Show seconds', 'How many seconds does the notification have to be shown.', QtGui.QLineEdit(str(self.getSetting('timer')))],
- ]
- def afterSaveSettings(self):
- try:
- int(self.getSetting('timer'))
- except:
- self.getSetting('timer')
- self.getSettingWidget('notify.timer').setText(str(NOTIFY_DEFAULT_TIMER))
- self.onSongChange(None)
+ def onReady(self, params):
+ self.o.show('montypc loaded!', monty.getCurrentSong(), time=self.getSetting('timer'))
+
+ def onDisconnect(self, params):
+ self.o.show('Disconnected!', time=self.getSetting('timer'))
+
+ def onStateChange(self, params):
+ self.o.show(params['newState'], monty.getCurrentSong(), time=self.getSetting('timer'))
+
+ def onVolumeChange(self, params):
+ self.o.show('Volume: %i%%'%(params['newVolume']), monty.getCurrentSong(), time=self.getSetting('timer'))
+
+ def _getSettings(self):
+ txt=QtGui.QTextEdit()
+ txt.insertPlainText(self.getSetting('songformat'))
+ return [
+ ['songformat', 'Song format', 'How to format the current playing song.', txt],
+ ['timer', 'Show seconds', 'How many seconds does the notification have to be shown.', QtGui.QLineEdit(str(self.getSetting('timer')))],
+ ]
+ def afterSaveSettings(self):
+ try:
+ int(self.getSetting('timer'))
+ except:
+ self.getSetting('timer')
+ self.getSettingWidget('notify.timer').setText(str(NOTIFY_DEFAULT_TIMER))
+ self.onSongChange(None)
diff --git a/plugins/PlayControl.py b/plugins/PlayControl.py
index 586527c..be7006b 100644
--- a/plugins/PlayControl.py
+++ b/plugins/PlayControl.py
@@ -12,15 +12,15 @@ import plugins
# Note that REPEAT precedes RANDOM. E.g. if repeat
# is ALBUM, and random is SONG, then it'll take a random song
# from the current album ...
-PC_RANDOM_NO=0 # no randomness
-PC_RANDOM_SONG=1 # choose a song random from the playlist
-PC_RANDOM_ALBUM=2 # choose a random album, and play that fully
-PC_RANDOM_QUEUE=3 # choose next song from the queue
+PC_RANDOM_NO=0 # no randomness
+PC_RANDOM_SONG=1 # choose a song random from the playlist
+PC_RANDOM_ALBUM=2 # choose a random album, and play that fully
+PC_RANDOM_QUEUE=3 # choose next song from the queue
-PC_REPEAT_NO=0 # no repeat
-PC_REPEAT_SONG=1 # repeat current song
-PC_REPEAT_ALBUM=2 # repeat current album
-PC_REPEAT_PLAYLIST=3 # repeat playlist
+PC_REPEAT_NO=0 # no repeat
+PC_REPEAT_SONG=1 # repeat current song
+PC_REPEAT_ALBUM=2 # repeat current album
+PC_REPEAT_PLAYLIST=3 # repeat playlist
PLAYCONTROL_SHUFFLE_DEFAULT=PC_RANDOM_ALBUM
PLAYCONTROL_OLDSHUFFLE_DEFAULT=PLAYCONTROL_SHUFFLE_DEFAULT
@@ -29,355 +29,355 @@ PLAYCONTROL_REPEAT_DEFAULT=PC_REPEAT_PLAYLIST
class wgPlayControl(QtGui.QWidget):
- """Displays controls for interacting with playing, like play, volume ..."""
- " control buttons"
- btnPlayPause=None
- btnStop=None
- btnPrevious=None
- btnNext=None
- " button to jump to current song"
- btnJmpCurrent=None
- " slider for current time"
- slrTime=None
- " slider for volume"
- slrVolume=None
- " indicator for volume"
- svgVolume=None
- " all objects in this widget"
- objects=None
+ """Displays controls for interacting with playing, like play, volume ..."""
+ " control buttons"
+ btnPlayPause=None
+ btnStop=None
+ btnPrevious=None
+ btnNext=None
+ " button to jump to current song"
+ btnJmpCurrent=None
+ " slider for current time"
+ slrTime=None
+ " slider for volume"
+ slrVolume=None
+ " indicator for volume"
+ svgVolume=None
+ " all objects in this widget"
+ objects=None
- cmbRepeat=None
- cmbShuffle=None
- p=None
-
- " contains the songs of the album the current song is playing. None, if the album is not set"
- curAlbumSongs=None
-
- " queued songs: int*"
- queuedSongs=[]
- # what mode where we in before the queue started?
- beforeQueuedMode=None
+ cmbRepeat=None
+ cmbShuffle=None
+ p=None
+
+ " contains the songs of the album the current song is playing. None, if the album is not set"
+ curAlbumSongs=None
+
+ " queued songs: int*"
+ queuedSongs=[]
+ # what mode where we in before the queue started?
+ beforeQueuedMode=None
- def __init__(self, p, parent=None):
- QtGui.QWidget.__init__(self, parent)
- self.p=p
+ def __init__(self, p, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.p=p
- class wgSvgSwitcher(QtSvg.QSvgWidget):
- """Widget showing an svg-image, which, when clicked, will (un)hide an element."""
- # the element we wish to hide/show
- scroller=None
- def __init__(self,scroller,parent=None):
- QtSvg.QSvgWidget.__init__(self,parent)
- self.scroller=scroller
- def mousePressEvent(self,event):
- self.scroller.setVisible(not self.scroller.isVisible())
- def wheelEvent(self, event):
- event.accept()
- numDegrees=event.delta() / 8
- numSteps=5*numDegrees/15
- self.scroller.setValue(self.scroller.value()+numSteps)
+ class wgSvgSwitcher(QtSvg.QSvgWidget):
+ """Widget showing an svg-image, which, when clicked, will (un)hide an element."""
+ # the element we wish to hide/show
+ scroller=None
+ def __init__(self,scroller,parent=None):
+ QtSvg.QSvgWidget.__init__(self,parent)
+ self.scroller=scroller
+ def mousePressEvent(self,event):
+ self.scroller.setVisible(not self.scroller.isVisible())
+ def wheelEvent(self, event):
+ event.accept()
+ numDegrees=event.delta() / 8
+ numSteps=5*numDegrees/15
+ self.scroller.setValue(self.scroller.value()+numSteps)
- self.slrTime=QtGui.QSlider(QtCore.Qt.Horizontal, self)
- self.slrTime.setMinimumWidth(100)
- self.slrVolume=QtGui.QSlider(QtCore.Qt.Vertical, self)
- self.slrVolume.setMaximum(100)
- self.slrVolume.setMinimumWidth(100)
- self.slrVolume.setMaximumWidth(350)
- # set to some value that'll never be chosen, that way onChange will be called automatically :)
- self.slrVolume.setValue(3.141595)
- self.slrVolume.setVisible(False)
- self.svgVolume=wgSvgSwitcher(self.slrVolume)
- self.svgVolume.setMaximumSize(64,64)
- self.svgVolume.setMinimumSize(64,64)
- self.btnPlayPause=Button("play", self.onBtnPlayPauseClick, 'gfx/media-playback-start.svg', True)
- self.btnStop=Button("stop", self.onBtnStopClick, 'gfx/media-playback-stop.svg', True)
- self.btnPrevious=Button("prev", self.onBtnPreviousClick, 'gfx/media-skip-backward.svg', True)
- self.btnNext=Button("next", self.onBtnNextClick, 'gfx/media-skip-forward.svg', True)
- self.btnJmpCurrent=Button("Current", self.onBtnJmpCurrentClick)
+ self.slrTime=QtGui.QSlider(QtCore.Qt.Horizontal, self)
+ self.slrTime.setMinimumWidth(100)
+ self.slrVolume=QtGui.QSlider(QtCore.Qt.Vertical, self)
+ self.slrVolume.setMaximum(100)
+ self.slrVolume.setMinimumWidth(100)
+ self.slrVolume.setMaximumWidth(350)
+ # set to some value that'll never be chosen, that way onChange will be called automatically :)
+ self.slrVolume.setValue(3.141595)
+ self.slrVolume.setVisible(False)
+ self.svgVolume=wgSvgSwitcher(self.slrVolume)
+ self.svgVolume.setMaximumSize(64,64)
+ self.svgVolume.setMinimumSize(64,64)
+ self.btnPlayPause=Button("play", self.onBtnPlayPauseClick, 'gfx/media-playback-start.svg', True)
+ self.btnStop=Button("stop", self.onBtnStopClick, 'gfx/media-playback-stop.svg', True)
+ self.btnPrevious=Button("prev", self.onBtnPreviousClick, 'gfx/media-skip-backward.svg', True)
+ self.btnNext=Button("next", self.onBtnNextClick, 'gfx/media-skip-forward.svg', True)
+ self.btnJmpCurrent=Button("Current", self.onBtnJmpCurrentClick)
- self.cmbShuffle=QtGui.QComboBox(self)
- self.cmbShuffle.addItem('Don\'t play dices')
- self.cmbShuffle.addItem('Random song')
- self.cmbShuffle.addItem('Random album')
- self.cmbShuffle.addItem('Queue')
- self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('shuffle')))
-
- self.cmbRepeat=QtGui.QComboBox(self)
- self.cmbRepeat.addItem('No repeat')
- self.cmbRepeat.addItem('Repeat current song')
- self.cmbRepeat.addItem('Repeat album')
- self.cmbRepeat.addItem('Playlist')
- self.cmbRepeat.setCurrentIndex(int(self.p.getSetting('repeat')))
+ self.cmbShuffle=QtGui.QComboBox(self)
+ self.cmbShuffle.addItem('Don\'t play dices')
+ self.cmbShuffle.addItem('Random song')
+ self.cmbShuffle.addItem('Random album')
+ self.cmbShuffle.addItem('Queue')
+ self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('shuffle')))
+
+ self.cmbRepeat=QtGui.QComboBox(self)
+ self.cmbRepeat.addItem('No repeat')
+ self.cmbRepeat.addItem('Repeat current song')
+ self.cmbRepeat.addItem('Repeat album')
+ self.cmbRepeat.addItem('Playlist')
+ self.cmbRepeat.setCurrentIndex(int(self.p.getSetting('repeat')))
- self.objects=[self.slrVolume, self.slrTime,
- self.btnStop, self.btnNext, self.btnPrevious]
+ self.objects=[self.slrVolume, self.slrTime,
+ self.btnStop, self.btnNext, self.btnPrevious]
- layout=QtGui.QHBoxLayout(parent)
- layout2=QtGui.QHBoxLayout(parent)
- layoutWidget=QtGui.QVBoxLayout(parent)
- layoutWidget.addLayout(layout)
- layoutWidget.addLayout(layout2)
- self.setLayout(layoutWidget)
-
- layout.addWidget(self.btnPrevious)
- layout.addWidget(self.btnPlayPause)
- layout.addWidget(self.btnStop)
- layout.addWidget(self.btnNext)
- layout.addWidget(self.slrTime)
- layout.addWidget(self.slrVolume)
- layout.addWidget(self.svgVolume)
+ layout=QtGui.QHBoxLayout(parent)
+ layout2=QtGui.QHBoxLayout(parent)
+ layoutWidget=QtGui.QVBoxLayout(parent)
+ layoutWidget.addLayout(layout)
+ layoutWidget.addLayout(layout2)
+ self.setLayout(layoutWidget)
+
+ layout.addWidget(self.btnPrevious)
+ layout.addWidget(self.btnPlayPause)
+ layout.addWidget(self.btnStop)
+ layout.addWidget(self.btnNext)
+ layout.addWidget(self.slrTime)
+ layout.addWidget(self.slrVolume)
+ layout.addWidget(self.svgVolume)
- layout2.addWidget(self.cmbRepeat)
- layout2.addWidget(self.cmbShuffle)
- layout2.addWidget(self.btnJmpCurrent)
-
- # queue gets loaded in _load of pluginPlayControl
- self.queuedSongs=[]
- self._onQueueUpdate()
+ layout2.addWidget(self.cmbRepeat)
+ layout2.addWidget(self.cmbShuffle)
+ layout2.addWidget(self.btnJmpCurrent)
+
+ # queue gets loaded in _load of pluginPlayControl
+ self.queuedSongs=[]
+ self._onQueueUpdate()
- self.connect(self.slrVolume, QtCore.SIGNAL('valueChanged(int)'),self.onVolumeSliderChange)
- self.connect(self.slrTime, QtCore.SIGNAL('sliderReleased()'),self.onTimeSliderChange)
-
- self.connect(self.cmbRepeat, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbRepeatChanged)
- self.connect(self.cmbShuffle, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbShuffleChanged)
-
- def addSongsToQueue(self, songs):
- self.queuedSongs.extend(songs)
- self._onQueueUpdate()
- if self.cmbShuffle.currentIndex()!=PC_RANDOM_QUEUE:
- self.cmbShuffle.setCurrentIndex(PC_RANDOM_QUEUE)
-
- def _onQueueUpdate(self):
- """This method gets called whenever the queue is updated"""
- self.cmbShuffle.setItemText(PC_RANDOM_QUEUE, "Queue (%i)"%(len(self.queuedSongs)))
-
- def onBtnJmpCurrentClick(self):
- plugins.getPlugin("Playlist").getPlaylist().ensureVisible(monty.getCurrentSong().getID())
- def onStateChange(self, params):
- newState=monty.getStatus()['state']
+ self.connect(self.slrVolume, QtCore.SIGNAL('valueChanged(int)'),self.onVolumeSliderChange)
+ self.connect(self.slrTime, QtCore.SIGNAL('sliderReleased()'),self.onTimeSliderChange)
+
+ self.connect(self.cmbRepeat, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbRepeatChanged)
+ self.connect(self.cmbShuffle, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbShuffleChanged)
+
+ def addSongsToQueue(self, songs):
+ self.queuedSongs.extend(songs)
+ self._onQueueUpdate()
+ if self.cmbShuffle.currentIndex()!=PC_RANDOM_QUEUE:
+ self.cmbShuffle.setCurrentIndex(PC_RANDOM_QUEUE)
+
+ def _onQueueUpdate(self):
+ """This method gets called whenever the queue is updated"""
+ self.cmbShuffle.setItemText(PC_RANDOM_QUEUE, "Queue (%i)"%(len(self.queuedSongs)))
+
+ def onBtnJmpCurrentClick(self):
+ plugins.getPlugin("Playlist").getPlaylist().ensureVisible(monty.getCurrentSong().getID())
+ def onStateChange(self, params):
+ newState=monty.getStatus()['state']
- map(lambda o: o.setEnabled(newState!='stop'), self.objects)
+ map(lambda o: o.setEnabled(newState!='stop'), self.objects)
- if newState=='play':
- self.btnPlayPause.changeIcon('gfx/media-playback-pause.svg')
- self.btnPlayPause.setText('pauze')
- elif newState=='pause' or newState=='stop':
- self.btnPlayPause.changeIcon('gfx/media-playback-start.svg')
- self.btnPlayPause.setText('play')
- def onVolumeChange(self, params):
- self.slrVolume.setValue(params['newVolume'])
- def onDisconnect(self, params):
- map(lambda o: o.setEnabled(False), self.objects)
- def onTimeChange(self, params):
- if not self.slrTime.isSliderDown():
- self.slrTime.setValue(params['newTime'])
- def onSongChange(self, params):
- try:
- self.slrTime.setMaximum(monty.getStatus()['length'])
- self.slrTime.setEnabled(True)
- except:
- pass
- # look in another thread for the songs in the current album!
- params=()
- start_new_thread(self.findAlbumSongs, params)
+ if newState=='play':
+ self.btnPlayPause.changeIcon('gfx/media-playback-pause.svg')
+ self.btnPlayPause.setText('pauze')
+ elif newState=='pause' or newState=='stop':
+ self.btnPlayPause.changeIcon('gfx/media-playback-start.svg')
+ self.btnPlayPause.setText('play')
+ def onVolumeChange(self, params):
+ self.slrVolume.setValue(params['newVolume'])
+ def onDisconnect(self, params):
+ map(lambda o: o.setEnabled(False), self.objects)
+ def onTimeChange(self, params):
+ if not self.slrTime.isSliderDown():
+ self.slrTime.setValue(params['newTime'])
+ def onSongChange(self, params):
+ try:
+ self.slrTime.setMaximum(monty.getStatus()['length'])
+ self.slrTime.setEnabled(True)
+ except:
+ pass
+ # look in another thread for the songs in the current album!
+ params=()
+ start_new_thread(self.findAlbumSongs, params)
- def beforeSongChange(self, params):
- nextID=None
- song=monty.getCurrentSong()
- # decide here what next song to play!
- repeat=self.cmbRepeat.currentIndex()
- random=self.cmbShuffle.currentIndex()
- # is the current song the last of the album?
- try:
- eofAlbum=int(song.getTrack())==int(self.curAlbumSongs[-1].getTrack())
- except:
- eofAlbum=False
- if repeat==PC_REPEAT_NO:
- # no repeat, nothing to see here! Pass on!
- pass
- elif repeat==PC_REPEAT_SONG:
- # we must repeat the previous song!
- nextID=params['curSongID']
- elif repeat==PC_REPEAT_ALBUM:
- # check if we are at the last track, if it is, we must start a new!
- if eofAlbum:
- nextID=self.curAlbumSongs[0].getID()
- elif repeat==PC_REPEAT_PLAYLIST:
- # repeating the playlist is handled by monty itself;
- # it is set in onCmbRepeatChanged.
- pass
-
- if repeat!=PC_REPEAT_SONG:
- if random==PC_RANDOM_QUEUE:
- # we must check here for the queue, because, if the queue is empty, we must
- # choose the next one according the method!
- # pick the next song from the queue. Simple as that :)
- if len(self.queuedSongs):
- # pick the front
- nextID=self.queuedSongs.pop(0)
- self._onQueueUpdate()
- else:
- # no songs anymore, so we must restore the old mode!
- # We should never arrive here though ...
- self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('oldshuffle')))
-
- if random==PC_RANDOM_NO:
- # just follow our leader Monty.
- pass
- elif random==PC_RANDOM_SONG:
- # pick a random song! This depends on what repeat-mode we're in.
- if repeat==PC_REPEAT_NO or repeat==PC_REPEAT_PLAYLIST:
- # we don't repeat anything, so we can just let monty pick the
- # next random one!
- pass
- elif repeat==PC_REPEAT_ALBUM and self.curAlbumSongs:
- # pick random song from current album
- nextID=self.curAlbumSongs[randint(0,len(self.curAlbumSongs)-1)].getID()
- elif random==PC_RANDOM_ALBUM:
- # pick a random album! This means, we pick the first song of a random
- # album.
- if eofAlbum and (repeat==PC_REPEAT_PLAYLIST or repeat==PC_REPEAT_NO):
- # all first songs of an album
- albums=filter(lambda s: s.getAlbum() and s.getTrack()==1, monty.listPlaylist())
- nextID=albums[randint(0,len(albums)-1)].getID()
- else:
- # we're not at end of album, so we fetch the next id
- # We must do this, because albums are not necesseraly in the same order
- for i in xrange(len(self.curAlbumSongs)):
- if self.curAlbumSongs[i].getID()==song.getID():
- nextID=self.curAlbumSongs[i+1].getID()
- break
-
- if random==PC_RANDOM_QUEUE:
- # now here reset the mode to the previous, if needed
- if len(self.queuedSongs)==0:
- self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('oldshuffle')))
+ def beforeSongChange(self, params):
+ nextID=None
+ song=monty.getCurrentSong()
+ # decide here what next song to play!
+ repeat=self.cmbRepeat.currentIndex()
+ random=self.cmbShuffle.currentIndex()
+ # is the current song the last of the album?
+ try:
+ eofAlbum=int(song.getTrack())==int(self.curAlbumSongs[-1].getTrack())
+ except:
+ eofAlbum=False
+ if repeat==PC_REPEAT_NO:
+ # no repeat, nothing to see here! Pass on!
+ pass
+ elif repeat==PC_REPEAT_SONG:
+ # we must repeat the previous song!
+ nextID=params['curSongID']
+ elif repeat==PC_REPEAT_ALBUM:
+ # check if we are at the last track, if it is, we must start a new!
+ if eofAlbum:
+ nextID=self.curAlbumSongs[0].getID()
+ elif repeat==PC_REPEAT_PLAYLIST:
+ # repeating the playlist is handled by monty itself;
+ # it is set in onCmbRepeatChanged.
+ pass
+
+ if repeat!=PC_REPEAT_SONG:
+ if random==PC_RANDOM_QUEUE:
+ # we must check here for the queue, because, if the queue is empty, we must
+ # choose the next one according the method!
+ # pick the next song from the queue. Simple as that :)
+ if len(self.queuedSongs):
+ # pick the front
+ nextID=self.queuedSongs.pop(0)
+ self._onQueueUpdate()
+ else:
+ # no songs anymore, so we must restore the old mode!
+ # We should never arrive here though ...
+ self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('oldshuffle')))
+
+ if random==PC_RANDOM_NO:
+ # just follow our leader Monty.
+ pass
+ elif random==PC_RANDOM_SONG:
+ # pick a random song! This depends on what repeat-mode we're in.
+ if repeat==PC_REPEAT_NO or repeat==PC_REPEAT_PLAYLIST:
+ # we don't repeat anything, so we can just let monty pick the
+ # next random one!
+ pass
+ elif repeat==PC_REPEAT_ALBUM and self.curAlbumSongs:
+ # pick random song from current album
+ nextID=self.curAlbumSongs[randint(0,len(self.curAlbumSongs)-1)].getID()
+ elif random==PC_RANDOM_ALBUM:
+ # pick a random album! This means, we pick the first song of a random
+ # album.
+ if eofAlbum and (repeat==PC_REPEAT_PLAYLIST or repeat==PC_REPEAT_NO):
+ # all first songs of an album
+ albums=filter(lambda s: s.getAlbum() and s.getTrack()==1, monty.listPlaylist())
+ nextID=albums[randint(0,len(albums)-1)].getID()
+ else:
+ # we're not at end of album, so we fetch the next id
+ # We must do this, because albums are not necesseraly in the same order
+ for i in xrange(len(self.curAlbumSongs)):
+ if self.curAlbumSongs[i].getID()==song.getID():
+ nextID=self.curAlbumSongs[i+1].getID()
+ break
+
+ if random==PC_RANDOM_QUEUE:
+ # now here reset the mode to the previous, if needed
+ if len(self.queuedSongs)==0:
+ self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('oldshuffle')))
- if nextID!=None:
- monty.play(nextID)
-
- def getCurAlbumSongs(self):
- return self.curAlbumSongs
- def findAlbumSongs(self):
- """This method looks for the songs in the album of current playing song."""
- song=monty.getCurrentSong()
- if self.curAlbumSongs and clSong.isSameAlbum(song, self.curAlbumSongs[0]):
- return
- self.curAlbumSongs=None
- if not song or not song.getAlbum():
- return
- self.curAlbumSongs=filter(lambda s: clSong.isSameAlbum(s, song), monty.listPlaylist())
- self.curAlbumSongs=sorted(self.curAlbumSongs, lambda l,r: numeric_compare(l.getTrack(), r.getTrack()))
+ if nextID!=None:
+ monty.play(nextID)
+
+ def getCurAlbumSongs(self):
+ return self.curAlbumSongs
+ def findAlbumSongs(self):
+ """This method looks for the songs in the album of current playing song."""
+ song=monty.getCurrentSong()
+ if self.curAlbumSongs and clSong.isSameAlbum(song, self.curAlbumSongs[0]):
+ return
+ self.curAlbumSongs=None
+ if not song or not song.getAlbum():
+ return
+ self.curAlbumSongs=filter(lambda s: clSong.isSameAlbum(s, song), monty.listPlaylist())
+ self.curAlbumSongs=sorted(self.curAlbumSongs, lambda l,r: numeric_compare(l.getTrack(), r.getTrack()))
- def onBtnPlayPauseClick(self):
- status=monty.getStatus()
- if status['state']=='play':
- monty.pause()
- self.p.extended("Toggling playback")
- elif status['state']=='stop':
- monty.play(None)
- self.p.extended("Pausing playback")
- else:
- monty.resume()
- def onBtnStopClick(self):
- monty.stop()
- self.p.extended("Stopping playback")
- def onBtnPreviousClick(self):
- monty.previous()
- self.p.extended("Playing previous")
- def onBtnNextClick(self):
- monty.next()
- self.p.extended("Playing next")
- def onTimeSliderChange(self):
- monty.seek(self.slrTime.value())
- def onVolumeSliderChange(self):
- v=self.slrVolume.value()
- monty.setVolume(v)
- if v<=1:
- mode='mute'
- else:
- mode=('0', 'min', 'med', 'max')[int(3*v/100)]
- self.svgVolume.load('gfx/stock_volume-%s.svg'%(mode))
-
- def onCmbRepeatChanged(self, newval):
- self.p.setSetting('repeat', newval)
- if newval==PC_REPEAT_PLAYLIST:
- monty.repeat(1)
- else:
- monty.repeat(0)
- def onCmbShuffleChanged(self, newval):
- if newval==PC_RANDOM_QUEUE:
- # must do some extra's if moving to queued-mode
- if len(self.queuedSongs):
- self.p.setSetting('oldshuffle', self.p.getSetting('shuffle'))
- else:
- self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('shuffle')))
- return
- else:
- # clear the queued songs when switching
- self.queuedSongs=[]
- self._onQueueUpdate()
+ def onBtnPlayPauseClick(self):
+ status=monty.getStatus()
+ if status['state']=='play':
+ monty.pause()
+ self.p.extended("Toggling playback")
+ elif status['state']=='stop':
+ monty.play(None)
+ self.p.extended("Pausing playback")
+ else:
+ monty.resume()
+ def onBtnStopClick(self):
+ monty.stop()
+ self.p.extended("Stopping playback")
+ def onBtnPreviousClick(self):
+ monty.previous()
+ self.p.extended("Playing previous")
+ def onBtnNextClick(self):
+ monty.next()
+ self.p.extended("Playing next")
+ def onTimeSliderChange(self):
+ monty.seek(self.slrTime.value())
+ def onVolumeSliderChange(self):
+ v=self.slrVolume.value()
+ monty.setVolume(v)
+ if v<=1:
+ mode='mute'
+ else:
+ mode=('0', 'min', 'med', 'max')[int(3*v/100)]
+ self.svgVolume.load('gfx/stock_volume-%s.svg'%(mode))
+
+ def onCmbRepeatChanged(self, newval):
+ self.p.setSetting('repeat', newval)
+ if newval==PC_REPEAT_PLAYLIST:
+ monty.repeat(1)
+ else:
+ monty.repeat(0)
+ def onCmbShuffleChanged(self, newval):
+ if newval==PC_RANDOM_QUEUE:
+ # must do some extra's if moving to queued-mode
+ if len(self.queuedSongs):
+ self.p.setSetting('oldshuffle', self.p.getSetting('shuffle'))
+ else:
+ self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('shuffle')))
+ return
+ else:
+ # clear the queued songs when switching
+ self.queuedSongs=[]
+ self._onQueueUpdate()
- self.p.setSetting('shuffle', newval)
- if newval==PC_RANDOM_SONG:
- monty.random(1)
- else:
- monty.random(0)
+ self.p.setSetting('shuffle', newval)
+ if newval==PC_RANDOM_SONG:
+ monty.random(1)
+ else:
+ monty.random(0)
- # save and load the queue
- def saveQueue(self):
- # save the ids as a list of space-separated numbers
- self.p.extended("saving queue")
- self.p.setSetting('queue', str(self.queuedSongs)[1:-1].replace(',', ''))
- def loadQueue(self):
- # just read all the numbers!
- self.p.extended("loading queue")
- self.queuedSongs=[]
- i=0
- ids=self.p.getSetting('queue').split(' ')
- for id in ids:
- try:
- self.queuedSongs.append(int(id))
- except:
- pass
- self._onQueueUpdate()
+ # save and load the queue
+ def saveQueue(self):
+ # save the ids as a list of space-separated numbers
+ self.p.extended("saving queue")
+ self.p.setSetting('queue', str(self.queuedSongs)[1:-1].replace(',', ''))
+ def loadQueue(self):
+ # just read all the numbers!
+ self.p.extended("loading queue")
+ self.queuedSongs=[]
+ i=0
+ ids=self.p.getSetting('queue').split(' ')
+ for id in ids:
+ try:
+ self.queuedSongs.append(int(id))
+ except:
+ pass
+ self._onQueueUpdate()
class pluginPlayControl(Plugin):
- o=None
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'PlayControl')
- self.addMontyListener('onStateChange', self.onStateChange)
- self.addMontyListener('beforeSongChange', self.beforeSongChange)
- self.addMontyListener('onSongChange', self.onSongChange)
- self.addMontyListener('onVolumeChange', self.onVolumeChange)
- self.addMontyListener('onReady', self.onStateChange)
- self.addMontyListener('onDisconnect', self.onDisconnect)
- self.addMontyListener('onTimeChange', self.onTimeChange)
- def _load(self):
- self.o=wgPlayControl(self, None)
- self.o.loadQueue()
- def _unload(self):
- self.o.saveQueue()
- self.o=None
- def getInfo(self):
- return "Have total control over the playing!"
-
- def addSongsToQueue(self, songs):
- return self.o.addSongsToQueue(songs)
-
- def onStateChange(self, params):
- self.o.onStateChange(params)
- def beforeSongChange(self, params):
- self.o.beforeSongChange(params)
- def onSongChange(self, params):
- self.o.onSongChange(params)
- def onVolumeChange(self, params):
- self.o.onVolumeChange(params)
- def onDisconnect(self, params):
- self.o.onDisconnect(params)
- def onTimeChange(self, params):
- self.o.onTimeChange(params)
-
- def _getDockWidget(self):
- return self._createDock(self.o)
+ o=None
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'PlayControl')
+ self.addMontyListener('onStateChange', self.onStateChange)
+ self.addMontyListener('beforeSongChange', self.beforeSongChange)
+ self.addMontyListener('onSongChange', self.onSongChange)
+ self.addMontyListener('onVolumeChange', self.onVolumeChange)
+ self.addMontyListener('onReady', self.onStateChange)
+ self.addMontyListener('onDisconnect', self.onDisconnect)
+ self.addMontyListener('onTimeChange', self.onTimeChange)
+ def _load(self):
+ self.o=wgPlayControl(self, None)
+ self.o.loadQueue()
+ def _unload(self):
+ self.o.saveQueue()
+ self.o=None
+ def getInfo(self):
+ return "Have total control over the playing!"
+
+ def addSongsToQueue(self, songs):
+ return self.o.addSongsToQueue(songs)
+
+ def onStateChange(self, params):
+ self.o.onStateChange(params)
+ def beforeSongChange(self, params):
+ self.o.beforeSongChange(params)
+ def onSongChange(self, params):
+ self.o.onSongChange(params)
+ def onVolumeChange(self, params):
+ self.o.onVolumeChange(params)
+ def onDisconnect(self, params):
+ self.o.onDisconnect(params)
+ def onTimeChange(self, params):
+ self.o.onTimeChange(params)
+
+ def _getDockWidget(self):
+ return self._createDock(self.o)
diff --git a/plugins/Playlist.py b/plugins/Playlist.py
index 3cb812b..811441d 100644
--- a/plugins/Playlist.py
+++ b/plugins/Playlist.py
@@ -7,85 +7,85 @@ from wgSongList import clrRowSel
import plugins
PLAYLIST_MODES_DEFAULT='$artist\n'\
- '$artist/$date - $album\n'\
- '$artist - $album\n'\
- '$album ($artist)\n'\
- '$genre\n'\
- '$genre/$artist\n'\
- '$genre/$artist - $album\n'
+ '$artist/$date - $album\n'\
+ '$artist - $album\n'\
+ '$album ($artist)\n'\
+ '$genre\n'\
+ '$genre/$artist\n'\
+ '$genre/$artist - $album\n'
# Dependencies:
# playcontrol
class pluginPlaylist(Plugin):
- o=None
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'Playlist')
- self.addMontyListener('onSongChange', self.onSongChange)
- def _load(self):
- self.o=Playlist(self.winMain, self, ['artist', 'title', 'track', 'album'], 'Playlist'
- , self.onDoubleClick, self.onKeyPress, self.getSetting('modes').split('\n'))
- def _unload(self):
- self.o=None
- def getPlaylist(self):
- return self.o
- def getInfo(self):
- return "The playlist showing the songs that will be played."
-
- def getList(self):
- return self.o
+ o=None
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Playlist')
+ self.addMontyListener('onSongChange', self.onSongChange)
+ def _load(self):
+ self.o=Playlist(self.winMain, self, ['artist', 'title', 'track', 'album'], 'Playlist'
+ , self.onDoubleClick, self.onKeyPress, self.getSetting('modes').split('\n'))
+ def _unload(self):
+ self.o=None
+ def getPlaylist(self):
+ return self.o
+ def getInfo(self):
+ return "The playlist showing the songs that will be played."
+
+ def getList(self):
+ return self.o
- def _getDockWidget(self):
- return self._createDock(self.o)
+ def _getDockWidget(self):
+ return self._createDock(self.o)
- def onDoubleClick(self):
- monty.play(self.o.getSelItemID())
+ def onDoubleClick(self):
+ monty.play(self.o.getSelItemID())
- def onKeyPress(self, event):
- if event.matches(QtGui.QKeySequence.Delete):
- # remove selection from playlist using DELETE-key
- ids=self.o.selectedIds()
- self.setStatus('Deleting '+str(len(ids))+' songs from playlist ...')
- doEvents()
+ def onKeyPress(self, event):
+ if event.matches(QtGui.QKeySequence.Delete):
+ # remove selection from playlist using DELETE-key
+ ids=self.o.selectedIds()
+ self.setStatus('Deleting '+str(len(ids))+' songs from playlist ...')
+ doEvents()
- monty.deleteFromPlaylist(ids)
+ monty.deleteFromPlaylist(ids)
- self.setStatus('')
- doEvents()
+ self.setStatus('')
+ doEvents()
- self.getWinMain().fillPlaylist()
- elif event.key()==QtCore.Qt.Key_Q:
- # queue selected songs
- # Hoho, this one needs the playcontrol plugin!
- plugins.getPlugin('playcontrol').addSongsToQueue(self.o.selectedIds())
- return QtGui.QWidget.keyPressEvent(self.o, event)
-
- def onSongChange(self, params):
- lst=self.o
- lst.colorID(int(params['newSongID']), clrRowSel)
+ self.getWinMain().fillPlaylist()
+ elif event.key()==QtCore.Qt.Key_Q:
+ # queue selected songs
+ # Hoho, this one needs the playcontrol plugin!
+ plugins.getPlugin('playcontrol').addSongsToQueue(self.o.selectedIds())
+ return QtGui.QWidget.keyPressEvent(self.o, event)
+
+ def onSongChange(self, params):
+ lst=self.o
+ lst.colorID(int(params['newSongID']), clrRowSel)
- if params['newSongID']!=-1:
- lst.ensureVisible(params['newSongID'])
+ if params['newSongID']!=-1:
+ lst.ensureVisible(params['newSongID'])
- _rowColorModifier=0
- _rowColorAdder=1
- def timerEvent(self, event):
- curSong=monty.getCurrentSong()
- if curSong:
- lst=self.lstPlaylist
- # color current playing song
- lst.colorID(curSong.getID(),
- QtGui.QColor(140-self._rowColorModifier*5,140-self._rowColorModifier*5,180))
+ _rowColorModifier=0
+ _rowColorAdder=1
+ def timerEvent(self, event):
+ curSong=monty.getCurrentSong()
+ if curSong:
+ lst=self.lstPlaylist
+ # color current playing song
+ lst.colorID(curSong.getID(),
+ QtGui.QColor(140-self._rowColorModifier*5,140-self._rowColorModifier*5,180))
- # make sure color changes nicely over time
- self._rowColorModifier=self._rowColorModifier+self._rowColorAdder
- if abs(self._rowColorModifier)>4:
- self._rowColorAdder=-1*self._rowColorAdder
+ # make sure color changes nicely over time
+ self._rowColorModifier=self._rowColorModifier+self._rowColorAdder
+ if abs(self._rowColorModifier)>4:
+ self._rowColorAdder=-1*self._rowColorAdder
- def _getSettings(self):
- modes=QtGui.QTextEdit()
- modes.insertPlainText(self.getSetting('modes'))
- return [
- ['modes', 'Modes', 'Sets the available modes.', modes],
- ]
- def afterSaveSettings(self):
- self.o.setModes(self.getSetting('modes').split('\n'))
+ def _getSettings(self):
+ modes=QtGui.QTextEdit()
+ modes.insertPlainText(self.getSetting('modes'))
+ return [
+ ['modes', 'Modes', 'Sets the available modes.', modes],
+ ]
+ def afterSaveSettings(self):
+ self.o.setModes(self.getSetting('modes').split('\n'))
diff --git a/plugins/Scrobbler.py b/plugins/Scrobbler.py
index bb2321f..ced3964 100644
--- a/plugins/Scrobbler.py
+++ b/plugins/Scrobbler.py
@@ -9,90 +9,90 @@ SCROBBLER_PASSWORD_DEFAULT=''
# TODO cached failed submissions
class pluginScrobbler(Plugin):
- submitted=False
- time=None
- loggedIn=False
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'Scrobbler')
- self.addMontyListener('onSongChange', self.onSongChange)
- self.addMontyListener('onTimeChange', self.onTimeChange)
-
- def _load(self):
- self._login()
-
- def _login(self):
- self.submitting=False
- self.loggedIn=False
- if self._username():
- self.normal("logging in %s"%(self._username()))
- try:
- login(self._username(), self._password())
- self.loggedIn=True
- except Exception, e:
- self.normal("failed to login: "+str(e))
- else:
- self.debug("no username provided, not logging in")
-
- def _username(self):
- return self.getSetting('username')
- def _password(self):
- return self.getSetting('password')
- def onTimeChange(self, params):
- if self.submitted==False and self.loggedIn:
- song=monty.getCurrentSong()
- if song.getTag('time')>30:
- if int(params['newTime'])>int(song.getTag('time'))/2 \
- or int(params['newTime'])>240:
- if not self.time:
- self.onSongChange(None)
- self.normal("submitting song")
- submit(song.getArtist(), song.getTitle(), self.time, 'P', '', song.getTag('time'), song.getAlbum(), song.getTrack(), False)
- self.debug("flushing ...")
- try:
- flush()
- self.submitted=True
- except Exception, e:
- self.important("failed to submit song1 - %s"%(e))
- self.extended("Logging in ...")
- self._login()
- try:
- flush()
- self.submitted=True
- except Exception, e:
- self.important("failed to submit song2 - %s"%(e))
- if self.submitted:
- self.debug("flushed")
- else:
- self.important("failed to submit the song!")
-
- def onSongChange(self, params):
- if self.loggedIn==False:
- return
- self.time=int(time.mktime(datetime.utcnow().timetuple()))
- self.submitted=False
- song=monty.getCurrentSong()
- # for loop: in case of SessionError, we need to retry once ...
- for i in [0, 1]:
- try:
- self.extended("submitting now playing")
- now_playing(song.getArtist(), song.getTitle(), song.getAlbum(), "", song.getTrack())
- self.debug("submitted")
- break
- except AuthError, e:
- self.important("failed to submit playing song - %s"%(e))
- break
- except SessionError, e:
- self.normal("session error")
- self._login()
- def _getSettings(self):
- return [
- ['username', 'Username', 'Username to submit to last.fm.', QtGui.QLineEdit(self._username())],
- ['password', 'Password', 'Password to user to submit. Note that the password is stored *unencrypted* to file.', QtGui.QLineEdit(self._password())],
- ]
- def afterSaveSettings(self):
- self._login()
- def getInfo(self):
- return "Submits tracks to last.fm"
+ submitted=False
+ time=None
+ loggedIn=False
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Scrobbler')
+ self.addMontyListener('onSongChange', self.onSongChange)
+ self.addMontyListener('onTimeChange', self.onTimeChange)
+
+ def _load(self):
+ self._login()
+
+ def _login(self):
+ self.submitting=False
+ self.loggedIn=False
+ if self._username():
+ self.normal("logging in %s"%(self._username()))
+ try:
+ login(self._username(), self._password())
+ self.loggedIn=True
+ except Exception, e:
+ self.normal("failed to login: "+str(e))
+ else:
+ self.debug("no username provided, not logging in")
+
+ def _username(self):
+ return self.getSetting('username')
+ def _password(self):
+ return self.getSetting('password')
+ def onTimeChange(self, params):
+ if self.submitted==False and self.loggedIn:
+ song=monty.getCurrentSong()
+ if song.getTag('time')>30:
+ if int(params['newTime'])>int(song.getTag('time'))/2 \
+ or int(params['newTime'])>240:
+ if not self.time:
+ self.onSongChange(None)
+ self.normal("submitting song")
+ submit(song.getArtist(), song.getTitle(), self.time, 'P', '', song.getTag('time'), song.getAlbum(), song.getTrack(), False)
+ self.debug("flushing ...")
+ try:
+ flush()
+ self.submitted=True
+ except Exception, e:
+ self.important("failed to submit song1 - %s"%(e))
+ self.extended("Logging in ...")
+ self._login()
+ try:
+ flush()
+ self.submitted=True
+ except Exception, e:
+ self.important("failed to submit song2 - %s"%(e))
+ if self.submitted:
+ self.debug("flushed")
+ else:
+ self.important("failed to submit the song!")
+
+ def onSongChange(self, params):
+ if self.loggedIn==False:
+ return
+ self.time=int(time.mktime(datetime.utcnow().timetuple()))
+ self.submitted=False
+ song=monty.getCurrentSong()
+ # for loop: in case of SessionError, we need to retry once ...
+ for i in [0, 1]:
+ try:
+ self.extended("submitting now playing")
+ now_playing(song.getArtist(), song.getTitle(), song.getAlbum(), "", song.getTrack())
+ self.debug("submitted")
+ break
+ except AuthError, e:
+ self.important("failed to submit playing song - %s"%(e))
+ break
+ except SessionError, e:
+ self.normal("session error")
+ self._login()
+ def _getSettings(self):
+ return [
+ ['username', 'Username', 'Username to submit to last.fm.', QtGui.QLineEdit(self._username())],
+ ['password', 'Password', 'Password to user to submit. Note that the password is stored *unencrypted* to file.', QtGui.QLineEdit(self._password())],
+ ]
+ def afterSaveSettings(self):
+ self._login()
+ def getInfo(self):
+ return "Submits tracks to last.fm"
@@ -110,293 +110,293 @@ from datetime import datetime, timedelta
from md5 import md5
SESSION_ID = None
-POST_URL = None
-NOW_URL = None
+POST_URL = None
+NOW_URL = None
HARD_FAILS = 0
-LAST_HS = None # Last handshake time
-HS_DELAY = 0 # wait this many seconds until next handshake
+LAST_HS = None # Last handshake time
+HS_DELAY = 0 # wait this many seconds until next handshake
SUBMIT_CACHE = []
-MAX_CACHE = 5 # keep only this many songs in the cache
+MAX_CACHE = 5 # keep only this many songs in the cache
PROTOCOL_VERSION = '1.2'
class BackendError(Exception):
- "Raised if the AS backend does something funny"
- pass
+ "Raised if the AS backend does something funny"
+ pass
class AuthError(Exception):
- "Raised on authencitation errors"
- pass
+ "Raised on authencitation errors"
+ pass
class PostError(Exception):
- "Raised if something goes wrong when posting data to AS"
- pass
+ "Raised if something goes wrong when posting data to AS"
+ pass
class SessionError(Exception):
- "Raised when problems with the session exist"
- pass
+ "Raised when problems with the session exist"
+ pass
class ProtocolError(Exception):
- "Raised on general Protocol errors"
- pass
+ "Raised on general Protocol errors"
+ pass
def login( user, password, client=('tst', '1.0') ):
- """Authencitate with AS (The Handshake)
-
- @param user: The username
- @param password: The password
- @param client: Client information (see http://www.audioscrobbler.net/development/protocol/ for more info)
- @type client: Tuple: (client-id, client-version)"""
- global LAST_HS, SESSION_ID, POST_URL, NOW_URL, HARD_FAILS, HS_DELAY, PROTOCOL_VERSION
-
- if LAST_HS is not None:
- next_allowed_hs = LAST_HS + timedelta(seconds=HS_DELAY)
- if datetime.now() < next_allowed_hs:
- delta = next_allowed_hs - datetime.now()
- raise ProtocolError("""Please wait another %d seconds until next handshake (login) attempt.""" % delta.seconds)
-
- LAST_HS = datetime.now()
-
- tstamp = int(mktime(datetime.now().timetuple()))
- url = "http://post.audioscrobbler.com/"
- pwhash = md5(password).hexdigest()
- token = md5( "%s%d" % (pwhash, int(tstamp))).hexdigest()
- values = {
- 'hs': 'true',
- 'p' : PROTOCOL_VERSION,
- 'c': client[0],
- 'v': client[1],
- 'u': user,
- 't': tstamp,
- 'a': token
- }
- data = urllib.urlencode(values)
- req = urllib2.Request("%s?%s" % (url, data) )
- response = urllib2.urlopen(req)
- result = response.read()
- lines = result.split('\n')
-
-
- if lines[0] == 'BADAUTH':
- raise AuthError('Bad username/password')
-
- elif lines[0] == 'BANNED':
- raise Exception('''This client-version was banned by Audioscrobbler. Please contact the author of this module!''')
-
- elif lines[0] == 'BADTIME':
- raise ValueError('''Your system time is out of sync with Audioscrobbler.Consider using an NTP-client to keep you system time in sync.''')
-
- elif lines[0].startswith('FAILED'):
- handle_hard_error()
- raise BackendError("Authencitation with AS failed. Reason: %s" %
- lines[0])
-
- elif lines[0] == 'OK':
- # wooooooohooooooo. We made it!
- SESSION_ID = lines[1]
- NOW_URL = lines[2]
- POST_URL = lines[3]
- HARD_FAILS = 0
-
- else:
- # some hard error
- handle_hard_error()
+ """Authencitate with AS (The Handshake)
+
+ @param user: The username
+ @param password: The password
+ @param client: Client information (see http://www.audioscrobbler.net/development/protocol/ for more info)
+ @type client: Tuple: (client-id, client-version)"""
+ global LAST_HS, SESSION_ID, POST_URL, NOW_URL, HARD_FAILS, HS_DELAY, PROTOCOL_VERSION
+
+ if LAST_HS is not None:
+ next_allowed_hs = LAST_HS + timedelta(seconds=HS_DELAY)
+ if datetime.now() < next_allowed_hs:
+ delta = next_allowed_hs - datetime.now()
+ raise ProtocolError("""Please wait another %d seconds until next handshake (login) attempt.""" % delta.seconds)
+
+ LAST_HS = datetime.now()
+
+ tstamp = int(mktime(datetime.now().timetuple()))
+ url = "http://post.audioscrobbler.com/"
+ pwhash = md5(password).hexdigest()
+ token = md5( "%s%d" % (pwhash, int(tstamp))).hexdigest()
+ values = {
+ 'hs': 'true',
+ 'p' : PROTOCOL_VERSION,
+ 'c': client[0],
+ 'v': client[1],
+ 'u': user,
+ 't': tstamp,
+ 'a': token
+ }
+ data = urllib.urlencode(values)
+ req = urllib2.Request("%s?%s" % (url, data) )
+ response = urllib2.urlopen(req)
+ result = response.read()
+ lines = result.split('\n')
+
+
+ if lines[0] == 'BADAUTH':
+ raise AuthError('Bad username/password')
+
+ elif lines[0] == 'BANNED':
+ raise Exception('''This client-version was banned by Audioscrobbler. Please contact the author of this module!''')
+
+ elif lines[0] == 'BADTIME':
+ raise ValueError('''Your system time is out of sync with Audioscrobbler.Consider using an NTP-client to keep you system time in sync.''')
+
+ elif lines[0].startswith('FAILED'):
+ handle_hard_error()
+ raise BackendError("Authencitation with AS failed. Reason: %s" %
+ lines[0])
+
+ elif lines[0] == 'OK':
+ # wooooooohooooooo. We made it!
+ SESSION_ID = lines[1]
+ NOW_URL = lines[2]
+ POST_URL = lines[3]
+ HARD_FAILS = 0
+
+ else:
+ # some hard error
+ handle_hard_error()
def handle_hard_error():
- "Handles hard errors."
- global SESSION_ID, HARD_FAILS, HS_DELAY
+ "Handles hard errors."
+ global SESSION_ID, HARD_FAILS, HS_DELAY
- if HS_DELAY == 0:
- HS_DELAY = 60
- elif HS_DELAY < 120*60:
- HS_DELAY *= 2
- if HS_DELAY > 120*60:
- HS_DELAY = 120*60
+ if HS_DELAY == 0:
+ HS_DELAY = 60
+ elif HS_DELAY < 120*60:
+ HS_DELAY *= 2
+ if HS_DELAY > 120*60:
+ HS_DELAY = 120*60
- HARD_FAILS += 1
- if HARD_FAILS == 3:
- SESSION_ID = None
+ HARD_FAILS += 1
+ if HARD_FAILS == 3:
+ SESSION_ID = None
def now_playing( artist, track, album="", length="", trackno="", mbid="" ):
- """Tells audioscrobbler what is currently running in your player. This won't
- affect the user-profile on last.fm. To do submissions, use the "submit"
- method
-
- @param artist: The artist name
- @param track: The track name
- @param album: The album name
- @param length: The song length in seconds
- @param trackno: The track number
- @param mbid: The MusicBrainz Track ID
- @return: True on success, False on failure"""
-
- global SESSION_ID, NOW_URL
-
- if SESSION_ID is None:
- raise AuthError("Please 'login()' first. (No session available)")
-
- if POST_URL is None:
- raise PostError("Unable to post data. Post URL was empty!")
-
- if length != "" and type(length) != type(1):
- raise TypeError("length should be of type int")
-
- if trackno != "" and type(trackno) != type(1):
- raise TypeError("trackno should be of type int")
-
- values = {'s': SESSION_ID,
- 'a': unicode(artist).encode('utf-8'),
- 't': unicode(track).encode('utf-8'),
- 'b': unicode(album).encode('utf-8'),
- 'l': length,
- 'n': trackno,
- 'm': mbid }
-
- data = urllib.urlencode(values)
- req = urllib2.Request(NOW_URL, data)
- response = urllib2.urlopen(req)
- result = response.read()
-
- if result.strip() == "OK":
- return True
- elif result.strip() == "BADSESSION" :
- raise SessionError('Invalid session')
- else:
- return False
+ """Tells audioscrobbler what is currently running in your player. This won't
+ affect the user-profile on last.fm. To do submissions, use the "submit"
+ method
+
+ @param artist: The artist name
+ @param track: The track name
+ @param album: The album name
+ @param length: The song length in seconds
+ @param trackno: The track number
+ @param mbid: The MusicBrainz Track ID
+ @return: True on success, False on failure"""
+
+ global SESSION_ID, NOW_URL
+
+ if SESSION_ID is None:
+ raise AuthError("Please 'login()' first. (No session available)")
+
+ if POST_URL is None:
+ raise PostError("Unable to post data. Post URL was empty!")
+
+ if length != "" and type(length) != type(1):
+ raise TypeError("length should be of type int")
+
+ if trackno != "" and type(trackno) != type(1):
+ raise TypeError("trackno should be of type int")
+
+ values = {'s': SESSION_ID,
+ 'a': unicode(artist).encode('utf-8'),
+ 't': unicode(track).encode('utf-8'),
+ 'b': unicode(album).encode('utf-8'),
+ 'l': length,
+ 'n': trackno,
+ 'm': mbid }
+
+ data = urllib.urlencode(values)
+ req = urllib2.Request(NOW_URL, data)
+ response = urllib2.urlopen(req)
+ result = response.read()
+
+ if result.strip() == "OK":
+ return True
+ elif result.strip() == "BADSESSION" :
+ raise SessionError('Invalid session')
+ else:
+ return False
def submit(artist, track, time, source='P', rating="", length="", album="",
- trackno="", mbid="", autoflush=False):
- """Append a song to the submission cache. Use 'flush()' to send the cache to
- AS. You can also set "autoflush" to True.
-
- From the Audioscrobbler protocol docs:
- ---------------------------------------------------------------------------
-
- The client should monitor the user's interaction with the music playing
- service to whatever extent the service allows. In order to qualify for
- submission all of the following criteria must be met:
-
- 1. The track must be submitted once it has finished playing. Whether it has
- finished playing naturally or has been manually stopped by the user is
- irrelevant.
- 2. The track must have been played for a duration of at least 240 seconds or
- half the track's total length, whichever comes first. Skipping or pausing
- the track is irrelevant as long as the appropriate amount has been played.
- 3. The total playback time for the track must be more than 30 seconds. Do
- not submit tracks shorter than this.
- 4. Unless the client has been specially configured, it should not attempt to
- interpret filename information to obtain metadata instead of tags (ID3,
- etc).
-
- @param artist: Artist name
- @param track: Track name
- @param time: Time the track *started* playing in the UTC timezone (see
- datetime.utcnow()).
-
- Example: int(time.mktime(datetime.utcnow()))
- @param source: Source of the track. One of:
- 'P': Chosen by the user
- 'R': Non-personalised broadcast (e.g. Shoutcast, BBC Radio 1)
- 'E': Personalised recommendation except Last.fm (e.g.
- Pandora, Launchcast)
- 'L': Last.fm (any mode). In this case, the 5-digit Last.fm
- recommendation key must be appended to this source ID to
- prove the validity of the submission (for example,
- "L1b48a").
- 'U': Source unknown
- @param rating: The rating of the song. One of:
- 'L': Love (on any mode if the user has manually loved the
- track)
- 'B': Ban (only if source=L)
- 'S': Skip (only if source=L)
- '': Not applicable
- @param length: The song length in seconds
- @param album: The album name
- @param trackno:The track number
- @param mbid: MusicBrainz Track ID
- @param autoflush: Automatically flush the cache to AS?
- """
-
- global SUBMIT_CACHE, MAX_CACHE
-
- source = source.upper()
- rating = rating.upper()
-
- if source == 'L' and (rating == 'B' or rating == 'S'):
- raise ProtocolError("""You can only use rating 'B' or 'S' on source 'L'.See the docs!""")
-
- if source == 'P' and length == '':
- raise ProtocolError("""Song length must be specified when using 'P' as source!""")
-
- if type(time) != type(1):
- raise ValueError("""The time parameter must be of type int (unix timestamp). Instead it was %s""" % time)
-
- SUBMIT_CACHE.append(
- { 'a': unicode(artist).encode('utf-8'),
- 't': unicode(track).encode('utf-8'),
- 'i': time,
- 'o': source,
- 'r': rating,
- 'l': length,
- 'b': unicode(album).encode('utf-8'),
- 'n': trackno,
- 'm': mbid
- }
- )
-
- if autoflush or len(SUBMIT_CACHE) >= MAX_CACHE:
- flush()
+ trackno="", mbid="", autoflush=False):
+ """Append a song to the submission cache. Use 'flush()' to send the cache to
+ AS. You can also set "autoflush" to True.
+
+ From the Audioscrobbler protocol docs:
+ ---------------------------------------------------------------------------
+
+ The client should monitor the user's interaction with the music playing
+ service to whatever extent the service allows. In order to qualify for
+ submission all of the following criteria must be met:
+
+ 1. The track must be submitted once it has finished playing. Whether it has
+ finished playing naturally or has been manually stopped by the user is
+ irrelevant.
+ 2. The track must have been played for a duration of at least 240 seconds or
+ half the track's total length, whichever comes first. Skipping or pausing
+ the track is irrelevant as long as the appropriate amount has been played.
+ 3. The total playback time for the track must be more than 30 seconds. Do
+ not submit tracks shorter than this.
+ 4. Unless the client has been specially configured, it should not attempt to
+ interpret filename information to obtain metadata instead of tags (ID3,
+ etc).
+
+ @param artist: Artist name
+ @param track: Track name
+ @param time: Time the track *started* playing in the UTC timezone (see
+ datetime.utcnow()).
+
+ Example: int(time.mktime(datetime.utcnow()))
+ @param source: Source of the track. One of:
+ 'P': Chosen by the user
+ 'R': Non-personalised broadcast (e.g. Shoutcast, BBC Radio 1)
+ 'E': Personalised recommendation except Last.fm (e.g.
+ Pandora, Launchcast)
+ 'L': Last.fm (any mode). In this case, the 5-digit Last.fm
+ recommendation key must be appended to this source ID to
+ prove the validity of the submission (for example,
+ "L1b48a").
+ 'U': Source unknown
+ @param rating: The rating of the song. One of:
+ 'L': Love (on any mode if the user has manually loved the
+ track)
+ 'B': Ban (only if source=L)
+ 'S': Skip (only if source=L)
+ '': Not applicable
+ @param length: The song length in seconds
+ @param album: The album name
+ @param trackno:The track number
+ @param mbid: MusicBrainz Track ID
+ @param autoflush: Automatically flush the cache to AS?
+ """
+
+ global SUBMIT_CACHE, MAX_CACHE
+
+ source = source.upper()
+ rating = rating.upper()
+
+ if source == 'L' and (rating == 'B' or rating == 'S'):
+ raise ProtocolError("""You can only use rating 'B' or 'S' on source 'L'.See the docs!""")
+
+ if source == 'P' and length == '':
+ raise ProtocolError("""Song length must be specified when using 'P' as source!""")
+
+ if type(time) != type(1):
+ raise ValueError("""The time parameter must be of type int (unix timestamp). Instead it was %s""" % time)
+
+ SUBMIT_CACHE.append(
+ { 'a': unicode(artist).encode('utf-8'),
+ 't': unicode(track).encode('utf-8'),
+ 'i': time,
+ 'o': source,
+ 'r': rating,
+ 'l': length,
+ 'b': unicode(album).encode('utf-8'),
+ 'n': trackno,
+ 'm': mbid
+ }
+ )
+
+ if autoflush or len(SUBMIT_CACHE) >= MAX_CACHE:
+ flush()
def flush():
- "Sends the cached songs to AS."
- global SUBMIT_CACHE
-
- values = {}
-
- for i, item in enumerate(SUBMIT_CACHE):
- for key in item:
- values[key + "[%d]" % i] = item[key]
-
- values['s'] = SESSION_ID
-
- data = urllib.urlencode(values)
- req = urllib2.Request(POST_URL, data)
- response = urllib2.urlopen(req)
- result = response.read()
- lines = result.split('\n')
-
- if lines[0] == "OK":
- SUBMIT_CACHE = []
- return True
- elif lines[0] == "BADSESSION" :
- raise SessionError('Invalid session')
- elif lines[0].startswith('FAILED'):
- handle_hard_error()
- raise BackendError("Authencitation with AS failed. Reason: %s" %
- lines[0])
- else:
- # some hard error
- handle_hard_error()
- return False
+ "Sends the cached songs to AS."
+ global SUBMIT_CACHE
+
+ values = {}
+
+ for i, item in enumerate(SUBMIT_CACHE):
+ for key in item:
+ values[key + "[%d]" % i] = item[key]
+
+ values['s'] = SESSION_ID
+
+ data = urllib.urlencode(values)
+ req = urllib2.Request(POST_URL, data)
+ response = urllib2.urlopen(req)
+ result = response.read()
+ lines = result.split('\n')
+
+ if lines[0] == "OK":
+ SUBMIT_CACHE = []
+ return True
+ elif lines[0] == "BADSESSION" :
+ raise SessionError('Invalid session')
+ elif lines[0].startswith('FAILED'):
+ handle_hard_error()
+ raise BackendError("Authencitation with AS failed. Reason: %s" %
+ lines[0])
+ else:
+ # some hard error
+ handle_hard_error()
+ return False
if __name__ == "__main__":
- login( 'user', 'password' )
- submit(
- 'De/Vision',
- 'Scars',
- 1192374052,
- source='P',
- length=3*60+44
- )
- submit(
- 'Spineshank',
- 'Beginning of the End',
- 1192374052+(5*60),
- source='P',
- length=3*60+32
- )
- submit(
- 'Dry Cell',
- 'Body Crumbles',
- 1192374052+(10*60),
- source='P',
- length=3*60+3
- )
- print flush()
+ login( 'user', 'password' )
+ submit(
+ 'De/Vision',
+ 'Scars',
+ 1192374052,
+ source='P',
+ length=3*60+44
+ )
+ submit(
+ 'Spineshank',
+ 'Beginning of the End',
+ 1192374052+(5*60),
+ source='P',
+ length=3*60+32
+ )
+ submit(
+ 'Dry Cell',
+ 'Body Crumbles',
+ 1192374052+(10*60),
+ source='P',
+ length=3*60+3
+ )
+ print flush()
diff --git a/plugins/Shortcuts.py b/plugins/Shortcuts.py
index baa462f..e76078f 100644
--- a/plugins/Shortcuts.py
+++ b/plugins/Shortcuts.py
@@ -6,81 +6,81 @@ from misc import *
import format
class pluginShortcuts(Plugin):
- keys=None
- col=None
- actionPrefix=None
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'Shortcuts')
+ keys=None
+ col=None
+ actionPrefix=None
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Shortcuts')
- def _load(self):
- winMain=self.getWinMain()
- # Note people wanting to implement global shortcuts in KDE4 (and sniffing through
- # this code): one needs to have a KApplication running, else shortcuts will fail
- self.col=kdeui.KActionCollection(None)
- self.col.addAssociatedWidget(winMain)
- self.keys=[
- ['toggleplay', QtCore.Qt.META+QtCore.Qt.Key_Home, self.togglePlay],
- ['volumeup', QtCore.Qt.META+QtCore.Qt.Key_PageDown, lambda b: monty.setVolume(monty.getVolume()-5)],
- ['volumedown', QtCore.Qt.META+QtCore.Qt.Key_PageUp, lambda b: monty.setVolume(monty.getVolume()+5)],
- ['playnext', QtCore.Qt.META+QtCore.Qt.Key_Right, monty.next],
- ['playprevious', QtCore.Qt.META+QtCore.Qt.Key_Left, monty.previous],
- ['showosd', QtCore.Qt.META+QtCore.Qt.Key_O, self.showOSD],
- ['togglewin', QtCore.Qt.META+QtCore.Qt.Key_P, self.toggleWinMain],
- ]
- # Note: don't use any non-alphanumerics in the prefix, as else it won't work
- self.actionPrefix="shortcuts"
-
- for entry in self.keys:
- name=entry[0]
- key=entry[1]
- callback=entry[2]
-
- self.debug("%s - %s"%(name, QtGui.QKeySequence(key).toString()))
-
- action=kdeui.KAction(winMain) # winMain needed
- action.setText(name)
- action.setObjectName(name)
- action.setGlobalShortcut(kdeui.KShortcut(key))
- QtCore.QObject.connect(action, QtCore.SIGNAL('triggered(bool)'), callback)
- self.col.addAction("%s%s"%(self.actionPrefix, action.objectName()), action)
- def _unload(self):
- actions=self.col.actions()
- for action in actions:
- try:
- if action.objectName()[0:len(self.actionPrefix)]==self.actionPrefix:
- self.debug("removing %s"%(action.objectName()))
- self.col.removeAction(action)
- except Exception, e:
- self.important(str(e))
-
- def getInfo(self):
- return "Shortcuts for mpd."
+ def _load(self):
+ winMain=self.getWinMain()
+ # Note people wanting to implement global shortcuts in KDE4 (and sniffing through
+ # this code): one needs to have a KApplication running, else shortcuts will fail
+ self.col=kdeui.KActionCollection(None)
+ self.col.addAssociatedWidget(winMain)
+ self.keys=[
+ ['toggleplay', QtCore.Qt.META+QtCore.Qt.Key_Home, self.togglePlay],
+ ['volumeup', QtCore.Qt.META+QtCore.Qt.Key_PageDown, lambda b: monty.setVolume(monty.getVolume()-5)],
+ ['volumedown', QtCore.Qt.META+QtCore.Qt.Key_PageUp, lambda b: monty.setVolume(monty.getVolume()+5)],
+ ['playnext', QtCore.Qt.META+QtCore.Qt.Key_Right, monty.next],
+ ['playprevious', QtCore.Qt.META+QtCore.Qt.Key_Left, monty.previous],
+ ['showosd', QtCore.Qt.META+QtCore.Qt.Key_O, self.showOSD],
+ ['togglewin', QtCore.Qt.META+QtCore.Qt.Key_P, self.toggleWinMain],
+ ]
+ # Note: don't use any non-alphanumerics in the prefix, as else it won't work
+ self.actionPrefix="shortcuts"
+
+ for entry in self.keys:
+ name=entry[0]
+ key=entry[1]
+ callback=entry[2]
+
+ self.debug("%s - %s"%(name, QtGui.QKeySequence(key).toString()))
+
+ action=kdeui.KAction(winMain) # winMain needed
+ action.setText(name)
+ action.setObjectName(name)
+ action.setGlobalShortcut(kdeui.KShortcut(key))
+ QtCore.QObject.connect(action, QtCore.SIGNAL('triggered(bool)'), callback)
+ self.col.addAction("%s%s"%(self.actionPrefix, action.objectName()), action)
+ def _unload(self):
+ actions=self.col.actions()
+ for action in actions:
+ try:
+ if action.objectName()[0:len(self.actionPrefix)]==self.actionPrefix:
+ self.debug("removing %s"%(action.objectName()))
+ self.col.removeAction(action)
+ except Exception, e:
+ self.important(str(e))
+
+ def getInfo(self):
+ return "Shortcuts for mpd."
- def showOSD(self, b):
- plugins.getPlugin('notify').onSongChange(None)
+ def showOSD(self, b):
+ plugins.getPlugin('notify').onSongChange(None)
- def togglePlay(self, btns=None, mods=None):
- if monty.isPlaying():
- monty.pause()
- else:
- monty.resume()
-
- def toggleWinMain(self, b):
- w=self.getWinMain()
- if w.isVisible():
- w.setVisible(False)
- else:
- w.setVisible(True)
-
- def _getSettings(self):
- txt=QtGui.QTextEdit()
- txt.setReadOnly(True)
- txt.insertPlainText("Keybindings (read-only)\n")
- for entry in self.keys:
- name=entry[0]
- key=entry[1]
-
- txt.insertPlainText("%s\t%s\n"%(name, QtGui.QKeySequence(key).toString()))
- return [
- ['', 'Keybindings', 'Current keybindings', txt],
- ]
+ def togglePlay(self, btns=None, mods=None):
+ if monty.isPlaying():
+ monty.pause()
+ else:
+ monty.resume()
+
+ def toggleWinMain(self, b):
+ w=self.getWinMain()
+ if w.isVisible():
+ w.setVisible(False)
+ else:
+ w.setVisible(True)
+
+ def _getSettings(self):
+ txt=QtGui.QTextEdit()
+ txt.setReadOnly(True)
+ txt.insertPlainText("Keybindings (read-only)\n")
+ for entry in self.keys:
+ name=entry[0]
+ key=entry[1]
+
+ txt.insertPlainText("%s\t%s\n"%(name, QtGui.QKeySequence(key).toString()))
+ return [
+ ['', 'Keybindings', 'Current keybindings', txt],
+ ]
diff --git a/plugins/SongStatus.py b/plugins/SongStatus.py
index 052e99b..71aa6e4 100644
--- a/plugins/SongStatus.py
+++ b/plugins/SongStatus.py
@@ -12,79 +12,79 @@ SONGSTATUS_FORMAT_DEFAULT='<font size="4">now $state</font>'\
'$if($length,<br /><font size="4">$time/$length</font>)'
class wgSongStatus(QtGui.QWidget):
- """Displays the status of the current song, if playing."""
- " label containing the info"
- lblInfo=None
- format=None
- p=None
- def __init__(self, p, parent=None):
- QtGui.QWidget.__init__(self, parent)
- self.p=p
+ """Displays the status of the current song, if playing."""
+ " label containing the info"
+ lblInfo=None
+ format=None
+ p=None
+ def __init__(self, p, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.p=p
- self.lblInfo=QtGui.QLabel()
- self.setMinimumWidth(400)
-
- layout=QtGui.QHBoxLayout()
- self.setLayout(layout)
+ self.lblInfo=QtGui.QLabel()
+ self.setMinimumWidth(400)
+
+ layout=QtGui.QHBoxLayout()
+ self.setLayout(layout)
- layout.addWidget(self.lblInfo)
- self.updateFormat()
+ layout.addWidget(self.lblInfo)
+ self.updateFormat()
- def update(self, params):
- status=monty.getStatus()
- song=monty.getCurrentSong()
+ def update(self, params):
+ status=monty.getStatus()
+ song=monty.getCurrentSong()
- values={'state':''}
- try:
- values['state']={'play':'playing', 'stop':'stopped', 'pause':'paused'}[status['state']]
- if 'time' in status:
- values['length']=sec2min(status['length'])
- values['time']=sec2min(status['time'])
- except:
- pass
-
- if song:
- self.lblInfo.setText(self.format(format.params(song, values)))
-
- def updateFormat(self):
- try:
- self.format=format.compile(self.p.getSetting('format'))
- except Exception, e:
- self.format=lambda p: "Invalid format: %s"%(e)
+ values={'state':''}
+ try:
+ values['state']={'play':'playing', 'stop':'stopped', 'pause':'paused'}[status['state']]
+ if 'time' in status:
+ values['length']=sec2min(status['length'])
+ values['time']=sec2min(status['time'])
+ except:
+ pass
+
+ if song:
+ self.lblInfo.setText(self.format(format.params(song, values)))
+
+ def updateFormat(self):
+ try:
+ self.format=format.compile(self.p.getSetting('format'))
+ except Exception, e:
+ self.format=lambda p: "Invalid format: %s"%(e)
- def text(self):
- return self.lblInfo.text()
+ def text(self):
+ return self.lblInfo.text()
class pluginSongStatus(Plugin):
- o=None
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'SongStatus')
- self.addMontyListener('onSongChange', self.update)
- self.addMontyListener('onTimeChange', self.update)
- self.addMontyListener('onStateChange', self.update)
- self.addMontyListener('onConnect', self.update)
- self.addMontyListener('onDisconnect', self.update)
-
- def _load(self):
- self.o=wgSongStatus(self, None)
- self.update(None)
- def _unload(self):
- self.o=None
- def getInfo(self):
- return "Show information about the current song."
-
- def update(self, params):
- self.o.update(params)
-
- def _getDockWidget(self):
- return self._createDock(self.o)
+ o=None
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'SongStatus')
+ self.addMontyListener('onSongChange', self.update)
+ self.addMontyListener('onTimeChange', self.update)
+ self.addMontyListener('onStateChange', self.update)
+ self.addMontyListener('onConnect', self.update)
+ self.addMontyListener('onDisconnect', self.update)
+
+ def _load(self):
+ self.o=wgSongStatus(self, None)
+ self.update(None)
+ def _unload(self):
+ self.o=None
+ def getInfo(self):
+ return "Show information about the current song."
+
+ def update(self, params):
+ self.o.update(params)
+
+ def _getDockWidget(self):
+ return self._createDock(self.o)
- def _getSettings(self):
- format=QtGui.QTextEdit()
- format.insertPlainText(self.getSetting('format'))
- return [
- ['format', 'Format', 'Format of the song status. Possible tags: $title, $artist, $album, $track, $time, $length, $state', format]
- ]
- def afterSaveSettings(self):
- self.o.updateFormat()
- self.o.update(None)
+ def _getSettings(self):
+ format=QtGui.QTextEdit()
+ format.insertPlainText(self.getSetting('format'))
+ return [
+ ['format', 'Format', 'Format of the song status. Possible tags: $title, $artist, $album, $track, $time, $length, $state', format]
+ ]
+ def afterSaveSettings(self):
+ self.o.updateFormat()
+ self.o.update(None)
diff --git a/plugins/Systray.py b/plugins/Systray.py
index 75d2267..b933225 100644
--- a/plugins/Systray.py
+++ b/plugins/Systray.py
@@ -7,107 +7,107 @@ import format
SYSTRAY_FORMAT="$if($title,$title)$if($artist, by $artist) - [$album # $track] ($time/$length)"
class pluginSystray(Plugin):
- o=None
- format=None
- eventObj=None
- time=None # indicator of current time [0..64]
- appIcon=None
- pixmap=None
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'Systray')
- self.addMontyListener('onSongChange', self.update)
- self.addMontyListener('onReady', self.update)
- self.addMontyListener('onConnect', self.update)
- self.addMontyListener('onDisconnect', self.update)
- # TODO only update this when necessary, i.e. mouse-hover etc
- self.addMontyListener('onTimeChange', self.update)
- self.appIcon=appIcon
-
+ o=None
+ format=None
+ eventObj=None
+ time=None # indicator of current time [0..64]
+ appIcon=None
+ pixmap=None
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Systray')
+ self.addMontyListener('onSongChange', self.update)
+ self.addMontyListener('onReady', self.update)
+ self.addMontyListener('onConnect', self.update)
+ self.addMontyListener('onDisconnect', self.update)
+ # TODO only update this when necessary, i.e. mouse-hover etc
+ self.addMontyListener('onTimeChange', self.update)
+ self.appIcon=appIcon
+
- def _load(self):
- self.format=format.compile(SYSTRAY_FORMAT)
- class SystrayWheelEventObject(QtCore.QObject):
- """This class listens for systray-wheel events"""
- def eventFilter(self, object, event):
- if type(event)==QtGui.QWheelEvent:
- numDegrees=event.delta() / 8
- numSteps=5*numDegrees/15
- monty.setVolume(int(monty.getStatus()['volume'])+numSteps)
- event.accept()
- return True
- return False
-
- self.o=QtGui.QSystemTrayIcon(appIcon, self.winMain)
- self.eventObj=SystrayWheelEventObject()
- self.o.installEventFilter(self.eventObj)
- self.winMain.connect(self.o, QtCore.SIGNAL('activated (QSystemTrayIcon::ActivationReason)')
- , self.onSysTrayClick)
- self.o.show()
- self.update(None)
-
- def _unload(self):
- self.o.hide()
- self.o.setIcon(QtGui.QIcon(None))
- self.o=None
- self.winMain._wheelEvent=None
- def getInfo(self):
- return "Display the montypc icon in the systray."
-
- def update(self, params):
- status=monty.getStatus()
- song=monty.getCurrentSong()
+ def _load(self):
+ self.format=format.compile(SYSTRAY_FORMAT)
+ class SystrayWheelEventObject(QtCore.QObject):
+ """This class listens for systray-wheel events"""
+ def eventFilter(self, object, event):
+ if type(event)==QtGui.QWheelEvent:
+ numDegrees=event.delta() / 8
+ numSteps=5*numDegrees/15
+ monty.setVolume(int(monty.getStatus()['volume'])+numSteps)
+ event.accept()
+ return True
+ return False
+
+ self.o=QtGui.QSystemTrayIcon(appIcon, self.winMain)
+ self.eventObj=SystrayWheelEventObject()
+ self.o.installEventFilter(self.eventObj)
+ self.winMain.connect(self.o, QtCore.SIGNAL('activated (QSystemTrayIcon::ActivationReason)')
+ , self.onSysTrayClick)
+ self.o.show()
+ self.update(None)
+
+ def _unload(self):
+ self.o.hide()
+ self.o.setIcon(QtGui.QIcon(None))
+ self.o=None
+ self.winMain._wheelEvent=None
+ def getInfo(self):
+ return "Display the montypc icon in the systray."
+
+ def update(self, params):
+ status=monty.getStatus()
+ song=monty.getCurrentSong()
- values={'state':''}
- try:
- values['state']={'play':'playing', 'stop':'stopped', 'pause':'paused'}[status['state']]
- if 'time' in status:
- values['length']=sec2min(status['length'])
- values['time']=sec2min(status['time'])
- except:
- pass
-
- if song:
- self.o.setToolTip(self.format(format.params(song, values)))
- else:
- self.o.setToolTip("montypc not playing")
-
- try:
- curTime=(64*status['time'])/status['length']
- except:
- curTime=-1
- if self.time!=curTime:
- self.time=curTime
- # redraw the systray icon
- self.pixmap=self.appIcon.pixmap(64,64)
- painter=QtGui.QPainter(self.pixmap)
- painter.fillRect(1, curTime, 63, 64, QtGui.QBrush(QtCore.Qt.white))
- appIcon.paint(painter, 1, 0, 63, 64)
- self.o.setIcon(QtGui.QIcon(self.pixmap))
-
- elif not song:
- self.time=None
- self.o.setIcon(appIcon)
+ values={'state':''}
+ try:
+ values['state']={'play':'playing', 'stop':'stopped', 'pause':'paused'}[status['state']]
+ if 'time' in status:
+ values['length']=sec2min(status['length'])
+ values['time']=sec2min(status['time'])
+ except:
+ pass
+
+ if song:
+ self.o.setToolTip(self.format(format.params(song, values)))
+ else:
+ self.o.setToolTip("montypc not playing")
+
+ try:
+ curTime=(64*status['time'])/status['length']
+ except:
+ curTime=-1
+ if self.time!=curTime:
+ self.time=curTime
+ # redraw the systray icon
+ self.pixmap=self.appIcon.pixmap(64,64)
+ painter=QtGui.QPainter(self.pixmap)
+ painter.fillRect(1, curTime, 63, 64, QtGui.QBrush(QtCore.Qt.white))
+ appIcon.paint(painter, 1, 0, 63, 64)
+ self.o.setIcon(QtGui.QIcon(self.pixmap))
+
+ elif not song:
+ self.time=None
+ self.o.setIcon(appIcon)
-
- def onSysTrayClick(self, reason):
- if reason==QtGui.QSystemTrayIcon.Trigger \
- or reason==QtGui.QSystemTrayIcon.Context:
- w=self.getWinMain()
- # left mouse button
- if w.isVisible():
- settings.setIntTuple('winMain.pos', w.x(), w.y())
- w.setVisible(False)
- else:
- w.setVisible(True)
- try:
- x,y=settings.getIntTuple('winMain.pos')
- except:
- x,y=0,0
- w.move(x, y)
- elif reason==QtGui.QSystemTrayIcon.MiddleClick:
- # middle mouse button
- if monty.isPlaying():
- monty.pause()
- else:
- monty.resume()
-
+
+ def onSysTrayClick(self, reason):
+ if reason==QtGui.QSystemTrayIcon.Trigger \
+ or reason==QtGui.QSystemTrayIcon.Context:
+ w=self.getWinMain()
+ # left mouse button
+ if w.isVisible():
+ settings.setIntTuple('winMain.pos', w.x(), w.y())
+ w.setVisible(False)
+ else:
+ w.setVisible(True)
+ try:
+ x,y=settings.getIntTuple('winMain.pos')
+ except:
+ x,y=0,0
+ w.move(x, y)
+ elif reason==QtGui.QSystemTrayIcon.MiddleClick:
+ # middle mouse button
+ if monty.isPlaying():
+ monty.pause()
+ else:
+ monty.resume()
+
diff --git a/plugins/Tabs.py b/plugins/Tabs.py
index 811096a..8914a4b 100644
--- a/plugins/Tabs.py
+++ b/plugins/Tabs.py
@@ -9,157 +9,157 @@ from clMonty import monty
from clPlugin import *
class ResetEvent(QtCore.QEvent):
- song=None
- def __init__(self, song=None):
- QtCore.QEvent.__init__(self,QtCore.QEvent.User)
- self.song=song
+ song=None
+ def __init__(self, song=None):
+ QtCore.QEvent.__init__(self,QtCore.QEvent.User)
+ self.song=song
class AddHtmlEvent(QtCore.QEvent):
- html=None
- def __init__(self,html):
- QtCore.QEvent.__init__(self,QtCore.QEvent.User)
- self.html=html
+ html=None
+ def __init__(self,html):
+ QtCore.QEvent.__init__(self,QtCore.QEvent.User)
+ self.html=html
TABS_DIR_DEFAULT='/jammin'
TABS_ENGINE_DEFAULT='http://www.google.com/search?q=tabs|chords+"$artist"+"$title"'
-TABS_SITES_DEFAULT='azchords.com <pre>(.*?)</pre>\n'\
- 'fretplay.com <P CLASS="tabs">(.*?)</P>\n'\
- 'guitaretab.com <pre style="COLOR: #000000; FONT-SIZE: 11px;">(.*)?</pre>'\
+TABS_SITES_DEFAULT='azchords.com <pre>(.*?)</pre>\n'\
+ 'fretplay.com <P CLASS="tabs">(.*?)</P>\n'\
+ 'guitaretab.com <pre style="COLOR: #000000; FONT-SIZE: 11px;">(.*)?</pre>'\
class wgTabs(QtGui.QWidget):
- " contains the tabs"
- txt=None
- p=None # plugin
- def __init__(self, p, parent=None):
- QtGui.QWidget.__init__(self, parent)
- self.p=p
- self.txt=QtGui.QTextEdit(parent)
- self.txt.setReadOnly(True)
-
- layout=QtGui.QVBoxLayout()
- layout.addWidget(self.txt)
- self.setLayout(layout)
+ " contains the tabs"
+ txt=None
+ p=None # plugin
+ def __init__(self, p, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.p=p
+ self.txt=QtGui.QTextEdit(parent)
+ self.txt.setReadOnly(True)
+
+ layout=QtGui.QVBoxLayout()
+ layout.addWidget(self.txt)
+ self.setLayout(layout)
-
- def refresh(self):
- song=monty.getCurrentSong()
- try:
- song._data['file']
- except:
- self.resetTxt()
- return
-
- self.resetTxt(song)
- start_new_thread(self.fetchTabs, (song,))
-
- def customEvent(self, event):
- if isinstance(event,ResetEvent):
- self.resetTxt(event.song)
- elif isinstance(event,AddHtmlEvent):
- self.txt.insertHtml(event.html)
+
+ def refresh(self):
+ song=monty.getCurrentSong()
+ try:
+ song._data['file']
+ except:
+ self.resetTxt()
+ return
+
+ self.resetTxt(song)
+ start_new_thread(self.fetchTabs, (song,))
+
+ def customEvent(self, event):
+ if isinstance(event,ResetEvent):
+ self.resetTxt(event.song)
+ elif isinstance(event,AddHtmlEvent):
+ self.txt.insertHtml(event.html)
- _mutex=QtCore.QMutex()
- _fetchCnt=0
- def fetchTabs(self, song):
- # only allow 1 instance to look tabs!
- self._mutex.lock()
- if self._fetchCnt:
- self._mutex.unlock()
- return
- self._fetchCnt=1
- self._mutex.unlock()
+ _mutex=QtCore.QMutex()
+ _fetchCnt=0
+ def fetchTabs(self, song):
+ # only allow 1 instance to look tabs!
+ self._mutex.lock()
+ if self._fetchCnt:
+ self._mutex.unlock()
+ return
+ self._fetchCnt=1
+ self._mutex.unlock()
- QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
-
- # save the data to file!
- save_dir=self.p.getSetting('dir')
- fInfo=QtCore.QFileInfo(save_dir)
- if fInfo.isDir():
- tabsFName=toAscii('%s/%s - %s.txt'%(save_dir,song.getArtist(),song.getTitle()))
- else:
- tabsFName=None
- # does the file exist? if yes, read that one!
- try:
- # we have it: load, and return!
- file=open(tabsFName, 'r')
- QtCore.QCoreApplication.postEvent(self, AddHtmlEvent(file.read()))
- file.close()
- self._fetchCnt=0
- return
- except:
- pass
+ QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
+
+ # save the data to file!
+ save_dir=self.p.getSetting('dir')
+ fInfo=QtCore.QFileInfo(save_dir)
+ if fInfo.isDir():
+ tabsFName=toAscii('%s/%s - %s.txt'%(save_dir,song.getArtist(),song.getTitle()))
+ else:
+ tabsFName=None
+ # does the file exist? if yes, read that one!
+ try:
+ # we have it: load, and return!
+ file=open(tabsFName, 'r')
+ QtCore.QCoreApplication.postEvent(self, AddHtmlEvent(file.read()))
+ file.close()
+ self._fetchCnt=0
+ return
+ except:
+ pass
- # fetch from inet
- QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<i>Searching tabs ...</i>'))
-
- lines=self.p.getSetting('sites').split('\n')
- sites={}
- for line in lines:
- if line.strip():
- sites[line[0:line.find('\t')]]=line[line.find('\t'):].strip()
- # construct URL to search!
- SE=self.p.getSetting('engine')
- try:
- ret=fetch(SE, sites, song, {}, stripHTML=False)
- if ret:
- txt='<pre>%s<br /><br /><a href="%s">%s</a></pre>'%(ret[0],ret[1],ret[1])
- # save for later use!
- if tabsFName:
- # we can't save if the path isn't correct
- try:
- file=open(tabsFName, 'w')
- file.write(ret[0])
- file.close()
- except:
- pass
- else:
- txt="No tabs found :'("
+ # fetch from inet
+ QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<i>Searching tabs ...</i>'))
+
+ lines=self.p.getSetting('sites').split('\n')
+ sites={}
+ for line in lines:
+ if line.strip():
+ sites[line[0:line.find('\t')]]=line[line.find('\t'):].strip()
+ # construct URL to search!
+ SE=self.p.getSetting('engine')
+ try:
+ ret=fetch(SE, sites, song, {}, stripHTML=False)
+ if ret:
+ txt='<pre>%s<br /><br /><a href="%s">%s</a></pre>'%(ret[0],ret[1],ret[1])
+ # save for later use!
+ if tabsFName:
+ # we can't save if the path isn't correct
+ try:
+ file=open(tabsFName, 'w')
+ file.write(ret[0])
+ file.close()
+ except:
+ pass
+ else:
+ txt="No tabs found :'("
- QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
- QtCore.QCoreApplication.postEvent(self, AddHtmlEvent(txt))
- except:
- print_exc()
- QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
- QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('Woops, site unavailable!'\
- '<br />You have an internet connection?'))
- self._fetchCnt=0
+ QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
+ QtCore.QCoreApplication.postEvent(self, AddHtmlEvent(txt))
+ except:
+ print_exc()
+ QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
+ QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('Woops, site unavailable!'\
+ '<br />You have an internet connection?'))
+ self._fetchCnt=0
- def resetTxt(self, song=None):
- self.txt.clear()
- if song:
- self.txt.insertHtml('<b>%s</b>\n<br /><u>%s</u><br />'\
- '<br />\n\n'%(song.getTitle(), song.getArtist()))
+ def resetTxt(self, song=None):
+ self.txt.clear()
+ if song:
+ self.txt.insertHtml('<b>%s</b>\n<br /><u>%s</u><br />'\
+ '<br />\n\n'%(song.getTitle(), song.getArtist()))
class pluginTabs(Plugin):
- o=None
- def __init__(self, winMain):
- Plugin.__init__(self, winMain, 'Tabs')
- self.addMontyListener('onSongChange', self.refresh)
- self.addMontyListener('onReady', self.refresh)
- self.addMontyListener('onDisconnect', self.onDisconnect)
- def _load(self):
- self.o=wgTabs(self, None)
- self.refresh(None)
- def _unload(self):
- self.o=None
- def getInfo(self):
- return "Show (and fetch) the tabs of the currently playing song."
-
- def _getDockWidget(self):
- return self._createDock(self.o)
-
- def refresh(self, params):
- self.o.refresh()
- def onDisconnect(self, params):
- self.o.resetTxt()
+ o=None
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Tabs')
+ self.addMontyListener('onSongChange', self.refresh)
+ self.addMontyListener('onReady', self.refresh)
+ self.addMontyListener('onDisconnect', self.onDisconnect)
+ def _load(self):
+ self.o=wgTabs(self, None)
+ self.refresh(None)
+ def _unload(self):
+ self.o=None
+ def getInfo(self):
+ return "Show (and fetch) the tabs of the currently playing song."
+
+ def _getDockWidget(self):
+ return self._createDock(self.o)
+
+ def refresh(self, params):
+ self.o.refresh()
+ def onDisconnect(self, params):
+ self.o.resetTxt()
- def _getSettings(self):
- sites=QtGui.QTextEdit()
- sites.insertPlainText(self.getSetting('sites'))
- return [
- ['engine', 'Search engine', 'The URL that is used to search. $artist, $title and $album are replaced in the URL.', QtGui.QLineEdit(self.getSetting('engine'))],
- ['sites', 'Sites & regexes', 'This field contains all sites, together with the regex needed to fetch the tabs.\nEvery line must look like this: $domain $regex-start(.*?)$regex-end\n$domain is the domain of the tabs website, $regex-start is the regex indicating the start of the tabs, $regex-end indicates the end. E.g. footabs.org <tabs>(.*?)</tabs>', sites],
- ['dir', 'Tabs directory', 'Directory where tabs should be stored and retrieved.', QtGui.QLineEdit(self.getSetting('dir'))],
- ]
- def afterSaveSettings(self):
- self.o.onSongChange(None)
+ def _getSettings(self):
+ sites=QtGui.QTextEdit()
+ sites.insertPlainText(self.getSetting('sites'))
+ return [
+ ['engine', 'Search engine', 'The URL that is used to search. $artist, $title and $album are replaced in the URL.', QtGui.QLineEdit(self.getSetting('engine'))],
+ ['sites', 'Sites & regexes', 'This field contains all sites, together with the regex needed to fetch the tabs.\nEvery line must look like this: $domain $regex-start(.*?)$regex-end\n$domain is the domain of the tabs website, $regex-start is the regex indicating the start of the tabs, $regex-end indicates the end. E.g. footabs.org <tabs>(.*?)</tabs>', sites],
+ ['dir', 'Tabs directory', 'Directory where tabs should be stored and retrieved.', QtGui.QLineEdit(self.getSetting('dir'))],
+ ]
+ def afterSaveSettings(self):
+ self.o.onSongChange(None)
diff --git a/plugins/__init__.py b/plugins/__init__.py
index aa940bc..0673752 100644
--- a/plugins/__init__.py
+++ b/plugins/__init__.py
@@ -11,69 +11,69 @@ PLUGIN_MSG=3
def loadPlugins():
- """(Re)load all modules in the plugins directory."""
- global _plugins
- _plugins={}
- for file in os.listdir('plugins'):
- if file[-3:]=='.py' and file!='__init__.py':
- name=file[:-3] # name without ext
- mod='plugins.%s'%(name) # mod name
- className='plugin%s'%(name) # classname
-
- _plugins[className.lower()]=[mod, className, None, None]
- loadPlugin(className, None)
+ """(Re)load all modules in the plugins directory."""
+ global _plugins
+ _plugins={}
+ for file in os.listdir('plugins'):
+ if file[-3:]=='.py' and file!='__init__.py':
+ name=file[:-3] # name without ext
+ mod='plugins.%s'%(name) # mod name
+ className='plugin%s'%(name) # classname
+
+ _plugins[className.lower()]=[mod, className, None, None]
+ loadPlugin(className, None)
def getPlugin(name):
- global _plugins
- try:
- return _plugins[name.lower()][PLUGIN_INSTANCE]
- except:
- try:
- return _plugins["plugin%s"%(name.lower())][PLUGIN_INSTANCE]
- except:
- return None
-
+ global _plugins
+ try:
+ return _plugins[name.lower()][PLUGIN_INSTANCE]
+ except:
+ try:
+ return _plugins["plugin%s"%(name.lower())][PLUGIN_INSTANCE]
+ except:
+ return None
+
def loadPlugin(className, parent):
- """Constructs a plugin."""
- global _plugins
- entry=_plugins[className.lower()]
- mod=entry[PLUGIN_MODULE]
- # ensure we get the latest version
- try:
- try:
- sys.modules[mod]
- reimport=True
- except:
- reimport=False
-
- if reimport:
- reload(sys.modules[mod])
- else:
- module=__import__(mod, globals(), locals(), className, -1)
-
- except Exception, e:
- _plugins[className.lower()][PLUGIN_MSG]=str(e)
- _plugins[className.lower()][PLUGIN_INSTANCE]=None
- log.important("Failed to load plugin %s: %s %s"%(className, str(type(e)), str(e)))
- return None
-
- module=sys.modules[mod]
- _plugins[className.lower()][PLUGIN_MSG]=None
-
- if parent:
- # instantiate the plugin
- _plugins[className.lower()][PLUGIN_INSTANCE]=module.__dict__[className](parent)
- else:
- _plugins[className.lower()][PLUGIN_INSTANCE]=None
- return _plugins[className.lower()][PLUGIN_INSTANCE]
-
+ """Constructs a plugin."""
+ global _plugins
+ entry=_plugins[className.lower()]
+ mod=entry[PLUGIN_MODULE]
+ # ensure we get the latest version
+ try:
+ try:
+ sys.modules[mod]
+ reimport=True
+ except:
+ reimport=False
+
+ if reimport:
+ reload(sys.modules[mod])
+ else:
+ module=__import__(mod, globals(), locals(), className, -1)
+
+ except Exception, e:
+ _plugins[className.lower()][PLUGIN_MSG]=str(e)
+ _plugins[className.lower()][PLUGIN_INSTANCE]=None
+ log.important("Failed to load plugin %s: %s %s"%(className, str(type(e)), str(e)))
+ return None
+
+ module=sys.modules[mod]
+ _plugins[className.lower()][PLUGIN_MSG]=None
+
+ if parent:
+ # instantiate the plugin
+ _plugins[className.lower()][PLUGIN_INSTANCE]=module.__dict__[className](parent)
+ else:
+ _plugins[className.lower()][PLUGIN_INSTANCE]=None
+ return _plugins[className.lower()][PLUGIN_INSTANCE]
+
def listPlugins():
- """Get the list of plugins available as { className => [mod, className, instance, msg] }."""
- global _plugins
- return _plugins
+ """Get the list of plugins available as { className => [mod, className, instance, msg] }."""
+ global _plugins
+ return _plugins
loadPlugins()
diff --git a/wgPlaylist.py b/wgPlaylist.py
index 13c4790..587be87 100644
--- a/wgPlaylist.py
+++ b/wgPlaylist.py
@@ -8,128 +8,128 @@ from clSettings import settings
class Playlist(QtGui.QWidget):
- """The Playlist widget is a list optimized for displaying a playlist, with filtering."""
- name=None # name of this playlist
- txtFilter=None # filter input
- btnClearFilter=None
- cmbMode=None # what mode? library, playlist?
- modes=[]
- lstSongs=None # list
- winMain=None # the main window
- onKeyPress=None
- _timerID=None
+ """The Playlist widget is a list optimized for displaying a playlist, with filtering."""
+ name=None # name of this playlist
+ txtFilter=None # filter input
+ btnClearFilter=None
+ cmbMode=None # what mode? library, playlist?
+ modes=[]
+ lstSongs=None # list
+ winMain=None # the main window
+ onKeyPress=None
+ _timerID=None
- def __init__(self, parent, wMain, headers, name, onDoubleClick, onKeyPress, modes):
- QtGui.QWidget.__init__(self, parent)
-
- self.name=name
- self.winMain=wMain
-
- self.onKeyPress=onKeyPress
-
- self.lstSongs=SongList(parent, self.name, headers, onDoubleClick)
-
- self.txtFilter=QtGui.QLineEdit()
- self.btnClearFilter=Button("Clear", self.clearTxtFilter)
-
- self.cmbMode=QtGui.QComboBox()
- self.setModes(modes)
-
- frame=QtGui.QVBoxLayout()
- filter=QtGui.QHBoxLayout()
- filter.addWidget(self.txtFilter)
- filter.addWidget(self.btnClearFilter)
- frame.addWidget(self.cmbMode)
- frame.addLayout(filter);
- frame.addWidget(self.lstSongs)
- self.setLayout(frame)
-
- self.connect(self.txtFilter, QtCore.SIGNAL('textChanged(const QString&)'), self.txtFilterChange)
- self.connect(self.txtFilter, QtCore.SIGNAL('editingFinished()'), self.txtFilterQuit)
- self.connect(self.cmbMode, QtCore.SIGNAL('currentIndexChanged(const QString&)'), self.onModeChangeChange)
-
- def clearTxtFilter(self, event):
- self.txtFilter.setText("")
-
- _prevTxtFilter=None
- def timerEvent(self, event):
- if self.txtFilter.text()!=self._prevTxtFilter:
- # only filter after a little while
- self.killTimer(self._timerID)
- self._timerID=None
- self.filter()
- def keyPressEvent(self, event):
- self.onKeyPress(event)
- def txtFilterChange(self):
- if self._timerID==None:
- # only start filtering after a little while
- self._timerID=self.startTimer(500)
- def txtFilterQuit(self):
- if self._timerID:
- self.killTimer(self._timerID)
- self._timerID=None
- def updateSongs(self, songs):
- """Update the list of songs."""
- start_new_thread(self._update, (songs,))
- def _update(self, songs):
- self.lstSongs.updateSongs(songs)
- if len(self.txtFilter.text()):
- self.filter()
-
- def setMode(self, mode, levels=''):
- """Set the mode of showing the list."""
- if mode=='playlist':
- self.cmbMode.setCurrentIndex(0)
- else:
- self.cmbMode.setCurrentIndex(self.cmbMode.findText(levels))
- def selectedSongs(self):
- """Get the selected songs."""
- return self.lstSongs.selectedSongs()
- def selectedIds(self):
- """Get the selected songIDs."""
- return map(lambda song: song._data['id'], self.lstSongs.selectedSongs())
- def getSelItemID(self):
- """Get the id of the first selected song."""
- try:
- return self.lstSongs.selectedSongs()[0].getID()
- except:
- return -1
+ def __init__(self, parent, wMain, headers, name, onDoubleClick, onKeyPress, modes):
+ QtGui.QWidget.__init__(self, parent)
+
+ self.name=name
+ self.winMain=wMain
+
+ self.onKeyPress=onKeyPress
+
+ self.lstSongs=SongList(parent, self.name, headers, onDoubleClick)
+
+ self.txtFilter=QtGui.QLineEdit()
+ self.btnClearFilter=Button("Clear", self.clearTxtFilter)
+
+ self.cmbMode=QtGui.QComboBox()
+ self.setModes(modes)
+
+ frame=QtGui.QVBoxLayout()
+ filter=QtGui.QHBoxLayout()
+ filter.addWidget(self.txtFilter)
+ filter.addWidget(self.btnClearFilter)
+ frame.addWidget(self.cmbMode)
+ frame.addLayout(filter);
+ frame.addWidget(self.lstSongs)
+ self.setLayout(frame)
+
+ self.connect(self.txtFilter, QtCore.SIGNAL('textChanged(const QString&)'), self.txtFilterChange)
+ self.connect(self.txtFilter, QtCore.SIGNAL('editingFinished()'), self.txtFilterQuit)
+ self.connect(self.cmbMode, QtCore.SIGNAL('currentIndexChanged(const QString&)'), self.onModeChangeChange)
+
+ def clearTxtFilter(self, event):
+ self.txtFilter.setText("")
+
+ _prevTxtFilter=None
+ def timerEvent(self, event):
+ if self.txtFilter.text()!=self._prevTxtFilter:
+ # only filter after a little while
+ self.killTimer(self._timerID)
+ self._timerID=None
+ self.filter()
+ def keyPressEvent(self, event):
+ self.onKeyPress(event)
+ def txtFilterChange(self):
+ if self._timerID==None:
+ # only start filtering after a little while
+ self._timerID=self.startTimer(500)
+ def txtFilterQuit(self):
+ if self._timerID:
+ self.killTimer(self._timerID)
+ self._timerID=None
+ def updateSongs(self, songs):
+ """Update the list of songs."""
+ start_new_thread(self._update, (songs,))
+ def _update(self, songs):
+ self.lstSongs.updateSongs(songs)
+ if len(self.txtFilter.text()):
+ self.filter()
+
+ def setMode(self, mode, levels=''):
+ """Set the mode of showing the list."""
+ if mode=='playlist':
+ self.cmbMode.setCurrentIndex(0)
+ else:
+ self.cmbMode.setCurrentIndex(self.cmbMode.findText(levels))
+ def selectedSongs(self):
+ """Get the selected songs."""
+ return self.lstSongs.selectedSongs()
+ def selectedIds(self):
+ """Get the selected songIDs."""
+ return map(lambda song: song._data['id'], self.lstSongs.selectedSongs())
+ def getSelItemID(self):
+ """Get the id of the first selected song."""
+ try:
+ return self.lstSongs.selectedSongs()[0].getID()
+ except:
+ return -1
- def filter(self):
- """Filter the songs according to the inputbox."""
- self.lstSongs.killFilters()
- start_new_thread(self.lstSongs.filter, (str(self.txtFilter.text()).strip().lower(), ))
-
- def colorID(self, id, clr):
- """Color song with ID $id with color $clr."""
- self.lstSongs.colorID(id, clr)
-
- def onModeChangeChange(self, value):
- if value=='playlist':
- self.lstSongs.setMode('playlist')
- else:
- self.lstSongs.setMode('library', self.modes[self.cmbMode.currentIndex()])
- settings.set("l%s.mode"%(self.name), self.cmbMode.currentIndex())
- self.filter()
+ def filter(self):
+ """Filter the songs according to the inputbox."""
+ self.lstSongs.killFilters()
+ start_new_thread(self.lstSongs.filter, (str(self.txtFilter.text()).strip().lower(), ))
+
+ def colorID(self, id, clr):
+ """Color song with ID $id with color $clr."""
+ self.lstSongs.colorID(id, clr)
+
+ def onModeChangeChange(self, value):
+ if value=='playlist':
+ self.lstSongs.setMode('playlist')
+ else:
+ self.lstSongs.setMode('library', self.modes[self.cmbMode.currentIndex()])
+ settings.set("l%s.mode"%(self.name), self.cmbMode.currentIndex())
+ self.filter()
- def showColumn(self, column, show=True):
- """Show or hide column $column."""
- self.lstSongs.showColumn(column, show)
- def selectRow(self, row):
- """Select row $row."""
- self.lstSongs.selectRow(row)
- def ensureVisible(self, id):
- """Make sure song with ID $id is visible."""
- self.lstSongs.ensureVisible(id)
+ def showColumn(self, column, show=True):
+ """Show or hide column $column."""
+ self.lstSongs.showColumn(column, show)
+ def selectRow(self, row):
+ """Select row $row."""
+ self.lstSongs.selectRow(row)
+ def ensureVisible(self, id):
+ """Make sure song with ID $id is visible."""
+ self.lstSongs.ensureVisible(id)
- def setModes(self, modes):
- i=int(settings.get("l%s.mode"%(self.name), 0))
- self.modes=['playlist']
- self.modes.extend(modes)
- self.cmbMode.clear()
- self.cmbMode.addItem('playlist')
- for mode in modes:
- if mode:
- self.cmbMode.addItem(mode.replace('$', ''))
- self.cmbMode.setCurrentIndex(i)
- self.onModeChangeChange(str(self.cmbMode.itemText(i)))
+ def setModes(self, modes):
+ i=int(settings.get("l%s.mode"%(self.name), 0))
+ self.modes=['playlist']
+ self.modes.extend(modes)
+ self.cmbMode.clear()
+ self.cmbMode.addItem('playlist')
+ for mode in modes:
+ if mode:
+ self.cmbMode.addItem(mode.replace('$', ''))
+ self.cmbMode.setCurrentIndex(i)
+ self.onModeChangeChange(str(self.cmbMode.itemText(i)))
diff --git a/wgSongList.py b/wgSongList.py
index da9d842..f06791a 100644
--- a/wgSongList.py
+++ b/wgSongList.py
@@ -14,7 +14,7 @@ LIB_ROW=0
LIB_VALUE=1
LIB_INDENT=2
LIB_NEXTROW=3
-LIB_EXPANDED=4 # values: 0, 1 or 2 (==song)
+LIB_EXPANDED=4 # values: 0, 1 or 2 (==song)
LIB_PARENT=5
clrRowSel=QtGui.QColor(180,180,180)
@@ -22,935 +22,935 @@ clrRowSel=QtGui.QColor(180,180,180)
class DoUpdate(QtCore.QEvent):
- def __init__(self):
- QtCore.QEvent.__init__(self, QtCore.QEvent.User)
+ def __init__(self):
+ QtCore.QEvent.__init__(self, QtCore.QEvent.User)
class DoResize(QtCore.QEvent):
- def __init__(self):
- QtCore.QEvent.__init__(self, QtCore.QEvent.User)
+ def __init__(self):
+ QtCore.QEvent.__init__(self, QtCore.QEvent.User)
class SongList(QtGui.QWidget):
- """The SongList widget is a list optimized for displaying an array of songs, with filtering option."""
- # CONFIGURATION VARIABLES
- " height of line in pxl"
- lineHeight=30
- " font size in pxl"
- fontSize=16
- " margin"
- margin=4
- vmargin=(lineHeight-fontSize)/2-1
- " width of the vscrollbar"
- scrollbarWidth=15
- " minimum column width"
- minColumnWidth=50
- " colors for alternating rows"
- colors=[QtGui.QColor(255,255,255), QtGui.QColor(230,230,255)]
- " color of selection"
- clrSel=QtGui.QColor(100,100,180)
- " background color"
- clrBg=QtGui.QColor(255,255,255)
- " indentation of hierarchy, in pixels"
- indentation=lineHeight
-
- " what function to call when the list is double clicked"
- onDoubleClick=None
-
- mode='playlist' # what mode is the songlist in? values: 'playlist', 'library'
- " the headers: ( (header, width, visible)+ )"
- headers=None
- songs=None # original songs
- numSongs=None # number of songs
-
- # 'edited' songs
- # in playlist mode, this can only filtering
- # in library mode, this indicates all entries: (row, tag-value, indentation, next-row, expanded)*
- fSongs=None # filtered songs
- numVisEntries=None # number of entries that are visible (including when scrolling)
-
-
- levels=[] # levels from the groupBy in library-mode
- groupByStr='' # groupBy used in library-mode
-
- vScrollbar=None
- hScrollbar=None
-
- topRow=-1
- numRows=-1 # total number of rows that can be visible in 1 time
- selRows=None # ranges of selected rows: ( (startROw,endRow)* )
- selIDs=None # ranges of selected IDs: [ [startID,endID] ]
- selMiscs=None # array of indexes for selected non-songs in library mode
-
- selMode=False # currently in select mode?
- resizeCol=None # resizing a column?
- clrID=None # do we have to color a row with certain ID? [ID, color]
- scrollMult=1 # how many rows do we jump when scrolling by dragging
- xOffset=0 # offset for drawing. Is changed by hScrollbar
- resizeColumn=None # indicates this column should be recalculated
- redrawID=None # redraw this ID/row only
-
- wgGfxAlbum=QtSvg.QSvgRenderer('gfx/gnome-cd.svg')
- wgGfxArtist=QtSvg.QSvgRenderer('gfx/user_icon.svg')
-
-
- def __init__(self, parent, name, headers, onDoubleClick):
- QtGui.QWidget.__init__(self, parent)
- self.onDoubleClick=onDoubleClick
-
- # we receive an array of strings; we convert that to an array of (header, width)
- self.name=name
- # load the headers, and fetch from the settings the width and visibility
- self.headers=map(lambda h: [h, int(settings.get('l%s.%s.width'%(self.name,h),250))
- , settings.get('l%s.%s.visible'%(self.name,h),'1')=='1'], headers)
- self.headers.insert(0, ['id', 30, settings.get('l%s.%s.visible'%(self.name,'id'),'0')=='1'])
- self.songs=None
- self.numSongs=None
- self.fSongs=None
- self.selMiscs=[]
- self.numVisEntries=None
- self.xOffset=0
- self.resizeColumn=None
-
- self._filters=[]
-
- self.vScrollbar=QtGui.QScrollBar(QtCore.Qt.Vertical, self)
- self.vScrollbar.setMinimum(0)
- self.vScrollbar.setMaximum(1)
- self.vScrollbar.setValue(0)
-
- self.hScrollbar=QtGui.QScrollBar(QtCore.Qt.Horizontal, self)
- self.hScrollbar.setMinimum(0)
- self.hScrollbar.setMaximum(1)
- self.hScrollbar.setValue(0)
- self.hScrollbar.setPageStep(200)
-
- self.topRow=0
- self.numRows=0
- self.selRows=[]
- self.selMode=False
- self.clrID=[-1,0]
-
- self.updateSongs([])
- doEvents()
-
- self.connect(self.vScrollbar, QtCore.SIGNAL('valueChanged(int)'),self.onVScroll)
- self.connect(self.hScrollbar, QtCore.SIGNAL('valueChanged(int)'),self.onHScroll)
-
- self.setMouseTracking(True)
- self.setFocusPolicy(QtCore.Qt.TabFocus or QtCore.Qt.ClickFocus
- or QtCore.Qt.StrongFocus or QtCore.Qt.WheelFocus)
-
- self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
- font=QtGui.QFont()
- font.setPixelSize(self.fontSize)
- font.setFamily('Comic Sans Ms')
- self.setFont(font)
-
- def sizeHint(self):
- return QtCore.QSize(10000,10000)
-
- def getSongs(self):
- return self.songs
-
- def customEvent(self, event):
- if isinstance(event, DoResize):
- self.resizeEvent(None)
- self.update()
- elif isinstance(event, DoUpdate):
- self.update()
- else:
- Logger.extended("wgSongList::Unknown event "+str(event))
-
- def setMode(self, mode, groupBy=''):
- self.selRows=[]
- self.selIDs=[]
- self.selMode=False
-
- if mode=='playlist':
- self.fSongs=self.songs
- self.numVisEntries=len(self.fSongs)
- elif mode=='library':
- self.groupBy(groupBy)
- else:
- raise Exception('Unknown mode %' %(mode))
-
- self.mode=mode
- QtCore.QCoreApplication.postEvent(self, DoResize())
-
- def groupBy(self, groupBy, strFilter=''):
- self.groupByStr=groupBy
- self.levels=groupBy.split('/')
- strFilter=strFilter.strip()
-
- l=[]
- formats=[]
- for i in xrange(0,len(self.levels)):
- formats.append(format.compile(self.levels[i]))
- # TODO also take l[1] etc into account?
- U="(Unknown)"
- xtra={"album":U, "artist":U, "date":"", "genre":U}
- compare=lambda left, right: cmp(\
- formats[0](format.params(left)).lower(), \
- formats[0](format.params(right)).lower() \
- )
-
- songs=self.songs
- if strFilter!='':
- songs=filter(lambda song: song.match(strFilter), songs)
- songs=sorted(songs, compare)
-
- numLevels=len(self.levels)
- self.fSongs=[[0, 'dummy', 0, -1, False]]
- row=0
- # four levels ought to be enough for everyone
- curLevels=[[None,0], [None,0], [None,0], [None,0]] # contains the values of current levels
- curLevel=0 # current level we're in
- parents=[-1,-1,-1,-1] # index of parent
- for song in songs:
- if not(self.songs):
- return
- for level in xrange(numLevels):
- # does the file have the required tag?
- if not formats[level](format.params(song, {}, xtra))==curLevels[level][LIB_ROW]:
- finalRow=row
- for i in xrange(level,numLevels):
- tagValue2=formats[i](format.params(song, {}, xtra))
-
- self.fSongs[curLevels[i][1]][LIB_NEXTROW]=finalRow
- self.fSongs.append([row, tagValue2, i, row+1, 0, parents[i]])
- parents[i+1]=row
-
- row+=1
- curLevels[i]=[tagValue2, row]
- curLevel=numLevels
- self.fSongs.append([row, song, curLevel, row+1, 2, parents[curLevel]])
- row+=1
-
- # update last entries' next-row of each level
- # If we have e.g. artist/album, then the last artist and last album of that
- # artist have to be repointed to the end of the list, else problems arise
- # showing those entries ...
- # indicate for each level whether we have processed that level yet
- processed=[False, False, False, False, False]
- numFSongs=len(self.fSongs)
- for i in xrange(numFSongs-1,0,-1):
- song=self.fSongs[i]
- # look for last top-level entry
- if song[LIB_INDENT]==0:
- song[LIB_NEXTROW]=numFSongs
- break
- if processed[song[LIB_INDENT]]==False:
- song[LIB_NEXTROW]=numFSongs
- processed[song[LIB_INDENT]]=True
-
-
- # remove the dummy
- self.fSongs.pop(0)
-
- self.numVisEntries=len(filter(lambda entry: entry[LIB_INDENT]==0, self.fSongs))
-
- def updateSongs(self, songs):
- """Update the displayed songs and clears selection."""
- self.songs=songs
- self.numSongs=len(songs)
-
- self.setMode(self.mode, self.groupByStr)
-
- self.redrawID=None
- QtCore.QCoreApplication.postEvent(self, DoUpdate())
-
- def selectedSongs(self):
- """Returns the list of selected songs."""
- ret=[]
- if self.mode=='playlist':
- cmp=lambda song: song._data['id']>=range[0] and song._data['id']<=range[1]
- elif self.mode=='library':
- cmp=lambda song: song._data['id']>=range[0] and song._data['id']<=range[1]
- for range in self.selIDs:
- # look for the songs in the current range
- songs=filter(cmp, self.songs)
- # add songs in range
- ret.extend(songs)
- return ret
-
- def killFilters(self):
- songs=self.songs
- self.songs=None
- while (len(self._filters)):
- # wait 'till everything's cleared
- doEvents()
- self.songs=songs
-
-
- # contains filters ready to be applied; only the top one will be used
- _filters=[]
- def filter(self, strFilter):
- """Filter songs according to $strFilter."""
-
- self._filters.append(strFilter)
- strFilter=self._filters[-1]
-
- try:
- if self.mode=='playlist':
- self.fSongs=filter(lambda song: song.match(strFilter), self.songs)
- self.numVisEntries=len(self.fSongs)
- else:
- self.groupBy(self.groupByStr, strFilter)
- except:
- # we might get here because self.songs is None
- pass
- self._filters=[]
- QtCore.QCoreApplication.postEvent(self, DoResize())
-
- def colorID(self, id, clr):
- """Color the row which contains song with id $id $clr."""
- self.clrID=[id, clr]
- self.redrawID=id
-
- QtCore.QCoreApplication.postEvent(self, DoUpdate())
-
- def selectRow(self, row):
- """Make $row the current selection."""
- self.selRows=[[row,row]]
-
- QtCore.QCoreApplication.postEvent(self, DoUpdate())
-
- def showColumn(self, column, show=True):
- """Hide or show column $column."""
- self.headers[column][2]=show
-
- self.update()
-
- def autoSizeColumn(self, column):
- """Resizes column $column to fit the widest entry in the non-filtered songs."""
- # we can't calculate it here, as retrieving the text-width can only
- # be done in the paintEvent method ...
- self.resizeColumn=column
-
- QtCore.QCoreApplication.postEvent(self, DoUpdate())
-
- def visibleSongs(self):
- """Get the songs currently visible."""
- ret=[]
- if self.mode=='playlist':
- for row in xrange(self.topRow, min(self.numSongs, self.topRow+self.numRows)-1):
- ret.append(self.fSongs[row])
- elif self.mode=='library':
- # note that if everything is folded, there'll be no songs!
- entries=self.fSongs
- index=self.libFirstVisRowIndex()
- count=0
- while index>=0 and index<len(entries) and count<self.numRows:
- entry=self.fSongs[index]
- if isinstance(entry[LIB_VALUE], Song):
- ret.append(entry[LIB_VALUE])
- index=self.libIthVisRowIndex(index)
- count+=1
- return ret
-
- def ensureVisible(self, id):
- """Make sure the song with $id is visible."""
- if len(filter(lambda song: song.getID()==id, self.visibleSongs())):
- return
- row=0
- ok=False
- if self.mode=='playlist':
- # playlist mode is simple: just hop to the song with id!
- for song in self.fSongs:
- row+=1
- if song.getID()==id:
- ok=True
- break
- elif self.mode=='library':
- # library mode is more complex: we must find out how many rows are visible,
- # and expand the parents of the song, if necessary
- indLevel=0 # indicates what is the deepest level that is expanded for the current entry
- # thus if current indent<=indLevel, then it is visible
- for entry in self.fSongs:
- if entry[LIB_EXPANDED]==1:
- indLevel=max(entry[LIB_INDENT]+1, indLevel)
- elif entry[LIB_EXPANDED]==0:
- indLevel=min(entry[LIB_INDENT], indLevel)
- if entry[LIB_INDENT]<=indLevel:
- row+=1
-
- #print "%s -> %s"%(str(indLevel), str(entry))
- if isinstance(entry[LIB_VALUE], Song) and entry[LIB_VALUE].getID()==id:
- # expand parents
- # must be expanded in reverse order, else we will count too many
- # entries ...
- parents=[]
- while entry[LIB_PARENT]>=0:
- entry=self.fSongs[entry[LIB_PARENT]]
- parents.append(entry)
- row-=1
- parents.reverse()
- for parent in parents:
- self.libExpand(parent)
- ok=True
- break
-
- if ok:
- self.vScrollbar.setValue(row-self.numRows/2)
- self.update()
-
-
- def onVScroll(self, value):
- # 'if value<0' needed because minimum can be after init <0 at some point ...
- if value<0: value=0
- if value>self.numVisEntries:value=self.numVisEntries
- self.topRow=value
-
- self.update()
-
- def onHScroll(self, value):
- self.xOffset=-self.hScrollbar.value()*2
- self.update()
-
- def _pos2row(self, pos):
- return int(pos.y()/self.lineHeight)-1
- def _row2entry(self, row):
- entry=self.fSongs[0]
- try:
- while row>0:
- if entry[LIB_EXPANDED]:
- entry=self.fSongs[entry[LIB_ROW]+1]
- else:
- entry=self.fSongs[entry[LIB_NEXTROW]]
- row-=1
- except:
- return None
- return entry
-
- def focusOutEvent(self, event):
- self.update()
- def focusInEvent(self, event):
- self.update()
- def wheelEvent(self, event):
- if self.vScrollbar.isVisible():
- event.accept()
- numDegrees=event.delta() / 8
- numSteps=5*numDegrees/15
- self.vScrollbar.setValue(self.vScrollbar.value()-numSteps)
-
- def resizeEvent(self, event):
- # max nr of rows shown
- self.numRows=int(self.height()/self.lineHeight)
-
- # check vertical scrollbar
- if self.numRows>self.numVisEntries:
- self.vScrollbar.setVisible(False)
- self.vScrollbar.setValue(0)
- else:
- self.vScrollbar.setVisible(True)
- self.vScrollbar.setPageStep(self.numRows-2)
- self.vScrollbar.setMinimum(0)
- self.vScrollbar.setMaximum(self.numVisEntries-self.numRows+1)
- self.vScrollbar.resize(self.scrollbarWidth, self.height()-self.lineHeight-1)
- self.vScrollbar.move(self.width()-self.vScrollbar.width()-1, self.lineHeight-1)
-
- # check horizontal scrollbar
- self.scrollWidth=0
- if self.mode=='playlist':
- for hdr in self.headers:
- if hdr[2]:
- self.scrollWidth+=hdr[1]
-
- if self.scrollWidth>self.width():
- self.hScrollbar.setVisible(True)
- self.hScrollbar.setMinimum(0)
- self.hScrollbar.setMaximum((self.scrollWidth-self.width())/2)
- self.hScrollbar.resize(self.width()-4, self.scrollbarWidth)
- self.hScrollbar.move(2, self.height()-self.hScrollbar.height()-1)
-
- # some changes because the hScrollbar takes some vertical space ...
- self.vScrollbar.resize(self.vScrollbar.width(), self.vScrollbar.height()-self.lineHeight)
- self.vScrollbar.setMaximum(self.vScrollbar.maximum()+1)
-
- self.numRows-=1
- else:
- self.hScrollbar.setVisible(False)
- self.hScrollbar.setValue(0)
-
- def libExpand(self, entry):
- if entry and entry[LIB_EXPANDED]==0:
- self.libToggle(entry)
- def libCollapse(self, entry):
- if entry and entry[LIB_EXPANDED]==1:
- self.libToggle(entry)
-
- def libToggle(self, entry):
- """Toggles expanded state. Returns new state"""
- expanded=entry[LIB_EXPANDED]
- if expanded!=2:
- # there was a '+' or a '-'!
- entry[LIB_EXPANDED]=(expanded+1)%2
- ret=entry[LIB_EXPANDED]
- # we must find out how many entries have appeared/disappeard
- # while collapsing.
- visibles=0 # how many new elements have appeared?
- i=entry[LIB_ROW]+1 # current element looking at
- indLevel=self.fSongs[i][LIB_INDENT]
- while i<=entry[LIB_NEXTROW]-1 and i<len(self.fSongs):
- entry2=self.fSongs[i]
- if entry2[LIB_EXPANDED]==1:
- indLevel=max(entry2[LIB_INDENT]+1, indLevel)
- elif entry2[LIB_EXPANDED]==0:
- indLevel=min(entry2[LIB_INDENT], indLevel)
- if entry2[LIB_INDENT]<=indLevel:
- visibles+=1
-
- if entry2[LIB_EXPANDED]==0:
- i=entry2[LIB_NEXTROW]
- else:
- i+=1
-
- mult=1
- if ret==0: mult=-1
- self.numVisEntries+=mult*visibles
- self.resizeEvent(None)
- return ret
-
- return 2
-
- def mousePressEvent(self, event):
- self.setFocus()
- pos=event.pos()
- row=self._pos2row(pos)
-
- done=False # indicates whether some action has been done or not
- if self.mode=='playlist':
- self.scrollMult=1
- if row==-1:
- # we're clicking in the header!
- self.resizeCol=None
- x=0+self.xOffset
- i=0
- # check if we're clicking between two columns, if so: resize mode!
- for hdr in self.headers:
- if hdr[2]:
- x+=hdr[1]
- if abs(x-pos.x())<4:
- self.resizeCol=i
- done=True
- i+=1
- elif self.mode=='library':
- entry=self._row2entry(row+self.topRow)
- if not entry:
- entry=self.fSongs[len(self.fSongs)-1]
- if entry and pos.x()>(1+entry[LIB_INDENT])*self.indentation \
- and pos.x()<(1+entry[LIB_INDENT]+3/2)*self.indentation:
- # we clicked in the margin, to expand or collapse
- self.libToggle(entry)
- done=True
-
- if done==False:
- self.selMode=True
- self.selIDs=[]
- self.selMiscs=[]
- if row==-1 and self.resizeCol==None:
- # we're not resizing, thus we can select all!
- self.selRows=[[0, len(self.fSongs)]]
- elif row>=0:
- # we start selection mode
- if self.mode=='playlist':
- self.selRows=[[self.topRow+row,self.topRow+row]]
- elif self.mode=='library':
- self.selRows=[[entry[LIB_ROW], entry[LIB_NEXTROW]-1]]
- self.selMode=True
-
- self.update()
-
- def mouseMoveEvent(self, event):
- pos=event.pos()
- row=self._pos2row(pos)
- if self.selMode:
- # we're in selection mode
- if row<0:
- # scroll automatically when going out of the widget
- row=0
- if self.topRow>0:
- self.scrollMult+=0.1
- jump=int(self.scrollMult)*int(abs(pos.y())/self.lineHeight)
- self.vScrollbar.setValue(self.vScrollbar.value()-jump)
- row=jump
- elif row>=self.numRows:
- # scroll automatically when going out of the widget
- self.scrollMult+=0.1
- jump=int(self.scrollMult)*int(abs(self.height()-pos.y())/self.lineHeight)
- self.vScrollbar.setValue(self.vScrollbar.value()+jump)
- row=self.numRows-jump
- else:
- # reset the scrollMultiplier
- self.scrollMult=1
-
- if self.mode=='playlist':
- self.selRows[0][1]=row+self.topRow
- elif self.mode=='library':
- self.selRows[0][1]=self.libIthVisRowIndex(self.libIthVisRowIndex(0,self.topRow), row)
- self.update()
- elif self.resizeCol!=None:
- row-=1
- # ohla, we're resizing a column!
- prev=0
- # calculate where we are
- for i in xrange(self.resizeCol):
- hdr=self.headers[i]
- if hdr[2]:
- prev+=hdr[1]
- self.headers[self.resizeCol][1]=pos.x()-prev-self.xOffset
- # minimum width check?
- if self.headers[self.resizeCol][1]<self.minColumnWidth:
- self.headers[self.resizeCol][1]=self.minColumnWidth
- self.resizeEvent(None)
- self.update()
-
- def mouseReleaseEvent(self, event):
- if self.selMode and len(self.selRows):
- # we were selecting, but now we're done.
- # We have to transform one range of rows
- # into range of selected IDs
- # The problem is that the list can be filtered, and that
- # consequtive, visible rows aren't always directly
- # consequtive in the unfiltered list.
- self.selMode=False # exit selection mode
- fSongs=self.fSongs
- self.selMiscs=[]
- ranges=[]
- curRange=[]
- # loop over all rows that are selected
- for entry in fSongs[min(self.selRows[0]):max(self.selRows[0])+1]:
- song=None
- if isinstance(entry,Song):
- song=entry
- elif isinstance(entry[LIB_VALUE],Song):
- song=entry[LIB_VALUE]
- else:
- self.selMiscs.append(entry[LIB_ROW])
-
- if song!=None:
- id=song.getID()
- # is this song directly after the previous row?
- if len(curRange)==0 or curRange[-1]+1==id:
- curRange.append(id)
- else:
- ranges.append(curRange)
- curRange=[id]
- if len(curRange):
- ranges.append(curRange)
- # clean up ranges
- self.selRows=[]
- self.selIDs=[]
- for range in ranges:
- self.selIDs.append([range[0], range[-1]])
- self.update()
-
- elif self.resizeCol!=None:
- # store this new width!
- self.saveColumnWidth(self.resizeCol)
- # we're not resizing anymore!
- self.resizeCol=None
- self.update()
- def saveColumnWidth(self, col):
- settings.set('l%s.%s.width'%(self.name,self.headers[col][0]), self.headers[col][1])
- def mouseDoubleClickEvent(self, event):
- pos=event.pos()
- row=self._pos2row(pos)
- if row>=0:
- self.onDoubleClick()
- else:
- # auto-size column
- x=0+self.xOffset
- i=0
- for hdr in self.headers:
- if hdr[2]:
- x+=hdr[1]
- if abs(x-pos.x())<4:
- self.autoSizeColumn(i)
- break
- i+=1
-
- def _paintPlaylist(self, p):
- self.redrawID=None
-
-
- lineHeight=self.lineHeight
- margin=self.margin
- vmargin=self.vmargin
- selRows=self.selRows
- width=self.width()
- if self.vScrollbar.isVisible():
- width-=self.scrollbarWidth
-
- if self.resizeColumn!=None:
- # we're autoresizing!
- # must be done here, because only here we can check the textwidth!
- # This is because of limitations it can be only be done in paintEvent
- hdr=self.headers[self.resizeColumn][0]
- w=self.minColumnWidth
- # loop over all visible songs ...
- for song in self.fSongs:
- rect=p.boundingRect(10,10,1,1, QtCore.Qt.AlignLeft, song.getTag(hdr))
- w=max(rect.width(), w)
- self.headers[self.resizeColumn][1]=w+2*margin
- self.saveColumnWidth(self.resizeColumn)
- self.resizeColumn=None
- self.resizeEvent(None)
- if self.redrawID!=None:
- # only update one row
- y=lineHeight
- for row in xrange(self.topRow, min(self.numVisEntries, self.topRow+self.numRows)):
- if self.fSongs[row]._data['id']==self.redrawID:
- self._paintPlaylistRow(p, row, y, width)
- y+=lineHeight
-
- self.redrawID=None
- return
-
- # paint the headers!
- p.fillRect(QtCore.QRect(0,0,width+self.vScrollbar.width(),lineHeight), QtGui.QBrush(QtCore.Qt.lightGray))
- p.drawRect(QtCore.QRect(0,0,width+self.vScrollbar.width()-1,lineHeight-1))
- x=margin+self.xOffset
- for hdr in self.headers:
- if hdr[2]:
- p.drawText(x, vmargin, hdr[1], lineHeight, QtCore.Qt.AlignLeft, hdr[0])
- x+=hdr[1]
- p.drawLine(QtCore.QPoint(x-margin,0), QtCore.QPoint(x-margin,lineHeight))
-
- if self.songs==None:
- return
- # fill the records!
- y=lineHeight
- for row in xrange(self.topRow, min(self.numVisEntries, self.topRow+self.numRows)):
- self._paintPlaylistRow(p, row, y, width)
- y+=lineHeight
- if y<self.height():
- # if we're short on songs, draw up the remaining area in background color
- p.fillRect(QtCore.QRect(0,y,width,self.height()-y), QtGui.QBrush(self.clrBg))
-
- def _paintPlaylistRow(self, p, row, y, width):
- """Paint row $row on $p on height $y and with width $width."""
- song=self.fSongs[row]
- lineHeight=self.lineHeight
- margin=self.margin
- vmargin=self.vmargin
- id=song._data['id']
-
- # determine color of row. Default is row-color, but can be overridden by
- # (in this order): selection, special row color!
- clr=self.colors[row%2] # background color of the row
- clrTxt=QtCore.Qt.black # color of the printed text
- # is it selected?
- values=[]
- if self.selMode:
- checkID=row
- values=self.selRows
- else:
- checkID=id
- values=self.selIDs
- # if values==[], it won't run!
- for range in values:
- # is selected if in range, which depends on the selection-mode
- if checkID>=min(range) and checkID<=max(range):
- clr=self.clrSel
- clrTxt=QtCore.Qt.white
- # it has a VIP-status!
- if id==int(self.clrID[0]):
- clrTxt=QtCore.Qt.white
- clr=self.clrID[1]
-
- # draw the row background
- p.fillRect(QtCore.QRect(2, y, width-3, lineHeight), QtGui.QBrush(clr))
-
- # draw a subtile rectangle
- p.setPen(QtGui.QColor(230,230,255))
- p.drawRect(QtCore.QRect(2, y, width-3, lineHeight))
-
- # Back To Black
- p.setPen(QtCore.Qt.black)
-
- # draw the column
- x=margin+self.xOffset
- for hdr in self.headers:
- if hdr[2]:
- # only if visible, duh!
- # rectangle we're allowed to print in
- text=song.getTag(hdr[0])
- if type(text)!=str and type(text)!=unicode:
- text=str(text)
- rect=p.boundingRect(x, y, hdr[1]-margin, lineHeight, QtCore.Qt.AlignLeft, text)
- p.setPen(clrTxt)
- p.drawText(x, y+vmargin, hdr[1]-margin, lineHeight, QtCore.Qt.AlignLeft, text)
- if rect.width()>hdr[1]-margin:
- # print ellipsis, if necessary
- p.fillRect(x+hdr[1]-15,y+1,15,lineHeight-1, QtGui.QBrush(clr))
- p.drawText(x+hdr[1]-15,y+vmargin,15,lineHeight-1, QtCore.Qt.AlignLeft, "...")
- x+=hdr[1]
- p.setPen(QtCore.Qt.black)
- p.drawLine(QtCore.QPoint(x-margin,y), QtCore.QPoint(x-margin,y+lineHeight))
-
- def libFirstVisRowIndex(self):
- """Returns the index of the first visible row in library mode."""
- # if not in library mode, the unthinkable might happen! Wooo!
- # TODO find better initial value
- row=0 # the visible rows we're visiting
- index=0 # what index does the current row have
- entries=self.fSongs
-
- while index<len(entries):
- if row>=self.topRow:
- break
- entry=entries[index]
- if entry[LIB_EXPANDED]==0:
- index=entry[LIB_NEXTROW]
- else:
- index+=1
- row+=1
- return index
- def libIthVisRowIndex(self, index, i=1):
- """Returns the index of the $i-th next row after $index that is visible (or -1) in library mode."""
- entries=self.fSongs
- while i>0 and index<len(entries):
- i-=1
- entry=self.fSongs[index]
- if entry[LIB_EXPANDED]==0:
- if index<0:
- return -1
- index=entry[LIB_NEXTROW]
- else:
- index+=1
-
- return index
-
-
- def libPrint(self):
- for entry in self.fSongs:
- indent=""
- for i in xrange(entry[2]):
- indent=("%s ")%(indent)
- print "%s%s" % (indent, entry)
-
- def _paintLibrary(self, p):
- width=self.width()
- height=self.height()
- lineHeight=self.lineHeight
- margin=self.margin
- vmargin=self.vmargin
-
- # paint the headers!
- p.fillRect(QtCore.QRect(0,0,width+self.vScrollbar.width(),lineHeight), QtGui.QBrush(QtCore.Qt.lightGray))
- p.drawRect(QtCore.QRect(0,0,width+self.vScrollbar.width()-1,lineHeight-1))
- p.drawText(margin, vmargin, width, lineHeight, QtCore.Qt.AlignLeft, self.groupByStr.replace('$', ''))
-
- entries=self.fSongs
-
- y=lineHeight
- x=margin
- indent=self.indentation
- index=self.libFirstVisRowIndex()
- row=0
- while index<len(entries) and y<height:
- entry=entries[index]
-
- level=entry[LIB_INDENT]
- isSong=isinstance(entry[LIB_VALUE], Song)
-
- if isSong:
- prefix=''
- text="%s %s"%(entry[LIB_VALUE].getTrack(), entry[LIB_VALUE].getTitle())
- else:
- if entry[LIB_EXPANDED]==1: prefix='-'
- elif entry[LIB_EXPANDED]==0: prefix='+'
- text=entry[LIB_VALUE]
-
- clr=self.colors[row%2] # background color of the row
- clrTxt=QtCore.Qt.black
-
- values=[]
- if self.selMode:
- checkID=index
- values=self.selRows
- elif self.selMode==False and isSong:
- checkID=entry[LIB_VALUE].getID()
- values=self.selIDs
-
- # if values==[], then it won't run!
- for range in values:
- # is selected if in range, which depends on the selection-mode
- if checkID>=min(range) and checkID<=max(range):
- clr=self.clrSel
- clrTxt=QtCore.Qt.white
-
- for i in self.selMiscs:
- if index==i:
- clr=self.clrSel
- clrTxt=QtCore.Qt.white
-
- # it has a VIP-status!
- if isSong and entry[LIB_VALUE].getID()==int(self.clrID[0]):
- clrTxt=QtCore.Qt.white
- clr=self.clrID[1]
-
- left=x+indent*(1+level)
- top=y+vmargin
- p.fillRect(QtCore.QRect(left,y,width-3,lineHeight), clr)
- p.setPen(clrTxt)
- p.drawText(left, top, 15, lineHeight, QtCore.Qt.AlignLeft, prefix)
- p.drawText(left+15, top, width, lineHeight, QtCore.Qt.AlignLeft, text)
- p.setPen(QtCore.Qt.black)
-
- obj=None
- if level<len(self.levels):
- if self.levels[level][0:len('$artist')]=='$artist':
- obj=self.wgGfxArtist
- elif self.levels[level][0:len('$album')]=='$album':
- obj=self.wgGfxAlbum
-
- if obj:
- obj.render(p, QtCore.QRectF(indent*level+1,y+1,lineHeight-1,lineHeight-1))
-
- y+=lineHeight
- row+=1
- index=self.libIthVisRowIndex(index)
- if index<0:
- break
-
- _mutex=QtCore.QMutex()
- _paintCnt=0
- def paintEvent(self, event):
- self._mutex.lock()
- if self._paintCnt:
- self._mutex.unlock()
- return
- self._paintCnt=1
- self._mutex.unlock()
-
-
- p=QtGui.QPainter(self)
-
- # for the moment, redraw everything ...
- p.fillRect(QtCore.QRect(0,0,self.width(),self.height()), QtGui.QBrush(self.clrBg))
- if self.mode=='playlist':
- self._paintPlaylist(p)
- elif self.mode=='library':
- self._paintLibrary(p)
-
- # draw a nice line around the widget!
- p.drawRect(QtCore.QRect(0,0,self.width()-1,self.height()-1))
- if self.hasFocus():
- p.drawRect(QtCore.QRect(1,1,self.width()-3,self.height()-3))
- else:
- p.setPen(QtCore.Qt.lightGray)
- p.drawRect(QtCore.QRect(1,1,self.width()-3,self.height()-3))
-
- self._paintCnt=0
-
- text=None
- if len(self._filters):
- text="Please wait while filtering ... (%i)"%(len(self._filters))
- #text='%s - %s' % (self.selMiscs, '')
- #text='%s - %s - %s' % (str(self.topRow), str(self.selRows), str(self.selIDs))
- #text='%s - %s'%(str(self.topRow), str(self.numVisEntries))
- if text:
- r=QtCore.QRect(10,self.height()-40,self.width()-20,20)
- p.fillRect(r, QtGui.QBrush(QtCore.Qt.white))
- p.drawText(r,QtCore.Qt.AlignLeft, text)
-
+ """The SongList widget is a list optimized for displaying an array of songs, with filtering option."""
+ # CONFIGURATION VARIABLES
+ " height of line in pxl"
+ lineHeight=30
+ " font size in pxl"
+ fontSize=16
+ " margin"
+ margin=4
+ vmargin=(lineHeight-fontSize)/2-1
+ " width of the vscrollbar"
+ scrollbarWidth=15
+ " minimum column width"
+ minColumnWidth=50
+ " colors for alternating rows"
+ colors=[QtGui.QColor(255,255,255), QtGui.QColor(230,230,255)]
+ " color of selection"
+ clrSel=QtGui.QColor(100,100,180)
+ " background color"
+ clrBg=QtGui.QColor(255,255,255)
+ " indentation of hierarchy, in pixels"
+ indentation=lineHeight
+
+ " what function to call when the list is double clicked"
+ onDoubleClick=None
+
+ mode='playlist' # what mode is the songlist in? values: 'playlist', 'library'
+ " the headers: ( (header, width, visible)+ )"
+ headers=None
+ songs=None # original songs
+ numSongs=None # number of songs
+
+ # 'edited' songs
+ # in playlist mode, this can only filtering
+ # in library mode, this indicates all entries: (row, tag-value, indentation, next-row, expanded)*
+ fSongs=None # filtered songs
+ numVisEntries=None # number of entries that are visible (including when scrolling)
+
+
+ levels=[] # levels from the groupBy in library-mode
+ groupByStr='' # groupBy used in library-mode
+
+ vScrollbar=None
+ hScrollbar=None
+
+ topRow=-1
+ numRows=-1 # total number of rows that can be visible in 1 time
+ selRows=None # ranges of selected rows: ( (startROw,endRow)* )
+ selIDs=None # ranges of selected IDs: [ [startID,endID] ]
+ selMiscs=None # array of indexes for selected non-songs in library mode
+
+ selMode=False # currently in select mode?
+ resizeCol=None # resizing a column?
+ clrID=None # do we have to color a row with certain ID? [ID, color]
+ scrollMult=1 # how many rows do we jump when scrolling by dragging
+ xOffset=0 # offset for drawing. Is changed by hScrollbar
+ resizeColumn=None # indicates this column should be recalculated
+ redrawID=None # redraw this ID/row only
+
+ wgGfxAlbum=QtSvg.QSvgRenderer('gfx/gnome-cd.svg')
+ wgGfxArtist=QtSvg.QSvgRenderer('gfx/user_icon.svg')
+
+
+ def __init__(self, parent, name, headers, onDoubleClick):
+ QtGui.QWidget.__init__(self, parent)
+ self.onDoubleClick=onDoubleClick
+
+ # we receive an array of strings; we convert that to an array of (header, width)
+ self.name=name
+ # load the headers, and fetch from the settings the width and visibility
+ self.headers=map(lambda h: [h, int(settings.get('l%s.%s.width'%(self.name,h),250))
+ , settings.get('l%s.%s.visible'%(self.name,h),'1')=='1'], headers)
+ self.headers.insert(0, ['id', 30, settings.get('l%s.%s.visible'%(self.name,'id'),'0')=='1'])
+ self.songs=None
+ self.numSongs=None
+ self.fSongs=None
+ self.selMiscs=[]
+ self.numVisEntries=None
+ self.xOffset=0
+ self.resizeColumn=None
+
+ self._filters=[]
+
+ self.vScrollbar=QtGui.QScrollBar(QtCore.Qt.Vertical, self)
+ self.vScrollbar.setMinimum(0)
+ self.vScrollbar.setMaximum(1)
+ self.vScrollbar.setValue(0)
+
+ self.hScrollbar=QtGui.QScrollBar(QtCore.Qt.Horizontal, self)
+ self.hScrollbar.setMinimum(0)
+ self.hScrollbar.setMaximum(1)
+ self.hScrollbar.setValue(0)
+ self.hScrollbar.setPageStep(200)
+
+ self.topRow=0
+ self.numRows=0
+ self.selRows=[]
+ self.selMode=False
+ self.clrID=[-1,0]
+
+ self.updateSongs([])
+ doEvents()
+
+ self.connect(self.vScrollbar, QtCore.SIGNAL('valueChanged(int)'),self.onVScroll)
+ self.connect(self.hScrollbar, QtCore.SIGNAL('valueChanged(int)'),self.onHScroll)
+
+ self.setMouseTracking(True)
+ self.setFocusPolicy(QtCore.Qt.TabFocus or QtCore.Qt.ClickFocus
+ or QtCore.Qt.StrongFocus or QtCore.Qt.WheelFocus)
+
+ self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
+ font=QtGui.QFont()
+ font.setPixelSize(self.fontSize)
+ font.setFamily('Comic Sans Ms')
+ self.setFont(font)
+
+ def sizeHint(self):
+ return QtCore.QSize(10000,10000)
+
+ def getSongs(self):
+ return self.songs
+
+ def customEvent(self, event):
+ if isinstance(event, DoResize):
+ self.resizeEvent(None)
+ self.update()
+ elif isinstance(event, DoUpdate):
+ self.update()
+ else:
+ Logger.extended("wgSongList::Unknown event "+str(event))
+
+ def setMode(self, mode, groupBy=''):
+ self.selRows=[]
+ self.selIDs=[]
+ self.selMode=False
+
+ if mode=='playlist':
+ self.fSongs=self.songs
+ self.numVisEntries=len(self.fSongs)
+ elif mode=='library':
+ self.groupBy(groupBy)
+ else:
+ raise Exception('Unknown mode %' %(mode))
+
+ self.mode=mode
+ QtCore.QCoreApplication.postEvent(self, DoResize())
+
+ def groupBy(self, groupBy, strFilter=''):
+ self.groupByStr=groupBy
+ self.levels=groupBy.split('/')
+ strFilter=strFilter.strip()
+
+ l=[]
+ formats=[]
+ for i in xrange(0,len(self.levels)):
+ formats.append(format.compile(self.levels[i]))
+ # TODO also take l[1] etc into account?
+ U="(Unknown)"
+ xtra={"album":U, "artist":U, "date":"", "genre":U}
+ compare=lambda left, right: cmp(\
+ formats[0](format.params(left)).lower(), \
+ formats[0](format.params(right)).lower() \
+ )
+
+ songs=self.songs
+ if strFilter!='':
+ songs=filter(lambda song: song.match(strFilter), songs)
+ songs=sorted(songs, compare)
+
+ numLevels=len(self.levels)
+ self.fSongs=[[0, 'dummy', 0, -1, False]]
+ row=0
+ # four levels ought to be enough for everyone
+ curLevels=[[None,0], [None,0], [None,0], [None,0]] # contains the values of current levels
+ curLevel=0 # current level we're in
+ parents=[-1,-1,-1,-1] # index of parent
+ for song in songs:
+ if not(self.songs):
+ return
+ for level in xrange(numLevels):
+ # does the file have the required tag?
+ if not formats[level](format.params(song, {}, xtra))==curLevels[level][LIB_ROW]:
+ finalRow=row
+ for i in xrange(level,numLevels):
+ tagValue2=formats[i](format.params(song, {}, xtra))
+
+ self.fSongs[curLevels[i][1]][LIB_NEXTROW]=finalRow
+ self.fSongs.append([row, tagValue2, i, row+1, 0, parents[i]])
+ parents[i+1]=row
+
+ row+=1
+ curLevels[i]=[tagValue2, row]
+ curLevel=numLevels
+ self.fSongs.append([row, song, curLevel, row+1, 2, parents[curLevel]])
+ row+=1
+
+ # update last entries' next-row of each level
+ # If we have e.g. artist/album, then the last artist and last album of that
+ # artist have to be repointed to the end of the list, else problems arise
+ # showing those entries ...
+ # indicate for each level whether we have processed that level yet
+ processed=[False, False, False, False, False]
+ numFSongs=len(self.fSongs)
+ for i in xrange(numFSongs-1,0,-1):
+ song=self.fSongs[i]
+ # look for last top-level entry
+ if song[LIB_INDENT]==0:
+ song[LIB_NEXTROW]=numFSongs
+ break
+ if processed[song[LIB_INDENT]]==False:
+ song[LIB_NEXTROW]=numFSongs
+ processed[song[LIB_INDENT]]=True
+
+
+ # remove the dummy
+ self.fSongs.pop(0)
+
+ self.numVisEntries=len(filter(lambda entry: entry[LIB_INDENT]==0, self.fSongs))
+
+ def updateSongs(self, songs):
+ """Update the displayed songs and clears selection."""
+ self.songs=songs
+ self.numSongs=len(songs)
+
+ self.setMode(self.mode, self.groupByStr)
+
+ self.redrawID=None
+ QtCore.QCoreApplication.postEvent(self, DoUpdate())
+
+ def selectedSongs(self):
+ """Returns the list of selected songs."""
+ ret=[]
+ if self.mode=='playlist':
+ cmp=lambda song: song._data['id']>=range[0] and song._data['id']<=range[1]
+ elif self.mode=='library':
+ cmp=lambda song: song._data['id']>=range[0] and song._data['id']<=range[1]
+ for range in self.selIDs:
+ # look for the songs in the current range
+ songs=filter(cmp, self.songs)
+ # add songs in range
+ ret.extend(songs)
+ return ret
+
+ def killFilters(self):
+ songs=self.songs
+ self.songs=None
+ while (len(self._filters)):
+ # wait 'till everything's cleared
+ doEvents()
+ self.songs=songs
+
+
+ # contains filters ready to be applied; only the top one will be used
+ _filters=[]
+ def filter(self, strFilter):
+ """Filter songs according to $strFilter."""
+
+ self._filters.append(strFilter)
+ strFilter=self._filters[-1]
+
+ try:
+ if self.mode=='playlist':
+ self.fSongs=filter(lambda song: song.match(strFilter), self.songs)
+ self.numVisEntries=len(self.fSongs)
+ else:
+ self.groupBy(self.groupByStr, strFilter)
+ except:
+ # we might get here because self.songs is None
+ pass
+ self._filters=[]
+ QtCore.QCoreApplication.postEvent(self, DoResize())
+
+ def colorID(self, id, clr):
+ """Color the row which contains song with id $id $clr."""
+ self.clrID=[id, clr]
+ self.redrawID=id
+
+ QtCore.QCoreApplication.postEvent(self, DoUpdate())
+
+ def selectRow(self, row):
+ """Make $row the current selection."""
+ self.selRows=[[row,row]]
+
+ QtCore.QCoreApplication.postEvent(self, DoUpdate())
+
+ def showColumn(self, column, show=True):
+ """Hide or show column $column."""
+ self.headers[column][2]=show
+
+ self.update()
+
+ def autoSizeColumn(self, column):
+ """Resizes column $column to fit the widest entry in the non-filtered songs."""
+ # we can't calculate it here, as retrieving the text-width can only
+ # be done in the paintEvent method ...
+ self.resizeColumn=column
+
+ QtCore.QCoreApplication.postEvent(self, DoUpdate())
+
+ def visibleSongs(self):
+ """Get the songs currently visible."""
+ ret=[]
+ if self.mode=='playlist':
+ for row in xrange(self.topRow, min(self.numSongs, self.topRow+self.numRows)-1):
+ ret.append(self.fSongs[row])
+ elif self.mode=='library':
+ # note that if everything is folded, there'll be no songs!
+ entries=self.fSongs
+ index=self.libFirstVisRowIndex()
+ count=0
+ while index>=0 and index<len(entries) and count<self.numRows:
+ entry=self.fSongs[index]
+ if isinstance(entry[LIB_VALUE], Song):
+ ret.append(entry[LIB_VALUE])
+ index=self.libIthVisRowIndex(index)
+ count+=1
+ return ret
+
+ def ensureVisible(self, id):
+ """Make sure the song with $id is visible."""
+ if len(filter(lambda song: song.getID()==id, self.visibleSongs())):
+ return
+ row=0
+ ok=False
+ if self.mode=='playlist':
+ # playlist mode is simple: just hop to the song with id!
+ for song in self.fSongs:
+ row+=1
+ if song.getID()==id:
+ ok=True
+ break
+ elif self.mode=='library':
+ # library mode is more complex: we must find out how many rows are visible,
+ # and expand the parents of the song, if necessary
+ indLevel=0 # indicates what is the deepest level that is expanded for the current entry
+ # thus if current indent<=indLevel, then it is visible
+ for entry in self.fSongs:
+ if entry[LIB_EXPANDED]==1:
+ indLevel=max(entry[LIB_INDENT]+1, indLevel)
+ elif entry[LIB_EXPANDED]==0:
+ indLevel=min(entry[LIB_INDENT], indLevel)
+ if entry[LIB_INDENT]<=indLevel:
+ row+=1
+
+ #print "%s -> %s"%(str(indLevel), str(entry))
+ if isinstance(entry[LIB_VALUE], Song) and entry[LIB_VALUE].getID()==id:
+ # expand parents
+ # must be expanded in reverse order, else we will count too many
+ # entries ...
+ parents=[]
+ while entry[LIB_PARENT]>=0:
+ entry=self.fSongs[entry[LIB_PARENT]]
+ parents.append(entry)
+ row-=1
+ parents.reverse()
+ for parent in parents:
+ self.libExpand(parent)
+ ok=True
+ break
+
+ if ok:
+ self.vScrollbar.setValue(row-self.numRows/2)
+ self.update()
+
+
+ def onVScroll(self, value):
+ # 'if value<0' needed because minimum can be after init <0 at some point ...
+ if value<0: value=0
+ if value>self.numVisEntries:value=self.numVisEntries
+ self.topRow=value
+
+ self.update()
+
+ def onHScroll(self, value):
+ self.xOffset=-self.hScrollbar.value()*2
+ self.update()
+
+ def _pos2row(self, pos):
+ return int(pos.y()/self.lineHeight)-1
+ def _row2entry(self, row):
+ entry=self.fSongs[0]
+ try:
+ while row>0:
+ if entry[LIB_EXPANDED]:
+ entry=self.fSongs[entry[LIB_ROW]+1]
+ else:
+ entry=self.fSongs[entry[LIB_NEXTROW]]
+ row-=1
+ except:
+ return None
+ return entry
+
+ def focusOutEvent(self, event):
+ self.update()
+ def focusInEvent(self, event):
+ self.update()
+ def wheelEvent(self, event):
+ if self.vScrollbar.isVisible():
+ event.accept()
+ numDegrees=event.delta() / 8
+ numSteps=5*numDegrees/15
+ self.vScrollbar.setValue(self.vScrollbar.value()-numSteps)
+
+ def resizeEvent(self, event):
+ # max nr of rows shown
+ self.numRows=int(self.height()/self.lineHeight)
+
+ # check vertical scrollbar
+ if self.numRows>self.numVisEntries:
+ self.vScrollbar.setVisible(False)
+ self.vScrollbar.setValue(0)
+ else:
+ self.vScrollbar.setVisible(True)
+ self.vScrollbar.setPageStep(self.numRows-2)
+ self.vScrollbar.setMinimum(0)
+ self.vScrollbar.setMaximum(self.numVisEntries-self.numRows+1)
+ self.vScrollbar.resize(self.scrollbarWidth, self.height()-self.lineHeight-1)
+ self.vScrollbar.move(self.width()-self.vScrollbar.width()-1, self.lineHeight-1)
+
+ # check horizontal scrollbar
+ self.scrollWidth=0
+ if self.mode=='playlist':
+ for hdr in self.headers:
+ if hdr[2]:
+ self.scrollWidth+=hdr[1]
+
+ if self.scrollWidth>self.width():
+ self.hScrollbar.setVisible(True)
+ self.hScrollbar.setMinimum(0)
+ self.hScrollbar.setMaximum((self.scrollWidth-self.width())/2)
+ self.hScrollbar.resize(self.width()-4, self.scrollbarWidth)
+ self.hScrollbar.move(2, self.height()-self.hScrollbar.height()-1)
+
+ # some changes because the hScrollbar takes some vertical space ...
+ self.vScrollbar.resize(self.vScrollbar.width(), self.vScrollbar.height()-self.lineHeight)
+ self.vScrollbar.setMaximum(self.vScrollbar.maximum()+1)
+
+ self.numRows-=1
+ else:
+ self.hScrollbar.setVisible(False)
+ self.hScrollbar.setValue(0)
+
+ def libExpand(self, entry):
+ if entry and entry[LIB_EXPANDED]==0:
+ self.libToggle(entry)
+ def libCollapse(self, entry):
+ if entry and entry[LIB_EXPANDED]==1:
+ self.libToggle(entry)
+
+ def libToggle(self, entry):
+ """Toggles expanded state. Returns new state"""
+ expanded=entry[LIB_EXPANDED]
+ if expanded!=2:
+ # there was a '+' or a '-'!
+ entry[LIB_EXPANDED]=(expanded+1)%2
+ ret=entry[LIB_EXPANDED]
+ # we must find out how many entries have appeared/disappeard
+ # while collapsing.
+ visibles=0 # how many new elements have appeared?
+ i=entry[LIB_ROW]+1 # current element looking at
+ indLevel=self.fSongs[i][LIB_INDENT]
+ while i<=entry[LIB_NEXTROW]-1 and i<len(self.fSongs):
+ entry2=self.fSongs[i]
+ if entry2[LIB_EXPANDED]==1:
+ indLevel=max(entry2[LIB_INDENT]+1, indLevel)
+ elif entry2[LIB_EXPANDED]==0:
+ indLevel=min(entry2[LIB_INDENT], indLevel)
+ if entry2[LIB_INDENT]<=indLevel:
+ visibles+=1
+
+ if entry2[LIB_EXPANDED]==0:
+ i=entry2[LIB_NEXTROW]
+ else:
+ i+=1
+
+ mult=1
+ if ret==0: mult=-1
+ self.numVisEntries+=mult*visibles
+ self.resizeEvent(None)
+ return ret
+
+ return 2
+
+ def mousePressEvent(self, event):
+ self.setFocus()
+ pos=event.pos()
+ row=self._pos2row(pos)
+
+ done=False # indicates whether some action has been done or not
+ if self.mode=='playlist':
+ self.scrollMult=1
+ if row==-1:
+ # we're clicking in the header!
+ self.resizeCol=None
+ x=0+self.xOffset
+ i=0
+ # check if we're clicking between two columns, if so: resize mode!
+ for hdr in self.headers:
+ if hdr[2]:
+ x+=hdr[1]
+ if abs(x-pos.x())<4:
+ self.resizeCol=i
+ done=True
+ i+=1
+ elif self.mode=='library':
+ entry=self._row2entry(row+self.topRow)
+ if not entry:
+ entry=self.fSongs[len(self.fSongs)-1]
+ if entry and pos.x()>(1+entry[LIB_INDENT])*self.indentation \
+ and pos.x()<(1+entry[LIB_INDENT]+3/2)*self.indentation:
+ # we clicked in the margin, to expand or collapse
+ self.libToggle(entry)
+ done=True
+
+ if done==False:
+ self.selMode=True
+ self.selIDs=[]
+ self.selMiscs=[]
+ if row==-1 and self.resizeCol==None:
+ # we're not resizing, thus we can select all!
+ self.selRows=[[0, len(self.fSongs)]]
+ elif row>=0:
+ # we start selection mode
+ if self.mode=='playlist':
+ self.selRows=[[self.topRow+row,self.topRow+row]]
+ elif self.mode=='library':
+ self.selRows=[[entry[LIB_ROW], entry[LIB_NEXTROW]-1]]
+ self.selMode=True
+
+ self.update()
+
+ def mouseMoveEvent(self, event):
+ pos=event.pos()
+ row=self._pos2row(pos)
+ if self.selMode:
+ # we're in selection mode
+ if row<0:
+ # scroll automatically when going out of the widget
+ row=0
+ if self.topRow>0:
+ self.scrollMult+=0.1
+ jump=int(self.scrollMult)*int(abs(pos.y())/self.lineHeight)
+ self.vScrollbar.setValue(self.vScrollbar.value()-jump)
+ row=jump
+ elif row>=self.numRows:
+ # scroll automatically when going out of the widget
+ self.scrollMult+=0.1
+ jump=int(self.scrollMult)*int(abs(self.height()-pos.y())/self.lineHeight)
+ self.vScrollbar.setValue(self.vScrollbar.value()+jump)
+ row=self.numRows-jump
+ else:
+ # reset the scrollMultiplier
+ self.scrollMult=1
+
+ if self.mode=='playlist':
+ self.selRows[0][1]=row+self.topRow
+ elif self.mode=='library':
+ self.selRows[0][1]=self.libIthVisRowIndex(self.libIthVisRowIndex(0,self.topRow), row)
+ self.update()
+ elif self.resizeCol!=None:
+ row-=1
+ # ohla, we're resizing a column!
+ prev=0
+ # calculate where we are
+ for i in xrange(self.resizeCol):
+ hdr=self.headers[i]
+ if hdr[2]:
+ prev+=hdr[1]
+ self.headers[self.resizeCol][1]=pos.x()-prev-self.xOffset
+ # minimum width check?
+ if self.headers[self.resizeCol][1]<self.minColumnWidth:
+ self.headers[self.resizeCol][1]=self.minColumnWidth
+ self.resizeEvent(None)
+ self.update()
+
+ def mouseReleaseEvent(self, event):
+ if self.selMode and len(self.selRows):
+ # we were selecting, but now we're done.
+ # We have to transform one range of rows
+ # into range of selected IDs
+ # The problem is that the list can be filtered, and that
+ # consequtive, visible rows aren't always directly
+ # consequtive in the unfiltered list.
+ self.selMode=False # exit selection mode
+ fSongs=self.fSongs
+ self.selMiscs=[]
+ ranges=[]
+ curRange=[]
+ # loop over all rows that are selected
+ for entry in fSongs[min(self.selRows[0]):max(self.selRows[0])+1]:
+ song=None
+ if isinstance(entry,Song):
+ song=entry
+ elif isinstance(entry[LIB_VALUE],Song):
+ song=entry[LIB_VALUE]
+ else:
+ self.selMiscs.append(entry[LIB_ROW])
+
+ if song!=None:
+ id=song.getID()
+ # is this song directly after the previous row?
+ if len(curRange)==0 or curRange[-1]+1==id:
+ curRange.append(id)
+ else:
+ ranges.append(curRange)
+ curRange=[id]
+ if len(curRange):
+ ranges.append(curRange)
+ # clean up ranges
+ self.selRows=[]
+ self.selIDs=[]
+ for range in ranges:
+ self.selIDs.append([range[0], range[-1]])
+ self.update()
+
+ elif self.resizeCol!=None:
+ # store this new width!
+ self.saveColumnWidth(self.resizeCol)
+ # we're not resizing anymore!
+ self.resizeCol=None
+ self.update()
+ def saveColumnWidth(self, col):
+ settings.set('l%s.%s.width'%(self.name,self.headers[col][0]), self.headers[col][1])
+ def mouseDoubleClickEvent(self, event):
+ pos=event.pos()
+ row=self._pos2row(pos)
+ if row>=0:
+ self.onDoubleClick()
+ else:
+ # auto-size column
+ x=0+self.xOffset
+ i=0
+ for hdr in self.headers:
+ if hdr[2]:
+ x+=hdr[1]
+ if abs(x-pos.x())<4:
+ self.autoSizeColumn(i)
+ break
+ i+=1
+
+ def _paintPlaylist(self, p):
+ self.redrawID=None
+
+
+ lineHeight=self.lineHeight
+ margin=self.margin
+ vmargin=self.vmargin
+ selRows=self.selRows
+ width=self.width()
+ if self.vScrollbar.isVisible():
+ width-=self.scrollbarWidth
+
+ if self.resizeColumn!=None:
+ # we're autoresizing!
+ # must be done here, because only here we can check the textwidth!
+ # This is because of limitations it can be only be done in paintEvent
+ hdr=self.headers[self.resizeColumn][0]
+ w=self.minColumnWidth
+ # loop over all visible songs ...
+ for song in self.fSongs:
+ rect=p.boundingRect(10,10,1,1, QtCore.Qt.AlignLeft, song.getTag(hdr))
+ w=max(rect.width(), w)
+ self.headers[self.resizeColumn][1]=w+2*margin
+ self.saveColumnWidth(self.resizeColumn)
+ self.resizeColumn=None
+ self.resizeEvent(None)
+ if self.redrawID!=None:
+ # only update one row
+ y=lineHeight
+ for row in xrange(self.topRow, min(self.numVisEntries, self.topRow+self.numRows)):
+ if self.fSongs[row]._data['id']==self.redrawID:
+ self._paintPlaylistRow(p, row, y, width)
+ y+=lineHeight
+
+ self.redrawID=None
+ return
+
+ # paint the headers!
+ p.fillRect(QtCore.QRect(0,0,width+self.vScrollbar.width(),lineHeight), QtGui.QBrush(QtCore.Qt.lightGray))
+ p.drawRect(QtCore.QRect(0,0,width+self.vScrollbar.width()-1,lineHeight-1))
+ x=margin+self.xOffset
+ for hdr in self.headers:
+ if hdr[2]:
+ p.drawText(x, vmargin, hdr[1], lineHeight, QtCore.Qt.AlignLeft, hdr[0])
+ x+=hdr[1]
+ p.drawLine(QtCore.QPoint(x-margin,0), QtCore.QPoint(x-margin,lineHeight))
+
+ if self.songs==None:
+ return
+ # fill the records!
+ y=lineHeight
+ for row in xrange(self.topRow, min(self.numVisEntries, self.topRow+self.numRows)):
+ self._paintPlaylistRow(p, row, y, width)
+ y+=lineHeight
+ if y<self.height():
+ # if we're short on songs, draw up the remaining area in background color
+ p.fillRect(QtCore.QRect(0,y,width,self.height()-y), QtGui.QBrush(self.clrBg))
+
+ def _paintPlaylistRow(self, p, row, y, width):
+ """Paint row $row on $p on height $y and with width $width."""
+ song=self.fSongs[row]
+ lineHeight=self.lineHeight
+ margin=self.margin
+ vmargin=self.vmargin
+ id=song._data['id']
+
+ # determine color of row. Default is row-color, but can be overridden by
+ # (in this order): selection, special row color!
+ clr=self.colors[row%2] # background color of the row
+ clrTxt=QtCore.Qt.black # color of the printed text
+ # is it selected?
+ values=[]
+ if self.selMode:
+ checkID=row
+ values=self.selRows
+ else:
+ checkID=id
+ values=self.selIDs
+ # if values==[], it won't run!
+ for range in values:
+ # is selected if in range, which depends on the selection-mode
+ if checkID>=min(range) and checkID<=max(range):
+ clr=self.clrSel
+ clrTxt=QtCore.Qt.white
+ # it has a VIP-status!
+ if id==int(self.clrID[0]):
+ clrTxt=QtCore.Qt.white
+ clr=self.clrID[1]
+
+ # draw the row background
+ p.fillRect(QtCore.QRect(2, y, width-3, lineHeight), QtGui.QBrush(clr))
+
+ # draw a subtile rectangle
+ p.setPen(QtGui.QColor(230,230,255))
+ p.drawRect(QtCore.QRect(2, y, width-3, lineHeight))
+
+ # Back To Black
+ p.setPen(QtCore.Qt.black)
+
+ # draw the column
+ x=margin+self.xOffset
+ for hdr in self.headers:
+ if hdr[2]:
+ # only if visible, duh!
+ # rectangle we're allowed to print in
+ text=song.getTag(hdr[0])
+ if type(text)!=str and type(text)!=unicode:
+ text=str(text)
+ rect=p.boundingRect(x, y, hdr[1]-margin, lineHeight, QtCore.Qt.AlignLeft, text)
+ p.setPen(clrTxt)
+ p.drawText(x, y+vmargin, hdr[1]-margin, lineHeight, QtCore.Qt.AlignLeft, text)
+ if rect.width()>hdr[1]-margin:
+ # print ellipsis, if necessary
+ p.fillRect(x+hdr[1]-15,y+1,15,lineHeight-1, QtGui.QBrush(clr))
+ p.drawText(x+hdr[1]-15,y+vmargin,15,lineHeight-1, QtCore.Qt.AlignLeft, "...")
+ x+=hdr[1]
+ p.setPen(QtCore.Qt.black)
+ p.drawLine(QtCore.QPoint(x-margin,y), QtCore.QPoint(x-margin,y+lineHeight))
+
+ def libFirstVisRowIndex(self):
+ """Returns the index of the first visible row in library mode."""
+ # if not in library mode, the unthinkable might happen! Wooo!
+ # TODO find better initial value
+ row=0 # the visible rows we're visiting
+ index=0 # what index does the current row have
+ entries=self.fSongs
+
+ while index<len(entries):
+ if row>=self.topRow:
+ break
+ entry=entries[index]
+ if entry[LIB_EXPANDED]==0:
+ index=entry[LIB_NEXTROW]
+ else:
+ index+=1
+ row+=1
+ return index
+ def libIthVisRowIndex(self, index, i=1):
+ """Returns the index of the $i-th next row after $index that is visible (or -1) in library mode."""
+ entries=self.fSongs
+ while i>0 and index<len(entries):
+ i-=1
+ entry=self.fSongs[index]
+ if entry[LIB_EXPANDED]==0:
+ if index<0:
+ return -1
+ index=entry[LIB_NEXTROW]
+ else:
+ index+=1
+
+ return index
+
+
+ def libPrint(self):
+ for entry in self.fSongs:
+ indent=""
+ for i in xrange(entry[2]):
+ indent=("%s ")%(indent)
+ print "%s%s" % (indent, entry)
+
+ def _paintLibrary(self, p):
+ width=self.width()
+ height=self.height()
+ lineHeight=self.lineHeight
+ margin=self.margin
+ vmargin=self.vmargin
+
+ # paint the headers!
+ p.fillRect(QtCore.QRect(0,0,width+self.vScrollbar.width(),lineHeight), QtGui.QBrush(QtCore.Qt.lightGray))
+ p.drawRect(QtCore.QRect(0,0,width+self.vScrollbar.width()-1,lineHeight-1))
+ p.drawText(margin, vmargin, width, lineHeight, QtCore.Qt.AlignLeft, self.groupByStr.replace('$', ''))
+
+ entries=self.fSongs
+
+ y=lineHeight
+ x=margin
+ indent=self.indentation
+ index=self.libFirstVisRowIndex()
+ row=0
+ while index<len(entries) and y<height:
+ entry=entries[index]
+
+ level=entry[LIB_INDENT]
+ isSong=isinstance(entry[LIB_VALUE], Song)
+
+ if isSong:
+ prefix=''
+ text="%s %s"%(entry[LIB_VALUE].getTrack(), entry[LIB_VALUE].getTitle())
+ else:
+ if entry[LIB_EXPANDED]==1: prefix='-'
+ elif entry[LIB_EXPANDED]==0: prefix='+'
+ text=entry[LIB_VALUE]
+
+ clr=self.colors[row%2] # background color of the row
+ clrTxt=QtCore.Qt.black
+
+ values=[]
+ if self.selMode:
+ checkID=index
+ values=self.selRows
+ elif self.selMode==False and isSong:
+ checkID=entry[LIB_VALUE].getID()
+ values=self.selIDs
+
+ # if values==[], then it won't run!
+ for range in values:
+ # is selected if in range, which depends on the selection-mode
+ if checkID>=min(range) and checkID<=max(range):
+ clr=self.clrSel
+ clrTxt=QtCore.Qt.white
+
+ for i in self.selMiscs:
+ if index==i:
+ clr=self.clrSel
+ clrTxt=QtCore.Qt.white
+
+ # it has a VIP-status!
+ if isSong and entry[LIB_VALUE].getID()==int(self.clrID[0]):
+ clrTxt=QtCore.Qt.white
+ clr=self.clrID[1]
+
+ left=x+indent*(1+level)
+ top=y+vmargin
+ p.fillRect(QtCore.QRect(left,y,width-3,lineHeight), clr)
+ p.setPen(clrTxt)
+ p.drawText(left, top, 15, lineHeight, QtCore.Qt.AlignLeft, prefix)
+ p.drawText(left+15, top, width, lineHeight, QtCore.Qt.AlignLeft, text)
+ p.setPen(QtCore.Qt.black)
+
+ obj=None
+ if level<len(self.levels):
+ if self.levels[level][0:len('$artist')]=='$artist':
+ obj=self.wgGfxArtist
+ elif self.levels[level][0:len('$album')]=='$album':
+ obj=self.wgGfxAlbum
+
+ if obj:
+ obj.render(p, QtCore.QRectF(indent*level+1,y+1,lineHeight-1,lineHeight-1))
+
+ y+=lineHeight
+ row+=1
+ index=self.libIthVisRowIndex(index)
+ if index<0:
+ break
+
+ _mutex=QtCore.QMutex()
+ _paintCnt=0
+ def paintEvent(self, event):
+ self._mutex.lock()
+ if self._paintCnt:
+ self._mutex.unlock()
+ return
+ self._paintCnt=1
+ self._mutex.unlock()
+
+
+ p=QtGui.QPainter(self)
+
+ # for the moment, redraw everything ...
+ p.fillRect(QtCore.QRect(0,0,self.width(),self.height()), QtGui.QBrush(self.clrBg))
+ if self.mode=='playlist':
+ self._paintPlaylist(p)
+ elif self.mode=='library':
+ self._paintLibrary(p)
+
+ # draw a nice line around the widget!
+ p.drawRect(QtCore.QRect(0,0,self.width()-1,self.height()-1))
+ if self.hasFocus():
+ p.drawRect(QtCore.QRect(1,1,self.width()-3,self.height()-3))
+ else:
+ p.setPen(QtCore.Qt.lightGray)
+ p.drawRect(QtCore.QRect(1,1,self.width()-3,self.height()-3))
+
+ self._paintCnt=0
+
+ text=None
+ if len(self._filters):
+ text="Please wait while filtering ... (%i)"%(len(self._filters))
+ #text='%s - %s' % (self.selMiscs, '')
+ #text='%s - %s - %s' % (str(self.topRow), str(self.selRows), str(self.selIDs))
+ #text='%s - %s'%(str(self.topRow), str(self.numVisEntries))
+ if text:
+ r=QtCore.QRect(10,self.height()-40,self.width()-20,20)
+ p.fillRect(r, QtGui.QBrush(QtCore.Qt.white))
+ p.drawText(r,QtCore.Qt.AlignLeft, text)
+
diff --git a/winConnect.py b/winConnect.py
index 2a7fe5c..80fa011 100644
--- a/winConnect.py
+++ b/winConnect.py
@@ -6,82 +6,82 @@ from traceback import print_exc
from clSettings import settings
class winConnect(QtGui.QWidget):
- txtHost=None
- txtPort=None
- lblInfo=None
- _timerID=None
+ txtHost=None
+ txtPort=None
+ lblInfo=None
+ _timerID=None
- def __init__(self, parent=None):
- QtGui.QWidget.__init__(self, parent)
- self.txtHost=QtGui.QLineEdit(settings.get('host', 'localhost'))
- self.txtPort=QtGui.QLineEdit(settings.get('port', '6600'))
- self.lblInfo=QtGui.QLabel("connecting ...")
+ def __init__(self, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.txtHost=QtGui.QLineEdit(settings.get('host', 'localhost'))
+ self.txtPort=QtGui.QLineEdit(settings.get('port', '6600'))
+ self.lblInfo=QtGui.QLabel("connecting ...")
- frame=QtGui.QVBoxLayout()
- inputs=QtGui.QHBoxLayout()
+ frame=QtGui.QVBoxLayout()
+ inputs=QtGui.QHBoxLayout()
- frame.addLayout(inputs)
- frame.addWidget(self.lblInfo)
+ frame.addLayout(inputs)
+ frame.addWidget(self.lblInfo)
- inputs.addWidget(self.txtHost)
- inputs.addWidget(self.txtPort)
+ inputs.addWidget(self.txtHost)
+ inputs.addWidget(self.txtPort)
- self.setWindowIcon(appIcon)
- self.setWindowTitle('Connect to mpd')
- self.setLayout(frame)
- self.resize(200,80)
- self.center()
- doEvents()
+ self.setWindowIcon(appIcon)
+ self.setWindowTitle('Connect to mpd')
+ self.setLayout(frame)
+ self.resize(200,80)
+ self.center()
+ doEvents()
- monty.addListener('onReady', self.onReady)
- monty.addListener('onConnect', self.onConnect)
+ monty.addListener('onReady', self.onReady)
+ monty.addListener('onConnect', self.onConnect)
- def center(self):
- screen = QtGui.QDesktopWidget().screenGeometry()
- size = self.geometry()
- self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2+100)
+ def center(self):
+ screen = QtGui.QDesktopWidget().screenGeometry()
+ size = self.geometry()
+ self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2+100)
- def monitor(self):
- self.txtHost.setEnabled(True)
- self.txtPort.setEnabled(True)
- if self._timerID==None:
- self._timerID=self.startTimer(200)
- if self.isVisible()==False:
- self.show()
- self.activateWindow()
- self.raise_()
- doEvents()
+ def monitor(self):
+ self.txtHost.setEnabled(True)
+ self.txtPort.setEnabled(True)
+ if self._timerID==None:
+ self._timerID=self.startTimer(200)
+ if self.isVisible()==False:
+ self.show()
+ self.activateWindow()
+ self.raise_()
+ doEvents()
-
- def onConnect(self, params):
- if self._timerID:
- self.killTimer(self._timerID)
- self._timerID=None
- self.lblInfo.setText('Connected!\nRestoring library and playlist ...')
- doEvents()
- settings.set('host', str(self.txtHost.text()))
- settings.set('port', str(self.txtPort.text()))
- self.txtHost.setEnabled(False)
- self.txtPort.setEnabled(False)
- doEvents()
+
+ def onConnect(self, params):
+ if self._timerID:
+ self.killTimer(self._timerID)
+ self._timerID=None
+ self.lblInfo.setText('Connected!\nRestoring library and playlist ...')
+ doEvents()
+ settings.set('host', str(self.txtHost.text()))
+ settings.set('port', str(self.txtPort.text()))
+ self.txtHost.setEnabled(False)
+ self.txtPort.setEnabled(False)
+ doEvents()
- def onReady(self, params):
- self.hide()
+ def onReady(self, params):
+ self.hide()
- def timerEvent(self, event):
- host=str(self.txtHost.text())
- try:
- port=int(self.txtPort.text())
- except:
- self.lblInfo.setText('Invalid port')
- return
+ def timerEvent(self, event):
+ host=str(self.txtHost.text())
+ try:
+ port=int(self.txtPort.text())
+ except:
+ self.lblInfo.setText('Invalid port')
+ return
- self.lblInfo.setText('Trying to connect to '+host+':'+str(port)+' ...')
- doEvents()
- monty.connect(host, port)
- doEvents()
-
- def windowActivationChange(self, bool):
- self.activateWindow()
- self.raise_()
- doEvents()
+ self.lblInfo.setText('Trying to connect to '+host+':'+str(port)+' ...')
+ doEvents()
+ monty.connect(host, port)
+ doEvents()
+
+ def windowActivationChange(self, bool):
+ self.activateWindow()
+ self.raise_()
+ doEvents()
diff --git a/winMain.py b/winMain.py
index d0054dd..100262a 100644
--- a/winMain.py
+++ b/winMain.py
@@ -16,274 +16,274 @@ import log
class winMain(QtGui.QMainWindow):
- """The winMain class is mpc's main window, showing the playlists and control-interface"""
- docks=[]
+ """The winMain class is mpc's main window, showing the playlists and control-interface"""
+ docks=[]
- " menus"
- mConnect=None
- mDisconnect=None
- mLayout=None
-
- " connection window"
- wConnect=None
- wSettings=None
+ " menus"
+ mConnect=None
+ mDisconnect=None
+ mLayout=None
+
+ " connection window"
+ wConnect=None
+ wSettings=None
- def __init__(self, parent=None):
- QtGui.QWidget.__init__(self, parent)
- self.setWindowTitle("montypc - An MPD client")
+ def __init__(self, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.setWindowTitle("montypc - An MPD client")
- self.wConnect=winConnect()
-
- self.statusBar() # create a statusbar
- mBar=self.menuBar() # create a menubar
- # menu file
- m=mBar.addMenu("File")
- m.setTearOffEnabled(True)
- # connect
- self.mConnect=m.addAction('Connect ...', self.wConnect.monitor)
- self.mConnect.setIcon(QtGui.QIcon('gfx/connect.png'))
- # disconnect
- self.mDisconnect=m.addAction('Disconnect', monty.disconnect)
- self.mDisconnect.setIcon(QtGui.QIcon('gfx/disconnect.png'))
- # separator
- m.addSeparator()
- # quit
- m.addAction("Quit", self.quit).setIcon(QtGui.QIcon('gfx/gtk-quit.svg'))
+ self.wConnect=winConnect()
+
+ self.statusBar() # create a statusbar
+ mBar=self.menuBar() # create a menubar
+ # menu file
+ m=mBar.addMenu("File")
+ m.setTearOffEnabled(True)
+ # connect
+ self.mConnect=m.addAction('Connect ...', self.wConnect.monitor)
+ self.mConnect.setIcon(QtGui.QIcon('gfx/connect.png'))
+ # disconnect
+ self.mDisconnect=m.addAction('Disconnect', monty.disconnect)
+ self.mDisconnect.setIcon(QtGui.QIcon('gfx/disconnect.png'))
+ # separator
+ m.addSeparator()
+ # quit
+ m.addAction("Quit", self.quit).setIcon(QtGui.QIcon('gfx/gtk-quit.svg'))
- # menu options
- m=mBar.addMenu("Options")
- m.setTearOffEnabled(True)
- # settings
- m.addAction("Settings", self.showWinSettings).setIcon(QtGui.QIcon('gfx/gtk-preferences.svg'))
+ # menu options
+ m=mBar.addMenu("Options")
+ m.setTearOffEnabled(True)
+ # settings
+ m.addAction("Settings", self.showWinSettings).setIcon(QtGui.QIcon('gfx/gtk-preferences.svg'))
- # menu layout
- self.mLayout=mBar.addMenu("Layout")
- self.mLayout.setTearOffEnabled(True)
+ # menu layout
+ self.mLayout=mBar.addMenu("Layout")
+ self.mLayout.setTearOffEnabled(True)
- newPlugins=False # are there new plugins?
- for k, entry in plugins.listPlugins().iteritems():
- # load the plugin
- plugin=plugins.loadPlugin(entry[plugins.PLUGIN_CLASS], self)
- if plugin:
- newPlugins=newPlugins|(settings.get('%s.load'%(plugin.getName(True)), None)==None)
- if settings.get('%s.load'%(plugin.getName(True)), '1')=='1':
- # load new plugins by default
- plugin.load()
- else:
- newPlugins=True
-
- self.updateLayoutMenu()
- self.setDockOptions(QtGui.QMainWindow.AllowNestedDocks \
- |QtGui.QMainWindow.AllowTabbedDocks \
- |QtGui.QMainWindow.VerticalTabs)
- self.setDockNestingEnabled(True)
+ newPlugins=False # are there new plugins?
+ for k, entry in plugins.listPlugins().iteritems():
+ # load the plugin
+ plugin=plugins.loadPlugin(entry[plugins.PLUGIN_CLASS], self)
+ if plugin:
+ newPlugins=newPlugins|(settings.get('%s.load'%(plugin.getName(True)), None)==None)
+ if settings.get('%s.load'%(plugin.getName(True)), '1')=='1':
+ # load new plugins by default
+ plugin.load()
+ else:
+ newPlugins=True
+
+ self.updateLayoutMenu()
+ self.setDockOptions(QtGui.QMainWindow.AllowNestedDocks \
+ |QtGui.QMainWindow.AllowTabbedDocks \
+ |QtGui.QMainWindow.VerticalTabs)
+ self.setDockNestingEnabled(True)
- try:
- x,y=settings.getIntTuple('winMain.pos')
- except:
- x,y=0,0
- try:
- w,h=settings.getIntTuple('winMain.size')
- except:
- w,h=800,600
- self.resize(w,h)
- self.move(x,y)
- self.restoreLayout()
+ try:
+ x,y=settings.getIntTuple('winMain.pos')
+ except:
+ x,y=0,0
+ try:
+ w,h=settings.getIntTuple('winMain.size')
+ except:
+ w,h=800,600
+ self.resize(w,h)
+ self.move(x,y)
+ self.restoreLayout()
- " add event handlers"
- monty.addListener('onReady', self.onReady)
- monty.addListener('onConnect', self.onConnect)
- monty.addListener('onDisconnect', self.onDisconnect)
- monty.addListener('onUpdateDBStart', self.onUpdateDBStart)
- monty.addListener('onUpdateDBFinish', self.onUpdateDBFinish)
+ " add event handlers"
+ monty.addListener('onReady', self.onReady)
+ monty.addListener('onConnect', self.onConnect)
+ monty.addListener('onDisconnect', self.onDisconnect)
+ monty.addListener('onUpdateDBStart', self.onUpdateDBStart)
+ monty.addListener('onUpdateDBFinish', self.onUpdateDBFinish)
- self.enableAll(True)
- self.setWindowIcon(appIcon)
- # set icon in system tray
- self.wConnect.monitor()
+ self.enableAll(True)
+ self.setWindowIcon(appIcon)
+ # set icon in system tray
+ self.wConnect.monitor()
- self.show()
- if newPlugins:
- self.showWinSettings()
- doEvents()
+ self.show()
+ if newPlugins:
+ self.showWinSettings()
+ doEvents()
- def quit(self):
- # unload all plugins
- for entry in plugins.listPlugins().values():
- p=entry[plugins.PLUGIN_INSTANCE]
- if p and p.isLoaded():
- p.unload()
+ def quit(self):
+ # unload all plugins
+ for entry in plugins.listPlugins().values():
+ p=entry[plugins.PLUGIN_INSTANCE]
+ if p and p.isLoaded():
+ p.unload()
- QtCore.QCoreApplication.exit()
-
-
- def updateLayoutMenu(self):
- self.mLayout.clear()
- self.mLayout.addAction('Save layout', self.saveLayout)
- self.mLayout.addAction('Restore layout', self.restoreLayout)
- self.mLayout.addSeparator()
- # create checkable menu
- a=QtGui.QAction('Show titlebars', self)
- a.setCheckable(True)
- a.setChecked(settings.get('winMain.show.titlebars', '1')=='1')
- self.toggleTitleBars(a.isChecked())
- self.connect(a, QtCore.SIGNAL('toggled(bool)'), self.toggleTitleBars)
- self.mLayout.addAction(a)
- self.mLayout.addSeparator()
- # can not use iterators, as that gives some creepy error 'bout c++
- actions=self.createPopupMenu().actions()
- for i in xrange(len(actions)):
- self.mLayout.addAction(actions[i])
-
- def toggleTitleBars(self, val):
- if val:
- settings.set('winMain.show.titlebars', '1')
- else:
- settings.set('winMain.show.titlebars', '0')
- for dock in self.docks:
- if val:
- dock.setTitleBarWidget(None)
- else:
- dock.setTitleBarWidget(QtGui.QWidget())
- def addDock(self, dock):
- if dock:
- self.docks.append(dock)
- self.addDockWidget(QtCore.Qt.TopDockWidgetArea, dock)
- self.updateLayoutMenu()
- def removeDock(self, dock):
- if dock:
- self.docks.remove(dock)
- self.removeDockWidget(dock)
- self.updateLayoutMenu()
+ QtCore.QCoreApplication.exit()
+
+
+ def updateLayoutMenu(self):
+ self.mLayout.clear()
+ self.mLayout.addAction('Save layout', self.saveLayout)
+ self.mLayout.addAction('Restore layout', self.restoreLayout)
+ self.mLayout.addSeparator()
+ # create checkable menu
+ a=QtGui.QAction('Show titlebars', self)
+ a.setCheckable(True)
+ a.setChecked(settings.get('winMain.show.titlebars', '1')=='1')
+ self.toggleTitleBars(a.isChecked())
+ self.connect(a, QtCore.SIGNAL('toggled(bool)'), self.toggleTitleBars)
+ self.mLayout.addAction(a)
+ self.mLayout.addSeparator()
+ # can not use iterators, as that gives some creepy error 'bout c++
+ actions=self.createPopupMenu().actions()
+ for i in xrange(len(actions)):
+ self.mLayout.addAction(actions[i])
+
+ def toggleTitleBars(self, val):
+ if val:
+ settings.set('winMain.show.titlebars', '1')
+ else:
+ settings.set('winMain.show.titlebars', '0')
+ for dock in self.docks:
+ if val:
+ dock.setTitleBarWidget(None)
+ else:
+ dock.setTitleBarWidget(QtGui.QWidget())
+ def addDock(self, dock):
+ if dock:
+ self.docks.append(dock)
+ self.addDockWidget(QtCore.Qt.TopDockWidgetArea, dock)
+ self.updateLayoutMenu()
+ def removeDock(self, dock):
+ if dock:
+ self.docks.remove(dock)
+ self.removeDockWidget(dock)
+ self.updateLayoutMenu()
- mMenuVisible=None
- def createPopupMenu(self):
- ret=QtGui.QMenu('Test', self)
- if self.mMenuVisible==None:
- # create checkable menu
- a=QtGui.QAction('Menubar', self)
- a.setCheckable(True)
- a.setChecked(True)
- self.connect(a, QtCore.SIGNAL('toggled(bool)'), self.switchMenubar)
-
- self.mMenuVisible=a
- ret.addAction(self.mMenuVisible)
- ret.addSeparator()
- actions=QtGui.QMainWindow.createPopupMenu(self).actions()
- for i in xrange(len(actions)-1):
- ret.addAction(actions[i])
- return ret
- def switchMenubar(self, val):
- self.menuBar().setVisible(val)
- def setStatus(self, status):
- """Set the text of the statusbar."""
- self.statusBar().showMessage(status)
- log.extended(status)
+ mMenuVisible=None
+ def createPopupMenu(self):
+ ret=QtGui.QMenu('Test', self)
+ if self.mMenuVisible==None:
+ # create checkable menu
+ a=QtGui.QAction('Menubar', self)
+ a.setCheckable(True)
+ a.setChecked(True)
+ self.connect(a, QtCore.SIGNAL('toggled(bool)'), self.switchMenubar)
+
+ self.mMenuVisible=a
+ ret.addAction(self.mMenuVisible)
+ ret.addSeparator()
+ actions=QtGui.QMainWindow.createPopupMenu(self).actions()
+ for i in xrange(len(actions)-1):
+ ret.addAction(actions[i])
+ return ret
+ def switchMenubar(self, val):
+ self.menuBar().setVisible(val)
+ def setStatus(self, status):
+ """Set the text of the statusbar."""
+ self.statusBar().showMessage(status)
+ log.extended(status)
- def saveLayout(self):
- f=open('layout', 'wb')
- f.write(self.saveState())
- f.close()
- log.normal("Layout saved")
- def restoreLayout(self):
- try:
- f=open('layout', 'rb')
- self.restoreState(f.read())
- f.close()
- except:
- pass
+ def saveLayout(self):
+ f=open('layout', 'wb')
+ f.write(self.saveState())
+ f.close()
+ log.normal("Layout saved")
+ def restoreLayout(self):
+ try:
+ f=open('layout', 'rb')
+ self.restoreState(f.read())
+ f.close()
+ except:
+ pass
- def showWinSettings(self):
- if not self.wSettings:
- self.wSettings=winSettings(self)
- self.wSettings.show()
- self.wSettings.raise_()
+ def showWinSettings(self):
+ if not self.wSettings:
+ self.wSettings=winSettings(self)
+ self.wSettings.show()
+ self.wSettings.raise_()
- def onReady(self, params):
- self.initialiseData()
+ def onReady(self, params):
+ self.initialiseData()
- def onConnect(self, params):
- log.normal("Connected to MPD")
- self.setStatus('Restoring library and playlist ...')
- self.mDisconnect.setEnabled(True)
- self.mConnect.setEnabled(False)
- doEvents()
+ def onConnect(self, params):
+ log.normal("Connected to MPD")
+ self.setStatus('Restoring library and playlist ...')
+ self.mDisconnect.setEnabled(True)
+ self.mConnect.setEnabled(False)
+ doEvents()
- def enableAll(self, value):
- for k,entry in plugins.listPlugins().iteritems():
- try:
- plugin=entry[plugins.PLUGIN_INSTANCE]
- plugin.o.setEnabled(value)
- except:
- pass
+ def enableAll(self, value):
+ for k,entry in plugins.listPlugins().iteritems():
+ try:
+ plugin=entry[plugins.PLUGIN_INSTANCE]
+ plugin.o.setEnabled(value)
+ except:
+ pass
- def initialiseData(self):
- """Initialise the library, playlist and some other small things"""
- self.setStatus("Filling library ...")
- doEvents()
- self.fillLibrary()
- doEvents()
-
- self.setStatus("Filling playlist ...")
- doEvents()
- self.fillPlaylist()
- doEvents()
-
- self.setStatus("Doing the rest ...")
- doEvents()
- self.enableAll(True)
- self.setStatus("")
- doEvents()
-
- def resizeEvent(self, event):
- settings.set('winMain.size', '%i %i'%(self.width(),self.height()))
- def moveEvent(self, event):
- settings.set('winMain.pos', '%i %i'%(self.x(),self.y()))
-
- def onDisconnect(self, params):
- log.normal("Disconnected from MPD")
- try:
- self.getPlaylistList().updateSongs([])
- except:
- pass
- try:
- self.getLibraryList().updateSongs([])
- except:
- pass
- self.mDisconnect.setEnabled(False)
- self.mConnect.setEnabled(True)
- self.enableAll(False)
- self.setStatus("You are disconnected. Choose File->Connect to reconnect!")
+ def initialiseData(self):
+ """Initialise the library, playlist and some other small things"""
+ self.setStatus("Filling library ...")
+ doEvents()
+ self.fillLibrary()
+ doEvents()
+
+ self.setStatus("Filling playlist ...")
+ doEvents()
+ self.fillPlaylist()
+ doEvents()
+
+ self.setStatus("Doing the rest ...")
+ doEvents()
+ self.enableAll(True)
+ self.setStatus("")
+ doEvents()
+
+ def resizeEvent(self, event):
+ settings.set('winMain.size', '%i %i'%(self.width(),self.height()))
+ def moveEvent(self, event):
+ settings.set('winMain.pos', '%i %i'%(self.x(),self.y()))
+
+ def onDisconnect(self, params):
+ log.normal("Disconnected from MPD")
+ try:
+ self.getPlaylistList().updateSongs([])
+ except:
+ pass
+ try:
+ self.getLibraryList().updateSongs([])
+ except:
+ pass
+ self.mDisconnect.setEnabled(False)
+ self.mConnect.setEnabled(True)
+ self.enableAll(False)
+ self.setStatus("You are disconnected. Choose File->Connect to reconnect!")
- def fillPlaylist(self):
- """Fill the playlist."""
- try:
- self.getPlaylistList().updateSongs(monty.listPlaylist())
- except:
- pass
+ def fillPlaylist(self):
+ """Fill the playlist."""
+ try:
+ self.getPlaylistList().updateSongs(monty.listPlaylist())
+ except:
+ pass
- def fillLibrary(self):
- """Fill the library."""
- try:
- self.getLibraryList().updateSongs(monty.listLibrary())
- except:
- pass
-
- def getPlaylistList(self):
- try:
- return plugins.getPlugin('Playlist').getList()
- except:
- print "Missing playlist plugin!"
- return None
+ def fillLibrary(self):
+ """Fill the library."""
+ try:
+ self.getLibraryList().updateSongs(monty.listLibrary())
+ except:
+ pass
+
+ def getPlaylistList(self):
+ try:
+ return plugins.getPlugin('Playlist').getList()
+ except:
+ print "Missing playlist plugin!"
+ return None
- def getLibraryList(self):
- try:
- return plugins.getPlugin('Library').getList()
- except:
- print "Missing library plugin!"
- return None
+ def getLibraryList(self):
+ try:
+ return plugins.getPlugin('Library').getList()
+ except:
+ print "Missing library plugin!"
+ return None
- def onUpdateDBFinish(self, params):
- self.setStatus('')
- def onUpdateDBStart(self, params):
- self.setStatus('Updating the database. Please wait ...')
+ def onUpdateDBFinish(self, params):
+ self.setStatus('')
+ def onUpdateDBStart(self, params):
+ self.setStatus('Updating the database. Please wait ...')
diff --git a/winSettings.py b/winSettings.py
index 0ae8cc4..f6f1a95 100644
--- a/winSettings.py
+++ b/winSettings.py
@@ -8,97 +8,97 @@ import plugins
class winSettings(QtGui.QWidget):
- btnSave=None
- btnClose=None
- lstPlugins=None
-
- winMain=None
-
- def __init__(self, winMain, parent=None):
- QtGui.QWidget.__init__(self, parent)
- self.winMain=winMain
-
- self.btnSave=Button('save all', self.onBtnSaveClick)
- self.btnClose=Button('close', self.onBtnCloseClick)
-
- self.lstPlugins=QtGui.QListWidget(self)
- layoutWin=QtGui.QVBoxLayout()
- layoutButtons=QtGui.QHBoxLayout()
-
- tabWidget=QtGui.QTabWidget(parent)
- self.setLayout(layoutWin)
-
- tabWidget.addTab(self.lstPlugins, "plugins")
- for k,entry in plugins.listPlugins().iteritems():
- plugin=entry[plugins.PLUGIN_INSTANCE]
- if plugin:
- tabWidget.addTab(plugin.getSettingsWidget(), plugin.getName())
- self.fillList()
-
- layoutWin.addWidget(tabWidget)
- layoutWin.addLayout(layoutButtons)
-
- layoutButtons.addStretch()
- layoutButtons.addWidget(self.btnSave)
- layoutButtons.addWidget(self.btnClose)
-
- self.connect(self.lstPlugins, QtCore.SIGNAL('itemChanged (QListWidgetItem*)'), self.onlstPluginItemChanged)
-
- self.setWindowIcon(appIcon)
- self.setWindowTitle('Settings')
- self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
- self.center()
- self.resize(800,400)
- doEvents()
+ btnSave=None
+ btnClose=None
+ lstPlugins=None
+
+ winMain=None
+
+ def __init__(self, winMain, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.winMain=winMain
+
+ self.btnSave=Button('save all', self.onBtnSaveClick)
+ self.btnClose=Button('close', self.onBtnCloseClick)
+
+ self.lstPlugins=QtGui.QListWidget(self)
+ layoutWin=QtGui.QVBoxLayout()
+ layoutButtons=QtGui.QHBoxLayout()
+
+ tabWidget=QtGui.QTabWidget(parent)
+ self.setLayout(layoutWin)
+
+ tabWidget.addTab(self.lstPlugins, "plugins")
+ for k,entry in plugins.listPlugins().iteritems():
+ plugin=entry[plugins.PLUGIN_INSTANCE]
+ if plugin:
+ tabWidget.addTab(plugin.getSettingsWidget(), plugin.getName())
+ self.fillList()
+
+ layoutWin.addWidget(tabWidget)
+ layoutWin.addLayout(layoutButtons)
+
+ layoutButtons.addStretch()
+ layoutButtons.addWidget(self.btnSave)
+ layoutButtons.addWidget(self.btnClose)
+
+ self.connect(self.lstPlugins, QtCore.SIGNAL('itemChanged (QListWidgetItem*)'), self.onlstPluginItemChanged)
+
+ self.setWindowIcon(appIcon)
+ self.setWindowTitle('Settings')
+ self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+ self.center()
+ self.resize(800,400)
+ doEvents()
- def fillList(self):
- self.lstPlugins.clear()
- for k,entry in plugins.listPlugins().iteritems():
- plugin=entry[plugins.PLUGIN_INSTANCE]
- if plugin:
- item=QtGui.QListWidgetItem("%s\t%s"%(entry[plugins.PLUGIN_CLASS], plugin.getInfo()))
- if plugin.isLoaded():
- item.setCheckState(QtCore.Qt.Checked)
- else:
- item.setCheckState(QtCore.Qt.Unchecked)
-
- if settings.get('%s.load'%(plugin.getName(True)),None)==None:
- # load new plugins by default
- item.setTextColor(QtCore.Qt.blue)
- settings.set('%s.load'%(plugin.getName(True)), 1)
- else:
- item=QtGui.QListWidgetItem("%s\t%s"%(entry[plugins.PLUGIN_CLASS], entry[plugins.PLUGIN_MSG]))
- item.setCheckState(QtCore.Qt.Unchecked)
- item.setTextColor(QtCore.Qt.red)
- self.lstPlugins.addItem(item)
-
+ def fillList(self):
+ self.lstPlugins.clear()
+ for k,entry in plugins.listPlugins().iteritems():
+ plugin=entry[plugins.PLUGIN_INSTANCE]
+ if plugin:
+ item=QtGui.QListWidgetItem("%s\t%s"%(entry[plugins.PLUGIN_CLASS], plugin.getInfo()))
+ if plugin.isLoaded():
+ item.setCheckState(QtCore.Qt.Checked)
+ else:
+ item.setCheckState(QtCore.Qt.Unchecked)
+
+ if settings.get('%s.load'%(plugin.getName(True)),None)==None:
+ # load new plugins by default
+ item.setTextColor(QtCore.Qt.blue)
+ settings.set('%s.load'%(plugin.getName(True)), 1)
+ else:
+ item=QtGui.QListWidgetItem("%s\t%s"%(entry[plugins.PLUGIN_CLASS], entry[plugins.PLUGIN_MSG]))
+ item.setCheckState(QtCore.Qt.Unchecked)
+ item.setTextColor(QtCore.Qt.red)
+ self.lstPlugins.addItem(item)
+
- def center(self):
- screen = QtGui.QDesktopWidget().screenGeometry()
- size = self.geometry()
- self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2+100)
-
- def onBtnSaveClick(self):
- map(lambda entry: entry[plugins.PLUGIN_INSTANCE] and entry[plugins.PLUGIN_INSTANCE].saveSettings(), plugins.listPlugins().values())
+ def center(self):
+ screen = QtGui.QDesktopWidget().screenGeometry()
+ size = self.geometry()
+ self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2+100)
+
+ def onBtnSaveClick(self):
+ map(lambda entry: entry[plugins.PLUGIN_INSTANCE] and entry[plugins.PLUGIN_INSTANCE].saveSettings(), plugins.listPlugins().values())
- def onBtnCloseClick(self):
- self.close()
- def onlstPluginItemChanged(self, item):
- # check here if we have to load or unload the plugin!
- toload=int(item.checkState()==QtCore.Qt.Checked)
- className=str(item.text()[0:str(item.text()).find('\t')])
- if toload:
- # refresh the plugin file
- plugin=plugins.loadPlugin(className, self.winMain)
- if plugin:
- plugin.load()
- self.fillList()
- else:
- plugin=plugins.getPlugin(className)
- if plugin:
- plugin.unload()
- if plugin:
- settings.set('%s.load'%(plugin.getName(True)), toload)
- def closeEvent(self, event):
- map(lambda entry: entry[plugins.PLUGIN_INSTANCE] and entry[plugins.PLUGIN_INSTANCE].resetSettingCache(), plugins.listPlugins().values())
- self.winMain.wSettings=None
+ def onBtnCloseClick(self):
+ self.close()
+ def onlstPluginItemChanged(self, item):
+ # check here if we have to load or unload the plugin!
+ toload=int(item.checkState()==QtCore.Qt.Checked)
+ className=str(item.text()[0:str(item.text()).find('\t')])
+ if toload:
+ # refresh the plugin file
+ plugin=plugins.loadPlugin(className, self.winMain)
+ if plugin:
+ plugin.load()
+ self.fillList()
+ else:
+ plugin=plugins.getPlugin(className)
+ if plugin:
+ plugin.unload()
+ if plugin:
+ settings.set('%s.load'%(plugin.getName(True)), toload)
+ def closeEvent(self, event):
+ map(lambda entry: entry[plugins.PLUGIN_INSTANCE] and entry[plugins.PLUGIN_INSTANCE].resetSettingCache(), plugins.listPlugins().values())
+ self.winMain.wSettings=None