summaryrefslogtreecommitdiff
path: root/nephilim
diff options
context:
space:
mode:
authorAnton Khirnov <wyskas@gmail.com>2009-02-20 10:49:00 +0100
committerAnton Khirnov <wyskas@gmail.com>2009-02-20 10:49:00 +0100
commitae872ea3018fe1dac39f867f386b081598fb0812 (patch)
tree856d2b6d32050174682305b421548e680fe5647a /nephilim
parent000f9d5ba84426da6b5211dd0bea4a401b8f4289 (diff)
Move modules to a separate dir.
Diffstat (limited to 'nephilim')
-rw-r--r--nephilim/LyricWiki_client.py578
-rw-r--r--nephilim/LyricWiki_types.py162
-rw-r--r--nephilim/clPlugin.py136
-rw-r--r--nephilim/clSong.py85
-rw-r--r--nephilim/default_layoutbin0 -> 302 bytes
-rw-r--r--nephilim/misc.py85
-rw-r--r--nephilim/mpclient.py364
-rw-r--r--nephilim/mpd.py360
-rw-r--r--nephilim/plugins/AlbumCover.py304
-rw-r--r--nephilim/plugins/Filebrowser.py47
-rw-r--r--nephilim/plugins/Library.py155
-rw-r--r--nephilim/plugins/Lyrics.py74
-rw-r--r--nephilim/plugins/Notify.py152
-rw-r--r--nephilim/plugins/PlayControl.py169
-rw-r--r--nephilim/plugins/Playlist.py92
-rw-r--r--nephilim/plugins/Systray.py125
-rw-r--r--nephilim/plugins/__init__.py97
-rw-r--r--nephilim/winConnect.py88
-rw-r--r--nephilim/winMain.py285
-rw-r--r--nephilim/winSettings.py176
20 files changed, 3534 insertions, 0 deletions
diff --git a/nephilim/LyricWiki_client.py b/nephilim/LyricWiki_client.py
new file mode 100644
index 0000000..76c24bb
--- /dev/null
+++ b/nephilim/LyricWiki_client.py
@@ -0,0 +1,578 @@
+##################################################
+# file: LyricWiki_client.py
+#
+# client stubs generated by "ZSI.generate.wsdl2python.WriteServiceModule"
+# /usr/bin/wsdl2py --complexType http://lyricwiki.org/server.php?wsdl
+#
+##################################################
+
+from LyricWiki_types import *
+import urlparse, types
+from ZSI.TCcompound import ComplexType, Struct
+from ZSI import client
+from ZSI.schema import GED, GTD
+import ZSI
+from ZSI.generate.pyclass import pyclass_type
+
+# Locator
+class LyricWikiLocator:
+ LyricWikiPort_address = "http://lyricwiki.org/server.php"
+ def getLyricWikiPortAddress(self):
+ return LyricWikiLocator.LyricWikiPort_address
+ def getLyricWikiPort(self, url=None, **kw):
+ return LyricWikiBindingSOAP(url or LyricWikiLocator.LyricWikiPort_address, **kw)
+
+# Methods
+class LyricWikiBindingSOAP:
+ def __init__(self, url, **kw):
+ kw.setdefault("readerclass", None)
+ kw.setdefault("writerclass", None)
+ # no resource properties
+ self.binding = client.Binding(url=url, **kw)
+ # no ws-addressing
+
+ # op: checkSongExists
+ def checkSongExists(self, request, **kw):
+ if isinstance(request, checkSongExistsRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#checkSongExists", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=checkSongExistsResponse.typecode.ofwhat, pyclass=checkSongExistsResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: searchArtists
+ def searchArtists(self, request, **kw):
+ if isinstance(request, searchArtistsRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#searchArtists", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=searchArtistsResponse.typecode.ofwhat, pyclass=searchArtistsResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: searchAlbums
+ def searchAlbums(self, request, **kw):
+ if isinstance(request, searchAlbumsRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#searchAlbums", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=searchAlbumsResponse.typecode.ofwhat, pyclass=searchAlbumsResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: searchSongs
+ def searchSongs(self, request, **kw):
+ if isinstance(request, searchSongsRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#searchSongs", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=searchSongsResponse.typecode.ofwhat, pyclass=searchSongsResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: getSOTD
+ def getSOTD(self, request, **kw):
+ if isinstance(request, getSOTDRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#getSOTD", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=getSOTDResponse.typecode.ofwhat, pyclass=getSOTDResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: getSong
+ def getSong(self, request, **kw):
+ if isinstance(request, getSongRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#getSong", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=getSongResponse.typecode.ofwhat, pyclass=getSongResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: getSongResult
+ def getSongResult(self, request, **kw):
+ if isinstance(request, getSongResultRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#getSongResult", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=getSongResultResponse.typecode.ofwhat, pyclass=getSongResultResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: getArtist
+ def getArtist(self, request, **kw):
+ if isinstance(request, getArtistRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#getArtist", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=getArtistResponse.typecode.ofwhat, pyclass=getArtistResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: getAlbum
+ def getAlbum(self, request, **kw):
+ if isinstance(request, getAlbumRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#getAlbum", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=getAlbumResponse.typecode.ofwhat, pyclass=getAlbumResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: getHometown
+ def getHometown(self, request, **kw):
+ if isinstance(request, getHometownRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#getHometown", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=getHometownResponse.typecode.ofwhat, pyclass=getHometownResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: postArtist
+ def postArtist(self, request, **kw):
+ if isinstance(request, postArtistRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#postArtist", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=postArtistResponse.typecode.ofwhat, pyclass=postArtistResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: postAlbum
+ def postAlbum(self, request, **kw):
+ if isinstance(request, postAlbumRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#postAlbum", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=postAlbumResponse.typecode.ofwhat, pyclass=postAlbumResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: postSong
+ def postSong(self, request, **kw):
+ if isinstance(request, postSongRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#postSong", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=postSongResponse.typecode.ofwhat, pyclass=postSongResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+ # op: postSong_flags
+ def postSong_flags(self, request, **kw):
+ if isinstance(request, postSong_flagsRequest) is False:
+ raise TypeError, "%s incorrect request type" % (request.__class__)
+ # no input wsaction
+ self.binding.Send(None, None, request, soapaction="urn:LyricWiki#postSong_flags", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
+ # no output wsaction
+ typecode = Struct(pname=None, ofwhat=postSong_flagsResponse.typecode.ofwhat, pyclass=postSong_flagsResponse.typecode.pyclass)
+ response = self.binding.Receive(typecode)
+ return response
+
+_checkSongExistsRequestTypecode = Struct(pname=("urn:LyricWiki","checkSongExists"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="song", aname="_song", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class checkSongExistsRequest:
+ typecode = _checkSongExistsRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ song -- part song
+ """
+ self._artist = kw.get("artist")
+ self._song = kw.get("song")
+checkSongExistsRequest.typecode.pyclass = checkSongExistsRequest
+
+_checkSongExistsResponseTypecode = Struct(pname=("urn:LyricWiki","checkSongExistsResponse"), ofwhat=[ZSI.TC.Boolean(pname="return", aname="_return", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class checkSongExistsResponse:
+ typecode = _checkSongExistsResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ return -- part return
+ """
+ self._return = kw.get("return")
+checkSongExistsResponse.typecode.pyclass = checkSongExistsResponse
+
+_searchArtistsRequestTypecode = Struct(pname=("urn:LyricWiki","searchArtists"), ofwhat=[ZSI.TC.String(pname="searchString", aname="_searchString", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class searchArtistsRequest:
+ typecode = _searchArtistsRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ searchString -- part searchString
+ """
+ self._searchString = kw.get("searchString")
+searchArtistsRequest.typecode.pyclass = searchArtistsRequest
+
+_searchArtistsResponseTypecode = Struct(pname=("urn:LyricWiki","searchArtistsResponse"), ofwhat=[ns0.ArrayOfstring_Def(pname="return", aname="_return", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class searchArtistsResponse:
+ typecode = _searchArtistsResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ return -- part return
+ """
+ self._return = kw.get("return")
+searchArtistsResponse.typecode.pyclass = searchArtistsResponse
+
+_searchAlbumsRequestTypecode = Struct(pname=("urn:LyricWiki","searchAlbums"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="album", aname="_album", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TCnumbers.Iint(pname="year", aname="_year", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class searchAlbumsRequest:
+ typecode = _searchAlbumsRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ album -- part album
+ year -- part year
+ """
+ self._artist = kw.get("artist")
+ self._album = kw.get("album")
+ self._year = kw.get("year")
+searchAlbumsRequest.typecode.pyclass = searchAlbumsRequest
+
+_searchAlbumsResponseTypecode = Struct(pname=("urn:LyricWiki","searchAlbumsResponse"), ofwhat=[ns0.AlbumResultArray_Def(pname="return", aname="_return", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class searchAlbumsResponse:
+ typecode = _searchAlbumsResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ return -- part return
+ """
+ self._return = kw.get("return")
+searchAlbumsResponse.typecode.pyclass = searchAlbumsResponse
+
+_searchSongsRequestTypecode = Struct(pname=("urn:LyricWiki","searchSongs"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="song", aname="_song", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class searchSongsRequest:
+ typecode = _searchSongsRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ song -- part song
+ """
+ self._artist = kw.get("artist")
+ self._song = kw.get("song")
+searchSongsRequest.typecode.pyclass = searchSongsRequest
+
+_searchSongsResponseTypecode = Struct(pname=("urn:LyricWiki","searchSongsResponse"), ofwhat=[ns0.SongResult_Def(pname="return", aname="_return", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class searchSongsResponse:
+ typecode = _searchSongsResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ return -- part return
+ """
+ self._return = kw.get("return")
+searchSongsResponse.typecode.pyclass = searchSongsResponse
+
+_getSOTDRequestTypecode = Struct(pname=("urn:LyricWiki","getSOTD"), ofwhat=[], pyclass=None, encoded="urn:LyricWiki")
+class getSOTDRequest:
+ typecode = _getSOTDRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ """
+getSOTDRequest.typecode.pyclass = getSOTDRequest
+
+_getSOTDResponseTypecode = Struct(pname=("urn:LyricWiki","getSOTDResponse"), ofwhat=[ns0.SOTDResult_Def(pname="return", aname="_return", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class getSOTDResponse:
+ typecode = _getSOTDResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ return -- part return
+ """
+ self._return = kw.get("return")
+getSOTDResponse.typecode.pyclass = getSOTDResponse
+
+_getSongRequestTypecode = Struct(pname=("urn:LyricWiki","getSong"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="song", aname="_song", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class getSongRequest:
+ typecode = _getSongRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ song -- part song
+ """
+ self._artist = kw.get("artist")
+ self._song = kw.get("song")
+getSongRequest.typecode.pyclass = getSongRequest
+
+_getSongResponseTypecode = Struct(pname=("urn:LyricWiki","getSongResponse"), ofwhat=[ns0.LyricsResult_Def(pname="return", aname="_return", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class getSongResponse:
+ typecode = _getSongResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ return -- part return
+ """
+ self._return = kw.get("return")
+getSongResponse.typecode.pyclass = getSongResponse
+
+_getSongResultRequestTypecode = Struct(pname=("urn:LyricWiki","getSongResult"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="song", aname="_song", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class getSongResultRequest:
+ typecode = _getSongResultRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ song -- part song
+ """
+ self._artist = kw.get("artist")
+ self._song = kw.get("song")
+getSongResultRequest.typecode.pyclass = getSongResultRequest
+
+_getSongResultResponseTypecode = Struct(pname=("urn:LyricWiki","getSongResultResponse"), ofwhat=[ns0.LyricsResult_Def(pname="songResult", aname="_songResult", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class getSongResultResponse:
+ typecode = _getSongResultResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ songResult -- part songResult
+ """
+ self._songResult = kw.get("songResult")
+getSongResultResponse.typecode.pyclass = getSongResultResponse
+
+_getArtistRequestTypecode = Struct(pname=("urn:LyricWiki","getArtist"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class getArtistRequest:
+ typecode = _getArtistRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ """
+ self._artist = kw.get("artist")
+getArtistRequest.typecode.pyclass = getArtistRequest
+
+_getArtistResponseTypecode = Struct(pname=("urn:LyricWiki","getArtistResponse"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ns0.AlbumDataArray_Def(pname="albums", aname="_albums", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class getArtistResponse:
+ typecode = _getArtistResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ albums -- part albums
+ """
+ self._artist = kw.get("artist")
+ self._albums = kw.get("albums")
+getArtistResponse.typecode.pyclass = getArtistResponse
+
+_getAlbumRequestTypecode = Struct(pname=("urn:LyricWiki","getAlbum"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="album", aname="_album", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TCnumbers.Iint(pname="year", aname="_year", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class getAlbumRequest:
+ typecode = _getAlbumRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ album -- part album
+ year -- part year
+ """
+ self._artist = kw.get("artist")
+ self._album = kw.get("album")
+ self._year = kw.get("year")
+getAlbumRequest.typecode.pyclass = getAlbumRequest
+
+_getAlbumResponseTypecode = Struct(pname=("urn:LyricWiki","getAlbumResponse"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="album", aname="_album", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TCnumbers.Iint(pname="year", aname="_year", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="amazonLink", aname="_amazonLink", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ns0.ArrayOfstring_Def(pname="songs", aname="_songs", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class getAlbumResponse:
+ typecode = _getAlbumResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ album -- part album
+ year -- part year
+ amazonLink -- part amazonLink
+ songs -- part songs
+ """
+ self._artist = kw.get("artist")
+ self._album = kw.get("album")
+ self._year = kw.get("year")
+ self._amazonLink = kw.get("amazonLink")
+ self._songs = kw.get("songs")
+getAlbumResponse.typecode.pyclass = getAlbumResponse
+
+_getHometownRequestTypecode = Struct(pname=("urn:LyricWiki","getHometown"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class getHometownRequest:
+ typecode = _getHometownRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ """
+ self._artist = kw.get("artist")
+getHometownRequest.typecode.pyclass = getHometownRequest
+
+_getHometownResponseTypecode = Struct(pname=("urn:LyricWiki","getHometownResponse"), ofwhat=[ZSI.TC.String(pname="country", aname="_country", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="state", aname="_state", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="hometown", aname="_hometown", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class getHometownResponse:
+ typecode = _getHometownResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ country -- part country
+ state -- part state
+ hometown -- part hometown
+ """
+ self._country = kw.get("country")
+ self._state = kw.get("state")
+ self._hometown = kw.get("hometown")
+getHometownResponse.typecode.pyclass = getHometownResponse
+
+_postArtistRequestTypecode = Struct(pname=("urn:LyricWiki","postArtist"), ofwhat=[ZSI.TC.Boolean(pname="overwriteIfExists", aname="_overwriteIfExists", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ns0.AlbumDataArray_Def(pname="albums", aname="_albums", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class postArtistRequest:
+ typecode = _postArtistRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ overwriteIfExists -- part overwriteIfExists
+ artist -- part artist
+ albums -- part albums
+ """
+ self._overwriteIfExists = kw.get("overwriteIfExists")
+ self._artist = kw.get("artist")
+ self._albums = kw.get("albums")
+postArtistRequest.typecode.pyclass = postArtistRequest
+
+_postArtistResponseTypecode = Struct(pname=("urn:LyricWiki","postArtistResponse"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.Boolean(pname="dataUsed", aname="_dataUsed", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="message", aname="_message", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class postArtistResponse:
+ typecode = _postArtistResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ dataUsed -- part dataUsed
+ message -- part message
+ """
+ self._artist = kw.get("artist")
+ self._dataUsed = kw.get("dataUsed")
+ self._message = kw.get("message")
+postArtistResponse.typecode.pyclass = postArtistResponse
+
+_postAlbumRequestTypecode = Struct(pname=("urn:LyricWiki","postAlbum"), ofwhat=[ZSI.TC.Boolean(pname="overwriteIfExists", aname="_overwriteIfExists", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="album", aname="_album", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TCnumbers.Iint(pname="year", aname="_year", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="asin", aname="_asin", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ns0.ArrayOfstring_Def(pname="songs", aname="_songs", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class postAlbumRequest:
+ typecode = _postAlbumRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ overwriteIfExists -- part overwriteIfExists
+ artist -- part artist
+ album -- part album
+ year -- part year
+ asin -- part asin
+ songs -- part songs
+ """
+ self._overwriteIfExists = kw.get("overwriteIfExists")
+ self._artist = kw.get("artist")
+ self._album = kw.get("album")
+ self._year = kw.get("year")
+ self._asin = kw.get("asin")
+ self._songs = kw.get("songs")
+postAlbumRequest.typecode.pyclass = postAlbumRequest
+
+_postAlbumResponseTypecode = Struct(pname=("urn:LyricWiki","postAlbumResponse"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="album", aname="_album", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TCnumbers.Iint(pname="year", aname="_year", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.Boolean(pname="dataUsed", aname="_dataUsed", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="message", aname="_message", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class postAlbumResponse:
+ typecode = _postAlbumResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ album -- part album
+ year -- part year
+ dataUsed -- part dataUsed
+ message -- part message
+ """
+ self._artist = kw.get("artist")
+ self._album = kw.get("album")
+ self._year = kw.get("year")
+ self._dataUsed = kw.get("dataUsed")
+ self._message = kw.get("message")
+postAlbumResponse.typecode.pyclass = postAlbumResponse
+
+_postSongRequestTypecode = Struct(pname=("urn:LyricWiki","postSong"), ofwhat=[ZSI.TC.Boolean(pname="overwriteIfExists", aname="_overwriteIfExists", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="song", aname="_song", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="lyrics", aname="_lyrics", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ns0.AlbumResultArray_Def(pname="onAlbums", aname="_onAlbums", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class postSongRequest:
+ typecode = _postSongRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ overwriteIfExists -- part overwriteIfExists
+ artist -- part artist
+ song -- part song
+ lyrics -- part lyrics
+ onAlbums -- part onAlbums
+ """
+ self._overwriteIfExists = kw.get("overwriteIfExists")
+ self._artist = kw.get("artist")
+ self._song = kw.get("song")
+ self._lyrics = kw.get("lyrics")
+ self._onAlbums = kw.get("onAlbums")
+postSongRequest.typecode.pyclass = postSongRequest
+
+_postSongResponseTypecode = Struct(pname=("urn:LyricWiki","postSongResponse"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="song", aname="_song", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.Boolean(pname="dataUsed", aname="_dataUsed", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="message", aname="_message", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class postSongResponse:
+ typecode = _postSongResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ song -- part song
+ dataUsed -- part dataUsed
+ message -- part message
+ """
+ self._artist = kw.get("artist")
+ self._song = kw.get("song")
+ self._dataUsed = kw.get("dataUsed")
+ self._message = kw.get("message")
+postSongResponse.typecode.pyclass = postSongResponse
+
+_postSong_flagsRequestTypecode = Struct(pname=("urn:LyricWiki","postSong_flags"), ofwhat=[ZSI.TC.Boolean(pname="overwriteIfExists", aname="_overwriteIfExists", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="song", aname="_song", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="lyrics", aname="_lyrics", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ns0.AlbumResultArray_Def(pname="onAlbums", aname="_onAlbums", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="flags", aname="_flags", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class postSong_flagsRequest:
+ typecode = _postSong_flagsRequestTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ overwriteIfExists -- part overwriteIfExists
+ artist -- part artist
+ song -- part song
+ lyrics -- part lyrics
+ onAlbums -- part onAlbums
+ flags -- part flags
+ """
+ self._overwriteIfExists = kw.get("overwriteIfExists")
+ self._artist = kw.get("artist")
+ self._song = kw.get("song")
+ self._lyrics = kw.get("lyrics")
+ self._onAlbums = kw.get("onAlbums")
+ self._flags = kw.get("flags")
+postSong_flagsRequest.typecode.pyclass = postSong_flagsRequest
+
+_postSong_flagsResponseTypecode = Struct(pname=("urn:LyricWiki","postSong_flagsResponse"), ofwhat=[ZSI.TC.String(pname="artist", aname="_artist", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="song", aname="_song", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.Boolean(pname="dataUsed", aname="_dataUsed", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="message", aname="_message", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:LyricWiki")
+class postSong_flagsResponse:
+ typecode = _postSong_flagsResponseTypecode
+ __metaclass__ = pyclass_type
+ def __init__(self, **kw):
+ """Keyword parameters:
+ artist -- part artist
+ song -- part song
+ dataUsed -- part dataUsed
+ message -- part message
+ """
+ self._artist = kw.get("artist")
+ self._song = kw.get("song")
+ self._dataUsed = kw.get("dataUsed")
+ self._message = kw.get("message")
+postSong_flagsResponse.typecode.pyclass = postSong_flagsResponse
diff --git a/nephilim/LyricWiki_types.py b/nephilim/LyricWiki_types.py
new file mode 100644
index 0000000..d27cb1c
--- /dev/null
+++ b/nephilim/LyricWiki_types.py
@@ -0,0 +1,162 @@
+##################################################
+# file: LyricWiki_types.py
+#
+# schema types generated by "ZSI.generate.wsdl2python.WriteServiceModule"
+# /usr/bin/wsdl2py --complexType http://lyricwiki.org/server.php?wsdl
+#
+##################################################
+
+import ZSI
+import ZSI.TCcompound
+from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED
+from ZSI.generate.pyclass import pyclass_type
+
+##############################
+# targetNamespace
+# urn:LyricWiki
+##############################
+
+class ns0:
+ targetNamespace = "urn:LyricWiki"
+
+ class ArrayOfstring_Def(ZSI.TC.Array, TypeDefinition):
+ #complexType/complexContent base="SOAP-ENC:Array"
+ schema = "urn:LyricWiki"
+ type = (schema, "ArrayOfstring")
+ def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw):
+ ofwhat = ZSI.TC.String(None, typed=False)
+ atype = (u'http://www.w3.org/2001/XMLSchema', u'string[]')
+ ZSI.TCcompound.Array.__init__(self, atype, ofwhat, pname=pname, childnames='item', **kw)
+
+ class AlbumResultArray_Def(ZSI.TC.Array, TypeDefinition):
+ #complexType/complexContent base="SOAP-ENC:Array"
+ schema = "urn:LyricWiki"
+ type = (schema, "AlbumResultArray")
+ def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw):
+ ofwhat = ZSI.TC.AnyType(None, typed=False)
+ atype = (u'http://www.w3.org/2001/XMLSchema', u'AlbumResult[]')
+ ZSI.TCcompound.Array.__init__(self, atype, ofwhat, pname=pname, childnames='item', **kw)
+
+ class AlbumResult_Def(ZSI.TCcompound.ComplexType, TypeDefinition):
+ schema = "urn:LyricWiki"
+ type = (schema, "AlbumResult")
+ def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw):
+ ns = ns0.AlbumResult_Def.schema
+ TClist = [ZSI.TC.String(pname="artist", aname="_artist", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="album", aname="_album", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname="year", aname="_year", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))]
+ self.attribute_typecode_dict = attributes or {}
+ if extend: TClist += ofwhat
+ if restrict: TClist = ofwhat
+ ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw)
+ class Holder:
+ __metaclass__ = pyclass_type
+ typecode = self
+ def __init__(self):
+ # pyclass
+ self._artist = None
+ self._album = None
+ self._year = None
+ return
+ Holder.__name__ = "AlbumResult_Holder"
+ self.pyclass = Holder
+
+ class SongResult_Def(ZSI.TCcompound.ComplexType, TypeDefinition):
+ schema = "urn:LyricWiki"
+ type = (schema, "SongResult")
+ def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw):
+ ns = ns0.SongResult_Def.schema
+ TClist = [ZSI.TC.String(pname="artist", aname="_artist", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="song", aname="_song", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))]
+ self.attribute_typecode_dict = attributes or {}
+ if extend: TClist += ofwhat
+ if restrict: TClist = ofwhat
+ ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw)
+ class Holder:
+ __metaclass__ = pyclass_type
+ typecode = self
+ def __init__(self):
+ # pyclass
+ self._artist = None
+ self._song = None
+ return
+ Holder.__name__ = "SongResult_Holder"
+ self.pyclass = Holder
+
+ class LyricsResult_Def(ZSI.TCcompound.ComplexType, TypeDefinition):
+ schema = "urn:LyricWiki"
+ type = (schema, "LyricsResult")
+ def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw):
+ ns = ns0.LyricsResult_Def.schema
+ TClist = [ZSI.TC.String(pname="artist", aname="_artist", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="song", aname="_song", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="lyrics", aname="_lyrics", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="url", aname="_url", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))]
+ self.attribute_typecode_dict = attributes or {}
+ if extend: TClist += ofwhat
+ if restrict: TClist = ofwhat
+ ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw)
+ class Holder:
+ __metaclass__ = pyclass_type
+ typecode = self
+ def __init__(self):
+ # pyclass
+ self._artist = None
+ self._song = None
+ self._lyrics = None
+ self._url = None
+ return
+ Holder.__name__ = "LyricsResult_Holder"
+ self.pyclass = Holder
+
+ class SOTDResult_Def(ZSI.TCcompound.ComplexType, TypeDefinition):
+ schema = "urn:LyricWiki"
+ type = (schema, "SOTDResult")
+ def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw):
+ ns = ns0.SOTDResult_Def.schema
+ TClist = [ZSI.TC.String(pname="artist", aname="_artist", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="song", aname="_song", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="nominatedBy", aname="_nominatedBy", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="reason", aname="_reason", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="lyrics", aname="_lyrics", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))]
+ self.attribute_typecode_dict = attributes or {}
+ if extend: TClist += ofwhat
+ if restrict: TClist = ofwhat
+ ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw)
+ class Holder:
+ __metaclass__ = pyclass_type
+ typecode = self
+ def __init__(self):
+ # pyclass
+ self._artist = None
+ self._song = None
+ self._nominatedBy = None
+ self._reason = None
+ self._lyrics = None
+ return
+ Holder.__name__ = "SOTDResult_Holder"
+ self.pyclass = Holder
+
+ class AlbumDataArray_Def(ZSI.TC.Array, TypeDefinition):
+ #complexType/complexContent base="SOAP-ENC:Array"
+ schema = "urn:LyricWiki"
+ type = (schema, "AlbumDataArray")
+ def __init__(self, pname, ofwhat=(), extend=False, restrict=False, attributes=None, **kw):
+ ofwhat = ZSI.TC.AnyType(None, typed=False)
+ atype = (u'http://www.w3.org/2001/XMLSchema', u'AlbumData[]')
+ ZSI.TCcompound.Array.__init__(self, atype, ofwhat, pname=pname, childnames='item', **kw)
+
+ class AlbumData_Def(ZSI.TCcompound.ComplexType, TypeDefinition):
+ schema = "urn:LyricWiki"
+ type = (schema, "AlbumData")
+ def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw):
+ ns = ns0.AlbumData_Def.schema
+ TClist = [ZSI.TC.String(pname="album", aname="_album", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TCnumbers.Iint(pname="year", aname="_year", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="amazonLink", aname="_amazonLink", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), GTD("urn:LyricWiki","ArrayOfstring",lazy=False)(pname="songs", aname="_songs", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded"))]
+ self.attribute_typecode_dict = attributes or {}
+ if extend: TClist += ofwhat
+ if restrict: TClist = ofwhat
+ ZSI.TCcompound.ComplexType.__init__(self, None, TClist, pname=pname, inorder=0, **kw)
+ class Holder:
+ __metaclass__ = pyclass_type
+ typecode = self
+ def __init__(self):
+ # pyclass
+ self._album = None
+ self._year = None
+ self._amazonLink = None
+ self._songs = None
+ return
+ Holder.__name__ = "AlbumData_Holder"
+ self.pyclass = Holder
+
+# end class ns0 (tns: urn:LyricWiki)
diff --git a/nephilim/clPlugin.py b/nephilim/clPlugin.py
new file mode 100644
index 0000000..efbd502
--- /dev/null
+++ b/nephilim/clPlugin.py
@@ -0,0 +1,136 @@
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import QVariant
+import logging
+
+import plugins
+from misc import *
+
+class Plugin:
+ name = None
+ dockWidget = None
+ settings = None
+ winMain = None
+ loaded = None
+ listeners = None
+ mpclient = None
+ DEFAULTS = {}
+
+ def __init__(self, winMain, name):
+ self.name = name
+ self.winMain = winMain
+ self.loaded = False
+ self.listeners = []
+ self.mpclient = winMain.mpclient
+ self.settings = QtCore.QSettings(ORGNAME, APPNAME)
+
+ #init settings
+ self.settings.beginGroup(self.name)
+ for key in self.DEFAULTS:
+ if not self.settings.contains(key):
+ self.settings.setValue(key, QVariant(self.DEFAULTS[key]))
+ self.settings.endGroup()
+
+ def getName(self):
+ 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 load(self):
+ logging.info("loading")
+ if len(self.listeners):
+ logging.debug("adding %s listeners"%(len(self.listeners)))
+ for listener in self.listeners:
+ self.mpclient.add_listener(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 not self.loaded:
+ return
+ logging.info("unloading")
+ if len(self.listeners):
+ logging.debug("removing %s listeners"%(len(self.listeners)))
+ for listener in self.listeners:
+ self.mpclient.removeListener(listener[0], listener[1])
+
+ self._unload()
+ dock_widget = self.getDockWidget()
+ if dock_widget:
+ self.winMain.removeDock(dock_widget)
+ self.dockWidget = None
+ self.settingsWidget = None
+ self.loaded = False
+ def isLoaded(self):
+ return self.loaded
+
+ def addListener(self, event, callback):
+ self.listeners.append([event, callback])
+
+ def getDockWidget(self, opts=None):
+ self.dockWidget = self._getDockWidget()
+ if self.dockWidget and opts:
+ self.dockWidget.setFeatures(opts)
+ self.dockWidget.setAllowedAreas(QtCore.Qt.AllDockWidgetAreas)
+ return self.dockWidget
+
+ class SettingsWidget(QtGui.QWidget):
+ """ plugins should subclass this"""
+ plugin = None
+ settings = None
+
+ def __init__(self, plugin):
+ QtGui.QWidget.__init__(self)
+ self.plugin = plugin
+ self.settings = QtCore.QSettings(ORGNAME, APPNAME)
+
+ def save_settings(self):
+ """ reimplement this"""
+ self.plugin.saveSettings()
+
+ def _add_widget(self, widget, label = '', tooltip = ''):
+ """adds a widget with label"""
+ if not self.layout():
+ logging.error('Attempted to call add_widget with no layout set.')
+ widget.setToolTip(tooltip)
+ layout = QtGui.QHBoxLayout()
+ layout.addWidget(QtGui.QLabel(label))
+ layout.addWidget(widget)
+ self.layout().addLayout(layout)
+
+ def get_settings_widget(self):
+ """Should return subclassed SettingsWidget."""
+ return
+
+ def resetSettingCache(self):
+ #self.settings=None
+ self.settingsWidget=None
+
+ def _getPluginClassname(self, cl):
+ """Returns the name of a plugin (without 'plugin'-prefix)"""
+ return str(cl).split('.')[-1].lower()[len('plugin'):]
+
+ def _getDockWidget(self):
+ """Override this one."""
+ return None
+ 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/nephilim/clSong.py b/nephilim/clSong.py
new file mode 100644
index 0000000..9b38076
--- /dev/null
+++ b/nephilim/clSong.py
@@ -0,0 +1,85 @@
+from PyQt4 import QtCore
+from misc import ORGNAME, APPNAME, sec2min
+import os
+
+# compare two songs with respect to their album
+def isSameAlbum(song1, song2):
+ 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
+
+ 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 TypeError:
+ self._data['track']=-1
+ break
+ self._data['track']=int(self._data['track'])
+
+ # ensure all string-values are utf-8 encoded
+ for tag in self._data.keys():
+ if isinstance(self._data[tag], str):
+ self._data[tag]=unicode(self._data[tag], "utf-8")
+ if 'time' in self._data:
+ self._data['time'] = int(self._data['time'])
+ self._data['timems'] = '%i:%i'%(self._data['time'] / 60, self._data['time'] % 60)
+ self._data['length'] = sec2min(self._data['time'])
+
+ 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 getTrack(self):
+ """Get the track."""
+ return self.getTag('track')
+
+ def getAlbum(self):
+ """Get the album."""
+ return self.getTag('album')
+
+ 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.__str__() in self.__str__().lower()
+
+ 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__()
+
+ return default
+
+ def expand_tags(self, str):
+ """Expands tags in form $tag in str."""
+ ret = str
+ for tag in self._data:
+ ret = ret.replace('$' + tag, unicode(self._data[tag]))
+ return ret
+
diff --git a/nephilim/default_layout b/nephilim/default_layout
new file mode 100644
index 0000000..5755674
--- /dev/null
+++ b/nephilim/default_layout
Binary files differ
diff --git a/nephilim/misc.py b/nephilim/misc.py
new file mode 100644
index 0000000..b07360d
--- /dev/null
+++ b/nephilim/misc.py
@@ -0,0 +1,85 @@
+from PyQt4 import QtCore, QtGui
+from htmlentitydefs import name2codepoint as n2cp
+import re
+import urllib2, cookielib
+import socket
+import unicodedata
+
+import logging
+
+socket.setdefaulttimeout(8)
+
+appIcon = 'gfx/icon.png'
+APPNAME = 'nephilim'
+ORGNAME = 'nephilim'
+
+def doEvents():
+ """Make some time for necessary events."""
+ QtCore.QEventLoop().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)
+
+def numeric_compare(x, y):
+ 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)))
+
+def toAscii(ustr):
+ if type(ustr)==str:
+ return ustr
+ return unicodedata.normalize('NFKD', ustr).encode('ascii', 'ignore')
+
+def substEntity(match):
+ 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()
+
+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]
+
+
+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)
diff --git a/nephilim/mpclient.py b/nephilim/mpclient.py
new file mode 100644
index 0000000..26b4d13
--- /dev/null
+++ b/nephilim/mpclient.py
@@ -0,0 +1,364 @@
+from PyQt4 import QtCore
+from clSong import Song
+from traceback import print_exc
+from misc import *
+import mpd
+from threading import Thread
+
+class MPClient(QtCore.QObject):
+ """This 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
+
+ # objects used for comparison with previous value
+ _curSongID = None
+ _curTime = None
+ _curState = None
+ _curVolume = None
+ _updatings_db = None
+ _cur_plist_id = 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
+ 'onPlaylistChange' : '',
+ }
+
+ 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=[]
+ self._cur_plist_id = -1
+
+ 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 = mpd.MPDClient()
+ self._client.connect(host, port)
+ except IOError:
+ 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 listPlaylist(self):
+ """Returns the current playlist."""
+ if not self.isConnected():
+ return []
+ return self._curPlaylist
+
+ def listLibrary(self):
+ """Returns the library."""
+ if not self.isConnected():
+ return []
+ return self._curLib
+
+ def getCurrentSong(self):
+ """Returns the current playing song."""
+ if self.isConnected()==False:
+ return None
+ return self._curSong
+
+ def updateDB(self, paths = None):
+ if not paths:
+ return self._client.update()
+ 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
+ else:
+ ret['length'] = 0
+ ret['time'] = 0
+ return ret
+ except Exception, d:
+ print_exc()
+ return None
+
+ def get_outputs(self):
+ """returns audio outputs"""
+ if self.isConnected():
+ return self._retrieve(self._client.outputs)
+ else:
+ return []
+ def set_output(self, output_id, state):
+ """set audio output output_id to state"""
+ if state:
+ self._client.enableoutput(output_id)
+ else:
+ self._client.disableoutput(output_id)
+
+ def urlhandlers(self):
+ if not self.isConnected():
+ return []
+ else:
+ return self._client.urlhandlers()
+
+ def repeat(self, val):
+ if isinstance(val, bool):
+ val = 1 if val else 0
+ self._client.repeat(val)
+ def random(self,val):
+ if isinstance(val, bool):
+ val = 1 if val else 0
+ 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
+
+ self._retrMutex.unlock()
+ return ret
+
+ def isPlaying(self):
+ return self.getStatus()['state'] == 'play'
+
+ def play(self, id):
+ """Play song with ID $id."""
+ self._playCalled = True
+ if id:
+ 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."""
+ if self._curSongID > 0:
+ 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 clear_playlist(self):
+ """Removes all songs from current playlist."""
+ self._client.clear()
+ self._updatePlaylist()
+
+ def addToPlaylist(self, paths):
+ """Add all files in $paths to the current playlist."""
+ try:
+ self._client.command_list_ok_begin()
+ for path in paths:
+ self._client.addid(unicode(path))
+ ret = self._client.command_list_end()
+ self._updatePlaylist()
+ if self._curState == 'stop':
+ self.play(ret[0])
+ except mpd.CommandError:
+ logging.error('Cannot add some files, check permissions.')
+
+ 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 add_listener(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."""
+ song = self._retrieve(self._client.currentsong)
+ if not song:
+ self._curSong = None
+ else:
+ self._curSong = Song(song)
+
+ 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)
+
+ 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."
+ status = self.getStatus()
+
+ if status == None:
+ self._client=None
+ self._raiseEvent('onDisconnect', None)
+ self.killTimer(self._timerID)
+ return
+
+ self._updateCurrentSong()
+ " check if song has changed"
+ song = self._curSong
+ if song:
+ curID = song.getID()
+ else:
+ curID = -1
+
+ 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 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
+
+ if 'playlist' in status:
+ cur_plist_id = int(status['playlist'])
+ if cur_plist_id != self._cur_plist_id:
+ self._updatePlaylist()
+ self._raiseEvent('onPlaylistChange', None)
+ self._cur_plist_id = cur_plist_id
+
+ " 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')
diff --git a/nephilim/mpd.py b/nephilim/mpd.py
new file mode 100644
index 0000000..2d14bb6
--- /dev/null
+++ b/nephilim/mpd.py
@@ -0,0 +1,360 @@
+# Python MPD client library
+# Copyright (C) 2008 J. Alexander Treuman <jat@spatialrift.net>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import socket
+
+
+HELLO_PREFIX = "OK MPD "
+ERROR_PREFIX = "ACK "
+SUCCESS = "OK"
+NEXT = "list_OK"
+
+
+class MPDError(Exception):
+ pass
+
+class ConnectionError(MPDError):
+ pass
+
+class ProtocolError(MPDError):
+ pass
+
+class CommandError(MPDError):
+ pass
+
+class CommandListError(MPDError):
+ pass
+
+
+class _NotConnected(object):
+ def __getattr__(self, attr):
+ return self._dummy
+
+ def _dummy(*args):
+ raise ConnectionError("Not connected")
+
+class MPDClient(object):
+ def __init__(self):
+ self.iterate = False
+ self._reset()
+ self._commands = {
+ # Admin Commands
+ "disableoutput": self._getnone,
+ "enableoutput": self._getnone,
+ "kill": None,
+ "update": self._getitem,
+ # Informational Commands
+ "status": self._getobject,
+ "stats": self._getobject,
+ "outputs": self._getoutputs,
+ "commands": self._getlist,
+ "notcommands": self._getlist,
+ "tagtypes": self._getlist,
+ "urlhandlers": self._getlist,
+ # Database Commands
+ "find": self._getsongs,
+ "list": self._getlist,
+ "listall": self._getdatabase,
+ "listallinfo": self._getdatabase,
+ "lsinfo": self._getdatabase,
+ "search": self._getsongs,
+ "count": self._getobject,
+ # Playlist Commands
+ "add": self._getnone,
+ "addid": self._getitem,
+ "clear": self._getnone,
+ "currentsong": self._getobject,
+ "delete": self._getnone,
+ "deleteid": self._getnone,
+ "load": self._getnone,
+ "rename": self._getnone,
+ "move": self._getnone,
+ "moveid": self._getnone,
+ "playlist": self._getplaylist,
+ "playlistinfo": self._getsongs,
+ "playlistid": self._getsongs,
+ "plchanges": self._getsongs,
+ "plchangesposid": self._getchanges,
+ "rm": self._getnone,
+ "save": self._getnone,
+ "shuffle": self._getnone,
+ "swap": self._getnone,
+ "swapid": self._getnone,
+ "listplaylist": self._getlist,
+ "listplaylistinfo": self._getsongs,
+ "playlistadd": self._getnone,
+ "playlistclear": self._getnone,
+ "playlistdelete": self._getnone,
+ "playlistmove": self._getnone,
+ "playlistfind": self._getsongs,
+ "playlistsearch": self._getsongs,
+ # Playback Commands
+ "crossfade": self._getnone,
+ "next": self._getnone,
+ "pause": self._getnone,
+ "play": self._getnone,
+ "playid": self._getnone,
+ "previous": self._getnone,
+ "random": self._getnone,
+ "repeat": self._getnone,
+ "seek": self._getnone,
+ "seekid": self._getnone,
+ "setvol": self._getnone,
+ "stop": self._getnone,
+ "volume": self._getnone,
+ # Miscellaneous Commands
+ "clearerror": self._getnone,
+ "close": None,
+ "password": self._getnone,
+ "ping": self._getnone,
+ }
+
+ def __getattr__(self, attr):
+ try:
+ retval = self._commands[attr]
+ except KeyError:
+ raise AttributeError("'%s' object has no attribute '%s'" %
+ (self.__class__.__name__, attr))
+ return lambda *args: self._docommand(attr, args, retval)
+
+ def _docommand(self, command, args, retval):
+ if self._commandlist is not None and not callable(retval):
+ raise CommandListError("%s not allowed in command list" % command)
+ self._writecommand(command, args)
+ if self._commandlist is None:
+ if callable(retval):
+ return retval()
+ return retval
+ self._commandlist.append(retval)
+
+ def _writeline(self, line):
+ self._wfile.write(("%s\n" % line).encode('utf-8'))
+ self._wfile.flush()
+
+ 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))
+ self._writeline(u" ".join(parts))
+
+ def _readline(self):
+ line = self._rfile.readline()
+ if not line.endswith("\n"):
+ raise ConnectionError("Connection lost while reading line")
+ line = line.rstrip("\n")
+ if line.startswith(ERROR_PREFIX):
+ error = line[len(ERROR_PREFIX):].strip()
+ raise CommandError(error)
+ if self._commandlist is not None:
+ if line == NEXT:
+ return
+ if line == SUCCESS:
+ raise ProtocolError("Got unexpected '%s'" % SUCCESS)
+ elif line == SUCCESS:
+ return
+ return line
+
+ def _readitem(self, separator):
+ line = self._readline()
+ if line is None:
+ return
+ item = line.split(separator, 1)
+ if len(item) < 2:
+ raise ProtocolError("Could not parse item: '%s'" % line)
+ return item
+
+ def _readitems(self, separator=": "):
+ item = self._readitem(separator)
+ while item:
+ yield item
+ item = self._readitem(separator)
+ raise StopIteration
+
+ def _readlist(self):
+ seen = None
+ for key, value in self._readitems():
+ if key != seen:
+ if seen is not None:
+ raise ProtocolError("Expected key '%s', got '%s'" %
+ (seen, key))
+ seen = key
+ yield value
+ raise StopIteration
+
+ def _readplaylist(self):
+ for key, value in self._readitems(":"):
+ yield value
+ raise StopIteration
+
+ def _readobjects(self, delimiters=[]):
+ obj = {}
+ for key, value in self._readitems():
+ key = key.lower()
+ if obj:
+ if key in delimiters:
+ yield obj
+ obj = {}
+ elif obj.has_key(key):
+ if not isinstance(obj[key], list):
+ obj[key] = [obj[key], value]
+ else:
+ obj[key].append(value)
+ continue
+ obj[key] = value
+ if obj:
+ yield obj
+ raise StopIteration
+
+ def _readcommandlist(self):
+ for retval in self._commandlist:
+ yield retval()
+ self._commandlist = None
+ self._getnone()
+ raise StopIteration
+
+ def _wrapiterator(self, iterator):
+ if not self.iterate:
+ return list(iterator)
+ return iterator
+
+ def _getnone(self):
+ line = self._readline()
+ if line is not None:
+ raise ProtocolError("Got unexpected return value: '%s'" % line)
+
+ def _getitem(self):
+ items = list(self._readitems())
+ if len(items) != 1:
+ return
+ return items[0][1]
+
+ def _getlist(self):
+ return self._wrapiterator(self._readlist())
+
+ def _getplaylist(self):
+ return self._wrapiterator(self._readplaylist())
+
+ def _getobject(self):
+ objs = list(self._readobjects())
+ if not objs:
+ return {}
+ return objs[0]
+
+ def _getobjects(self, delimiters):
+ return self._wrapiterator(self._readobjects(delimiters))
+
+ def _getsongs(self):
+ return self._getobjects(["file"])
+
+ def _getdatabase(self):
+ return self._getobjects(["file", "directory", "playlist"])
+
+ def _getoutputs(self):
+ return self._getobjects(["outputid"])
+
+ def _getchanges(self):
+ return self._getobjects(["cpos"])
+
+ def _getcommandlist(self):
+ try:
+ return self._wrapiterator(self._readcommandlist())
+ except CommandError:
+ self._commandlist = None
+ raise
+
+ def _hello(self):
+ line = self._rfile.readline()
+ if not line.endswith("\n"):
+ raise ConnectionError("Connection lost while reading MPD hello")
+ line = line.rstrip("\n")
+ if not line.startswith(HELLO_PREFIX):
+ raise ProtocolError("Got invalid MPD hello: '%s'" % line)
+ self.mpd_version = line[len(HELLO_PREFIX):].strip()
+
+ def _reset(self):
+ self.mpd_version = None
+ self._commandlist = None
+ self._sock = None
+ self._rfile = _NotConnected()
+ self._wfile = _NotConnected()
+
+ def connect(self, host, port):
+ if self._sock:
+ raise ConnectionError("Already connected")
+ msg = "getaddrinfo returns an empty list"
+ try:
+ flags = socket.AI_ADDRCONFIG
+ except AttributeError:
+ flags = 0
+ if port == None: #assume Unix domain socket
+ try:
+ self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self._sock.connect(host)
+ except socket.error, msg:
+ if self._sock:
+ self._sock.close()
+ self._sock = None
+ else:
+ for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
+ socket.SOCK_STREAM, socket.IPPROTO_TCP,
+ flags):
+ af, socktype, proto, canonname, sa = res
+ try:
+ self._sock = socket.socket(af, socktype, proto)
+ self._sock.connect(sa)
+ except socket.error, msg:
+ if self._sock:
+ self._sock.close()
+ self._sock = None
+ continue
+ break
+ if not self._sock:
+ raise socket.error(msg)
+ self._rfile = self._sock.makefile("rb")
+ self._wfile = self._sock.makefile("wb")
+ try:
+ self._hello()
+ except:
+ self.disconnect()
+ raise
+
+ def disconnect(self):
+ self._rfile.close()
+ self._wfile.close()
+ self._sock.close()
+ self._reset()
+
+ def command_list_ok_begin(self):
+ if self._commandlist is not None:
+ raise CommandListError("Already in command list")
+ self._writecommand("command_list_ok_begin")
+ self._commandlist = []
+
+ def command_list_end(self):
+ if self._commandlist is None:
+ raise CommandListError("Not in command list")
+ self._writecommand("command_list_end")
+ return self._getcommandlist()
+
+
+def escape(text):
+ return text.replace("\\", "\\\\").replace('"', '\\"')
+
+
+# vim: set expandtab shiftwidth=4 softtabstop=4 textwidth=79:
diff --git a/nephilim/plugins/AlbumCover.py b/nephilim/plugins/AlbumCover.py
new file mode 100644
index 0000000..426df96
--- /dev/null
+++ b/nephilim/plugins/AlbumCover.py
@@ -0,0 +1,304 @@
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import QVariant
+from traceback import print_exc
+import os
+import shutil
+import logging
+
+from ..clPlugin import Plugin
+from ..misc import ORGNAME, APPNAME
+
+# FETCH MODES
+AC_NO_FETCH = 0
+AC_FETCH_LOCAL_DIR = 1
+AC_FETCH_INTERNET = 2
+
+class wgAlbumCover(QtGui.QLabel):
+ " container for the image"
+ img = None
+ imgLoaded = False
+ p = None
+ cover_dirname = None
+ cover_filepath = None
+ menu = None
+ def __init__(self, p, parent=None):
+ QtGui.QWidget.__init__(self,parent)
+ self.p=p
+ self.setAlignment(QtCore.Qt.AlignCenter)
+
+ # popup menu
+ self.menu = QtGui.QMenu("album")
+ select_file_action = self.menu.addAction('Select cover file...')
+ self.connect(select_file_action, QtCore.SIGNAL('triggered()'), self.select_cover_file)
+ fetch_amazon_action = self.menu.addAction('Fetch cover from Amazon.')
+ self.connect(fetch_amazon_action, QtCore.SIGNAL('triggered()'), self.fetch_amazon)
+
+ def mousePressEvent(self, event):
+ if event.button()==QtCore.Qt.RightButton:
+ self.menu.popup(event.globalPos())
+
+ def select_cover_file(self):
+ try:
+ song = self.p.mpclient.getCurrentSong()
+ file = QtGui.QFileDialog.getOpenFileName(self
+ , "Select album cover for %s - %s"%(song.getArtist(), song.getAlbum())
+ , self.cover_dirname
+ , ""
+ )
+ if file:
+ shutil.copy(file, self.cover_filepath)
+ else:
+ return
+ except IOError:
+ logging.info("Error setting cover file.")
+ self.refresh()
+
+ def get_cover(self):
+ if self.imgLoaded:
+ return self.pixmap()
+ return None
+
+ def refresh(self, params = None):
+ logging.info("refreshing cover")
+ song = self.p.mpclient.getCurrentSong()
+ if not song:
+ self.clear()
+ self.update()
+ return
+
+ dirname = unicode(self.p.settings.value(self.p.getName() + '/coverdir').toString())
+ self.cover_dirname = dirname.replace('$musicdir', self.p.settings.value('MPD/music_dir').toString()).replace('$songdir', os.path.dirname(song.getFilepath()))
+ filebase = unicode(self.p.settings.value(self.p.getName() + '/covername').toString())
+ self.cover_filepath = os.path.join(self.cover_dirname, song.expand_tags(filebase).replace(os.path.sep, '_'))
+ self.fetchCover(song)
+
+ def fetchCover(self, song):
+ """Fetch cover (from internet or local dir)"""
+ # set default cover
+
+ if not os.path.exists(self.cover_filepath):
+ success = False
+ for i in [0, 1]:
+ src = self.p.settings.value(self.p.getName() + '/action%i'%i).toInt()[0]
+ if src != AC_NO_FETCH:
+ if self.fetchCoverSrc(song, src):
+ success = True
+ break
+ if not success:
+ self.imgLoaded = False
+ self.setPixmap(QtGui.QPixmap('gfx/no-cd-cover.png').scaled(self.size(), QtCore.Qt.KeepAspectRatio,
+ QtCore.Qt.SmoothTransformation))
+ return
+
+ try:
+ self.setPixmap(QtGui.QPixmap(self.cover_filepath).scaled(self.size(), QtCore.Qt.KeepAspectRatio,
+ QtCore.Qt.SmoothTransformation))
+ self.imgLoaded = True
+ logging.info("cover set!")
+ except IOError:
+ logging.warning("Error loading album cover" + self.cover_filepath)
+
+ self.update()
+
+ def getLocalACPath(self, song):
+ """Get the local path of an albumcover."""
+ covers = ['cover', 'album', 'front']
+
+ # fetch gfx extensions
+ exts = QtGui.QImageReader().supportedImageFormats()
+ exts = map(lambda ext: '*.' + unicode(ext), exts)
+
+ # fetch cover album titles
+ filter = []
+ for cover in covers:
+ for ext in exts:
+ filter.append(cover.strip() + ext)
+
+ dir = QtCore.QDir(self.cover_dirname)
+ if not dir:
+ logging.warning('Error opening directory' + self.cover_dirname)
+ return None;
+ dir.setNameFilters(filter)
+ files = dir.entryList()
+ if files:
+ return unicode(dir.filePath(files[0]))
+ # if this failed, try any supported image
+ dir.setNameFilters(exts)
+ files = dir.entryList()
+ if files:
+ return unicode(dir.filePath(files[0]))
+ logging.info("done probing: no matching albumcover found")
+ return None
+
+ def fetch_amazon(self):
+ self.fetchCoverSrc(self.p.mpclient.getCurrentSong(), AC_FETCH_INTERNET)
+ self.refresh()
+
+ def fetchCoverSrc(self, song, src):
+ """Fetch the album cover for $song from $src."""
+ if not src in [AC_FETCH_INTERNET, AC_FETCH_LOCAL_DIR]:
+ logging.warning("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()
+ logging.info("fetch from Amazon")
+ if not coverURL:
+ logging.info("not found on Amazon")
+ return False
+ # read the url, i.e. retrieve image data
+ img=urllib.urlopen(coverURL)
+ # open file, and write the read of img!
+ f=open(self.cover_filepath,'wb')
+ f.write(img.read())
+ f.close()
+ return True
+ except:
+ logging.info("failed to download cover from Amazon")
+ print_exc()
+ return False
+
+ if src == AC_FETCH_LOCAL_DIR:
+ file=self.getLocalACPath(song)
+ try:
+ shutil.copy(file, self.cover_filepath)
+ return True
+ except:
+ logging.info("Failed to create cover file")
+ return False
+
+class pluginAlbumCover(Plugin):
+ o = None
+ DEFAULTS = {'coverdir' : '$musicdir/$songdir', 'covername' : '.cover_mpclient_$artist_$album',
+ 'action0' : 1, 'action1' : 1}
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'AlbumCover')
+
+ def _load(self):
+ self.o = wgAlbumCover(self, None)
+ self.mpclient.add_listener('onSongChange' , self.o.refresh)
+ self.mpclient.add_listener('onReady' , self.o.refresh)
+ self.mpclient.add_listener('onDisconnect' , self.o.refresh)
+ self.mpclient.add_listener('onStateChange', self.o.refresh)
+ 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 _getDockWidget(self):
+ return self._createDock(self.o)
+
+ class SettingsWidgetAlbumCover(Plugin.SettingsWidget):
+ actions = []
+ coverdir = None
+ covername = None
+
+ def __init__(self, plugin):
+ Plugin.SettingsWidget.__init__(self, plugin)
+ self.settings.beginGroup(self.plugin.getName())
+
+ self.actions = [QtGui.QComboBox(), QtGui.QComboBox()]
+ for i,action in enumerate(self.actions):
+ action.addItem("No action.")
+ action.addItem("Local dir")
+ action.addItem("Amazon")
+ action.setCurrentIndex(self.settings.value('action' + str(i)).toInt()[0])
+
+ self.coverdir = QtGui.QLineEdit(self.settings.value('coverdir').toString())
+ self.covername = QtGui.QLineEdit(self.settings.value('covername').toString())
+
+ self.setLayout(QtGui.QVBoxLayout())
+ self._add_widget(self.actions[0], 'Action 0')
+ self._add_widget(self.actions[1], 'Action 1')
+ self._add_widget(self.coverdir, 'Cover directory',
+ 'Where should %s store covers.\n'
+ '$musicdir will be expanded to path to MPD music library\n'
+ '$songdir will be expanded to path to the song (relative to $musicdir'
+ %APPNAME)
+ self._add_widget(self.covername, 'Cover filename', 'Filename for %s cover files.'%APPNAME)
+ self.settings.endGroup()
+
+ def save_settings(self):
+ self.settings.beginGroup(self.plugin.getName())
+ self.settings.setValue('action0', QVariant(self.actions[0].currentIndex()))
+ self.settings.setValue('action1', QVariant(self.actions[1].currentIndex()))
+ self.settings.setValue('coverdir', QVariant(self.coverdir.text()))
+ self.settings.setValue('covername', QVariant(self.covername.text()))
+ self.settings.endGroup()
+ self.plugin.o.refresh()
+
+ def get_settings_widget(self):
+ return self.SettingsWidgetAlbumCover(self)
+
+
+# This is the amazon cover fetcher using their webservice api
+# Thank you, http://www.semicomplete.com/scripts/albumcover.py
+import re
+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
+
+ 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)
diff --git a/nephilim/plugins/Filebrowser.py b/nephilim/plugins/Filebrowser.py
new file mode 100644
index 0000000..0a2908d
--- /dev/null
+++ b/nephilim/plugins/Filebrowser.py
@@ -0,0 +1,47 @@
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import QVariant
+import os
+
+from ..clPlugin import Plugin
+from ..misc import ORGNAME, APPNAME
+
+class pluginFilebrowser(Plugin):
+ view = None
+ model = None
+
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Filebrowser')
+
+ def _load(self):
+ self.model = QtGui.QDirModel()
+ self.model.setFilter(QtCore.QDir.AllDirs|QtCore.QDir.AllEntries)
+ self.model.setSorting(QtCore.QDir.DirsFirst)
+
+ self.view = QtGui.QListView()
+ self.view.setModel(self.model)
+ self.view.setRootIndex(self.model.index(os.path.expanduser('~')))
+ self.view.setSelectionMode(QtGui.QTreeWidget.ExtendedSelection)
+ self.view.connect(self.view, QtCore.SIGNAL('activated(const QModelIndex&)'), self.item_activated)
+
+ def _unload(self):
+ self.view = None
+ self.model = None
+
+ def getInfo(self):
+ return 'A file browser that allows adding files not in collection.'
+
+ def _getDockWidget(self):
+ return self._createDock(self.view)
+
+ def item_activated(self, index):
+ if self.model.hasChildren(index):
+ self.view.setRootIndex(index)
+ else:
+ if not 'file://' in self.mpclient.urlhandlers():
+ self.setStatus('file:// handler not available. Connect via unix domain sockets.')
+ return
+ paths = []
+ for index in self.view.selectedIndexes():
+ paths.append(u'file://' + self.model.filePath(index))
+ self.mpclient.addToPlaylist(paths)
+
diff --git a/nephilim/plugins/Library.py b/nephilim/plugins/Library.py
new file mode 100644
index 0000000..60490d9
--- /dev/null
+++ b/nephilim/plugins/Library.py
@@ -0,0 +1,155 @@
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import QVariant
+
+from ..clPlugin import Plugin
+from ..misc import ORGNAME, APPNAME
+
+class pluginLibrary(Plugin):
+ o=None
+ DEFAULTS = {'modes' : 'artist\n'\
+ 'artist/album\n'\
+ 'artist/date/album\n'\
+ 'genre\n'\
+ 'genre/artist\n'\
+ 'genre/artist/album\n'}
+
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Library')
+ self.settings = QtCore.QSettings(ORGNAME, APPNAME)
+ def _load(self):
+ self.o = LibraryWidget(self)
+ self.mpclient.add_listener('onReady', self.o.fill_library)
+ self.mpclient.add_listener('onDisconnect', self.o.fill_library)
+ self.mpclient.add_listener('onUpdateDBFinish', self.o.fill_library)
+ def _unload(self):
+ self.o = None
+
+ def getInfo(self):
+ return "List showing all the songs allowing filtering and grouping."
+
+ def _getDockWidget(self):
+ return self._createDock(self.o)
+
+ class SettingsWidgetLibrary(Plugin.SettingsWidget):
+ modes = None
+ def __init__(self, plugin):
+ Plugin.SettingsWidget.__init__(self, plugin)
+ self.setLayout(QtGui.QVBoxLayout())
+
+ self.modes = QtGui.QTextEdit()
+ self.modes.insertPlainText(self.settings.value(self.plugin.getName() + '/modes').toString())
+ self.layout().addWidget(self.modes)
+
+ def save_settings(self):
+ self.settings.setValue(self.plugin.getName() + '/modes', QVariant(self.modes.toPlainText()))
+ self.plugin.o.refresh_modes()
+
+ def get_settings_widget(self):
+ return self.SettingsWidgetLibrary(self)
+
+class LibraryWidget(QtGui.QWidget):
+ library = None
+ search_txt = None
+ modes = None
+ settings = None
+ plugin = None
+
+ def __init__(self, plugin):
+ QtGui.QWidget.__init__(self)
+ self.plugin = plugin
+ self.settings = QtCore.QSettings(ORGNAME, APPNAME)
+ self.settings.beginGroup(self.plugin.getName())
+
+ self.modes = QtGui.QComboBox()
+ self.refresh_modes()
+ self.connect(self.modes, QtCore.SIGNAL('activated(int)'), self.modes_activated)
+
+ self.search_txt = QtGui.QLineEdit()
+ self.connect(self.search_txt, QtCore.SIGNAL('textChanged(const QString&)'),
+ self.filter_changed)
+ self.connect(self.search_txt, QtCore.SIGNAL('returnPressed()'), self.add_filtered)
+
+ #construct the library
+ self.library = QtGui.QTreeWidget()
+ self.library.setColumnCount(1)
+ self.library.setAlternatingRowColors(True)
+ self.library.setSelectionMode(QtGui.QTreeWidget.ExtendedSelection)
+ self.library.headerItem().setHidden(True)
+ self.fill_library()
+ self.connect(self.library, QtCore.SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.add_selection)
+
+ self.setLayout(QtGui.QVBoxLayout())
+ self.layout().setSpacing(2)
+ self.layout().setMargin(0)
+ self.layout().addWidget(self.modes)
+ self.layout().addWidget(self.search_txt)
+ self.layout().addWidget(self.library)
+
+ def refresh_modes(self):
+ self.modes.clear()
+ for mode in self.settings.value('/modes').toString().split('\n'):
+ self.modes.addItem(mode)
+ self.modes.setCurrentIndex(self.settings.value('current_mode').toInt()[0])
+
+
+ def fill_library(self, params = None):
+ self.library.clear()
+
+ #build a tree from library
+ tree = [{},self.library.invisibleRootItem()]
+ for song in self.plugin.mpclient.listLibrary():
+ cur_item = tree
+ for part in str(self.modes.currentText()).split('/'):
+ tag = song.getTag(part)
+ if isinstance(tag, list):
+ tag = tag[0] #FIXME hack to make songs with multiple genres work.
+ if not tag:
+ tag = 'Unknown'
+ if tag in cur_item[0]:
+ cur_item = cur_item[0][tag]
+ else:
+ it = QtGui.QTreeWidgetItem([tag])
+ cur_item[1].addChild(it)
+ cur_item[0][tag] = [{}, it]
+ cur_item = cur_item[0][tag]
+ it = QtGui.QTreeWidgetItem(['%02d %s'%(song.getTrack() if song.getTrack() else 0,
+ song.getTitle() if song.getTitle() else song.getFilepath())], 1000)
+ it.setData(0, QtCore.Qt.UserRole, QVariant(song))
+ cur_item[1].addChild(it)
+
+ self.library.sortItems(0, QtCore.Qt.AscendingOrder)
+
+ def filter_changed(self, text):
+ items = self.library.findItems(text, QtCore.Qt.MatchContains|QtCore.Qt.MatchRecursive)
+ for i in range(self.library.topLevelItemCount()):
+ self.library.topLevelItem(i).setHidden(True)
+ for item in items:
+ while item.parent():
+ item = item.parent()
+ item.setHidden(False)
+ self.filtered_items = items
+
+ def add_filtered(self):
+ self.library.clearSelection()
+ for item in self.filtered_items:
+ item.setSelected(True)
+ self.add_selection()
+ self.library.clearSelection()
+ self.search_txt.clear()
+
+ def add_selection(self):
+ paths = []
+ for item in self.library.selectedItems():
+ self.item_to_playlist(item, paths)
+ self.plugin.mpclient.addToPlaylist(paths)
+
+ def item_to_playlist(self, item, add_queue):
+ if item.type() == 1000:
+ add_queue.append(item.data(0, QtCore.Qt.UserRole).toPyObject().getFilepath())
+ else:
+ for i in range(item.childCount()):
+ self.item_to_playlist(item.child(i), add_queue)
+
+ def modes_activated(self):
+ self.settings.setValue('current_mode', QVariant(self.modes.currentIndex()))
+ self.fill_library()
diff --git a/nephilim/plugins/Lyrics.py b/nephilim/plugins/Lyrics.py
new file mode 100644
index 0000000..1983f2a
--- /dev/null
+++ b/nephilim/plugins/Lyrics.py
@@ -0,0 +1,74 @@
+from PyQt4 import QtGui,QtCore
+from PyQt4.QtCore import QVariant
+
+from thread import start_new_thread
+import logging
+
+from ..clPlugin import Plugin
+from .. import LyricWiki_client
+
+class wgLyrics(QtGui.QWidget):
+ " contains the lyrics"
+ txtView = None # text-object
+ p = None # plugin
+ def __init__(self, p, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.p = p
+ self.curLyrics = ''
+
+ self.txtView = QtGui.QTextEdit(self)
+ self.txtView.setReadOnly(True)
+
+ self.setLayout(QtGui.QVBoxLayout())
+ self.layout().setSpacing(0)
+ self.layout().setMargin(0)
+ self.layout().addWidget(self.txtView)
+
+ def set_lyrics(self, song, lyrics):
+ self.txtView.clear()
+ if song:
+ self.txtView.insertHtml('<b>%s</b>\n<br /><u>%s</u><br />'\
+ '<br />\n\n'%(song.getTitle(), song.getArtist()))
+ if lyrics:
+ self.txtView.insertPlainText(lyrics)
+
+class pluginLyrics(Plugin):
+ o = None
+ DEFAULTS = {'sites' : ['lyricwiki'], 'lyricdir' : '$musicdir/$songdir',
+ 'lyricname' : '.lyric_mpclient_$artist_$album_$song'}
+
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Lyrics')
+ self.addListener('onSongChange', self.refresh)
+ self.addListener('onReady', self.refresh)
+ def _load(self):
+ self.o = wgLyrics(self)
+ 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 = None):
+ lyrics = None
+ song = self.mpclient.getCurrentSong()
+ if not song:
+ self.o.set_lyrics(None, None)
+ return
+ for site in self.settings.value(self.getName() + '/sites').toStringList():
+ lyrics = eval('self.fetch_%s(song)'%site)
+ if lyrics:
+ self.o.set_lyrics(song, lyrics)
+ return
+
+ def fetch_lyricwiki(self, song):
+ soap = LyricWiki_client.LyricWikiBindingSOAP("http://lyricwiki.org/server.php")
+ req = LyricWiki_client.getSongRequest()
+ req.Artist = song.getArtist()
+ req.Song = song.getTitle()
+ result = soap.getSong(req)
+
+ return result.Return.Lyrics
+
diff --git a/nephilim/plugins/Notify.py b/nephilim/plugins/Notify.py
new file mode 100644
index 0000000..b3c2285
--- /dev/null
+++ b/nephilim/plugins/Notify.py
@@ -0,0 +1,152 @@
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import QVariant
+from traceback import print_exc
+
+from ..misc import sec2min, ORGNAME, APPNAME
+from ..clPlugin import Plugin
+from .. import plugins
+
+NOTIFY_PRIORITY_SONG = 1
+NOTIFY_PRIORITY_VOLUME = 2
+
+class winNotify(QtGui.QWidget):
+ _timerID=None
+ winMain=None
+ p=None
+
+ _current_priority = 0
+
+ timer=None
+
+ cover_label = None
+ text_label = None
+
+ def __init__(self, p, winMain, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.p=p
+ self.winMain=winMain
+
+ layout = QtGui.QHBoxLayout()
+ self.cover_label = QtGui.QLabel()
+ self.text_label = QtGui.QLabel()
+ self.text_label.setWordWrap(True)
+ layout.addWidget(self.cover_label)
+ layout.addWidget(self.text_label)
+ self.setLayout(layout)
+
+ self.setWindowFlags(QtCore.Qt.ToolTip | QtCore.Qt.WindowStaysOnTopHint)
+ self.setWindowOpacity(0.7)
+
+ font = QtGui.QFont()
+ font.setPixelSize(20)
+ self.setFont(font)
+
+ def mousePressEvent(self, event):
+ self.hide()
+
+ def show(self, text, time = 3, priority = 0):
+ if not priority >= self._current_priority:
+ return
+ self._current_priority = priority
+
+ cover = plugins.getPlugin('albumcover').getWidget().get_cover()
+ if cover:
+ self.cover_label.setPixmap(cover.scaledToHeight(self.fontInfo().pixelSize()*4))
+ else:
+ self.cover_label.clear()
+
+ self.text_label.setText(text)
+ if self._timerID:
+ self.killTimer(self._timerID)
+ self._timerID=self.startTimer(500)
+ self.timer = time*2
+ self.resize(self.layout().sizeHint())
+ self.centerH()
+ self.setVisible(True)
+ self.timerEvent(None)
+
+ def hide(self):
+ if self._timerID:
+ self.killTimer(self._timerID)
+ self._timerID=None
+ self.setHidden(True)
+ self._current_priority = -1
+
+ def centerH(self):
+ screen = QtGui.QDesktopWidget().screenGeometry()
+ size = self.geometry()
+ self.move((screen.width()-size.width())/2, 100)
+
+ def timerEvent(self, event):
+ self.timer-=1
+ if self.timer<=0:
+ self.hide()
+ self.update()
+
+class pluginNotify(Plugin):
+ o=None
+ DEFAULTS = {'songformat' : '$track - $artist - $title ($album) [$length]',
+ 'timer' : 3}
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Notify')
+ self.addListener('onSongChange', self.onSongChange)
+ self.addListener('onReady', self.onReady)
+ self.addListener('onDisconnect', self.onDisconnect)
+ self.addListener('onStateChange', self.onStateChange)
+ self.addListener('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):
+ song = self.mpclient.getCurrentSong()
+ if not song:
+ return
+ self.settings.beginGroup(self.name)
+ self.o.show(song.expand_tags(self.settings.value('songformat').toString()), self.settings.value('timer').toInt()[0],
+ NOTIFY_PRIORITY_SONG)
+ self.settings.endGroup()
+
+ def onReady(self, params):
+ self.o.show('mpclientpc loaded!', self.settings.value(self.name + '/timer').toInt()[0])
+
+ def onDisconnect(self, params):
+ self.o.show('Disconnected!', self.settings.value(self.name + '/timer').toInt()[0])
+
+ def onStateChange(self, params):
+ self.o.show(params['newState'], self.settings.value(self.name + '/timer').toInt()[0])
+
+ def onVolumeChange(self, params):
+ self.o.show('Volume: %i%%'%(params['newVolume']), self.settings.value(self.name + '/timer').toInt()[0], priority = NOTIFY_PRIORITY_VOLUME)
+
+ class SettingsWidgetNotify(Plugin.SettingsWidget):
+ format = None
+ timer = None
+
+ def __init__(self, plugin):
+ Plugin.SettingsWidget.__init__(self, plugin)
+ self.settings.beginGroup(self.plugin.getName())
+
+ self.format = QtGui.QLineEdit(self.settings.value('songformat').toString())
+
+ self.timer = QtGui.QLineEdit(self.settings.value('timer').toString())
+ self.timer.setValidator(QtGui.QIntValidator(self.timer))
+
+ self.setLayout(QtGui.QVBoxLayout())
+ self.layout().addWidget(self.format)
+ self.layout().addWidget(self.timer)
+ self.settings.endGroup()
+
+ def save_settings(self):
+ self.settings.beginGroup(self.plugin.getName())
+ self.settings.setValue('songformat', QVariant(self.format.text()))
+ self.settings.setValue('timer', QVariant(self.timer.text().toInt()[0]))
+ self.settings.endGroup()
+ self.plugin.onSongChange(None)
+
+ def get_settings_widget(self):
+ return self.SettingsWidgetNotify(self)
diff --git a/nephilim/plugins/PlayControl.py b/nephilim/plugins/PlayControl.py
new file mode 100644
index 0000000..c5690be
--- /dev/null
+++ b/nephilim/plugins/PlayControl.py
@@ -0,0 +1,169 @@
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import QVariant
+import logging
+
+from ..misc import ORGNAME, APPNAME, Button
+from ..clPlugin import Plugin
+
+class wgPlayControl(QtGui.QToolBar):
+ """Displays controls for interacting with playing, like play, volume ..."""
+ " control buttons"
+ btnPlayPause = None
+ btnStop = None
+ btnPrevious = None
+ btnNext = None
+ slrVolume=None
+ repeat = None
+ random = None
+ p = None
+
+ " queued songs: int*"
+ queuedSongs=[]
+ # what mode where we in before the queue started?
+ beforeQueuedMode=None
+
+ class VolumeSlider(QtGui.QSlider):
+
+ def __init__(self, parent):
+ QtGui.QSlider.__init__(self, parent)
+ self.setOrientation(parent.orientation())
+ self.setMaximum(100)
+ self.setToolTip('Volume control')
+
+ def paintEvent(self, event):
+ painter = QtGui.QPainter(self)
+ painter.eraseRect(self.rect())
+
+ grad = QtGui.QLinearGradient(0, 0, self.width(), self.height())
+ grad.setColorAt(0, self.palette().color(QtGui.QPalette.Window))
+ grad.setColorAt(1, self.palette().color(QtGui.QPalette.Highlight))
+ if self.orientation() == QtCore.Qt.Horizontal:
+ rect = QtCore.QRect(0, 0, self.width() * self.value() / self.maximum(), self.height())
+ else:
+ rect = QtCore.QRect(0, self.height() * (1 - float(self.value()) / self.maximum()), self.width(), self.height())
+ painter.fillRect(rect, QtGui.QBrush(grad))
+
+ def __init__(self, p, parent = None):
+ QtGui.QToolBar.__init__(self, parent)
+ self.setMovable(True)
+ self.p = p
+
+ self.slrVolume = self.VolumeSlider(self)
+ self.connect(self.slrVolume, QtCore.SIGNAL('valueChanged(int)'),self.onVolumeSliderChange)
+
+ 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.random = QtGui.QPushButton(QtGui.QIcon('gfx/random.png'), '', self)
+ self.random.setToolTip('Random')
+ self.random.setCheckable(True)
+ self.connect(self.random, QtCore.SIGNAL('toggled(bool)'), self.p.mpclient.random)
+
+ self.repeat = QtGui.QPushButton(QtGui.QIcon('gfx/repeat.png'), '', self)
+ self.repeat.setToolTip('Repeat')
+ self.repeat.setCheckable(True)
+ self.connect(self.repeat, QtCore.SIGNAL('toggled(bool)'), self.p.mpclient.repeat)
+
+ self.addWidget(self.btnPlayPause)
+ self.addWidget(self.btnStop)
+ self.addWidget(self.btnPrevious)
+ self.addWidget(self.btnNext)
+ self.addSeparator()
+ self.addWidget(self.slrVolume)
+ self.addSeparator()
+ self.addWidget(self.random)
+ self.addWidget(self.repeat)
+
+ self.connect(self, QtCore.SIGNAL('orientationChanged(Qt::Orientation)'), self.slrVolume.setOrientation)
+
+ # queue gets loaded in _load of pluginPlayControl
+ self.queuedSongs=[]
+
+ def addSongsToQueue(self, songs):
+ self.queuedSongs.extend(songs)
+
+ def onStateChange(self, params):
+ status = self.p.mpclient.getStatus()
+
+ if status['state'] == 'play':
+ self.btnPlayPause.changeIcon('gfx/media-playback-pause.svg')
+ self.btnPlayPause.setToolTip('pauze')
+ elif status['state'] == 'pause' or status['state'] == 'stop':
+ self.btnPlayPause.changeIcon('gfx/media-playback-start.svg')
+ self.btnPlayPause.setToolTip('play')
+
+ def onVolumeChange(self, params):
+ self.slrVolume.setValue(params['newVolume'])
+
+ def onBtnPlayPauseClick(self):
+ status=self.p.mpclient.getStatus()
+ if status['state']=='play':
+ self.p.mpclient.pause()
+ logging.info("Toggling playback")
+ elif status['state']=='stop':
+ self.p.mpclient.play(None)
+ logging.info("Pausing playback")
+ else:
+ self.p.mpclient.resume()
+ def onBtnStopClick(self):
+ self.p.mpclient.stop()
+ logging.info("Stopping playback")
+ def onBtnPreviousClick(self):
+ self.p.mpclient.previous()
+ logging.info("Playing previous")
+ def onBtnNextClick(self):
+ self.p.mpclient.next()
+ logging.info("Playing next")
+ def onVolumeSliderChange(self):
+ v=self.slrVolume.value()
+ self.p.mpclient.setVolume(v)
+ if v<=1:
+ mode='mute'
+ else:
+ mode=('0', 'min', 'med', 'max')[int(3*v/100)]
+
+ # save and load the queue
+ def saveQueue(self):
+ # save the ids as a list of space-separated numbers
+ logging.info("saving queue")
+ self.p.settings.setValue(self.p.getName() + '/queue', QVariant(str(self.queuedSongs)[1:-1].replace(',', '')))
+ def loadQueue(self):
+ # just read all the numbers!
+ logging.info("loading queue")
+ self.queuedSongs=[]
+ i=0
+ ids=self.p.settings.value(self.p.getName() + '/queue').toString().split(' ')
+ for id in ids:
+ try:
+ self.queuedSongs.append(int(id))
+ except:
+ pass
+
+class pluginPlayControl(Plugin):
+ o=None
+ DEFAULTS = {'queue' : ''}
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'PlayControl')
+ self.addListener('onStateChange', self.onStateChange)
+ self.addListener('onVolumeChange', self.onVolumeChange)
+ self.addListener('onReady', self.onStateChange)
+ def _load(self):
+ self.o = wgPlayControl(self, None)
+ self.o.loadQueue()
+ self.winMain.addToolBar(QtCore.Qt.TopToolBarArea, self.o)
+ def _unload(self):
+ self.o.saveQueue()
+ self.winMain.removeToolBar(self.o)
+ 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 onVolumeChange(self, params):
+ self.o.onVolumeChange(params)
diff --git a/nephilim/plugins/Playlist.py b/nephilim/plugins/Playlist.py
new file mode 100644
index 0000000..2258701
--- /dev/null
+++ b/nephilim/plugins/Playlist.py
@@ -0,0 +1,92 @@
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import QVariant
+
+from ..clPlugin import Plugin
+
+# Dependencies:
+# playcontrol
+class pluginPlaylist(Plugin):
+ o = None
+ DEFAULTS = {'columns': ['track', 'title', 'artist',
+ 'date', 'album', 'length']}
+
+ def __init__(self, winMain):
+ Plugin.__init__(self, winMain, 'Playlist')
+ def _load(self):
+ self.o = PlaylistWidget(self)
+ self.mpclient.add_listener('onPlaylistChange', self.on_playlist_change)
+ self.mpclient.add_listener('onDisconnect', self.on_playlist_change)
+ self.mpclient.add_listener('onReady', self.on_playlist_change)
+
+ def _unload(self):
+ self.o = None
+ def getInfo(self):
+ return "The playlist showing the songs that will be played."
+
+ def _getDockWidget(self):
+ return self._createDock(self.o)
+
+ def on_playlist_change(self, params = None):
+ self.o.fill_playlist()
+
+class PlaylistWidget(QtGui.QWidget):
+ plugin = None
+ playlist = None
+
+ def __init__(self, plugin):
+ QtGui.QWidget.__init__(self)
+ self.plugin = plugin
+
+ self.playlist = self.Playlist(self.plugin)
+
+ self.setLayout(QtGui.QVBoxLayout())
+ self.layout().setSpacing(0)
+ self.layout().setMargin(0)
+ self.layout().addWidget(self.playlist)
+
+ class Playlist(QtGui.QTreeWidget):
+ song = None
+ plugin = None
+
+ def __init__(self, plugin):
+ QtGui.QTreeWidget.__init__(self)
+ self.plugin = plugin
+
+ self.setSelectionMode(QtGui.QTreeWidget.ExtendedSelection)
+ self.setAlternatingRowColors(True)
+ self.setRootIsDecorated(False)
+ columns = self.plugin.settings.value(self.plugin.getName() + '/columns').toStringList()
+ self.setColumnCount(len(columns))
+ self.setHeaderLabels(columns)
+ self.header().restoreState(self.plugin.settings.value(self.plugin.getName() + '/header_state').toByteArray())
+ self.connect(self, QtCore.SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self._song_activated)
+ self.connect(self.header(), QtCore.SIGNAL('geometriesChanged()'), self._save_state)
+
+ def _save_state(self):
+ self.plugin.settings.setValue(self.plugin.getName() + '/header_state', QVariant(self.header().saveState()))
+
+ def _song_activated(self, item):
+ self.plugin.mpclient.play(item.data(0, QtCore.Qt.UserRole).toPyObject().getID())
+
+ def fill(self):
+ columns = self.plugin.settings.value(self.plugin.getName() + '/columns').toStringList()
+ self.clear()
+ for song in self.plugin.mpclient.listPlaylist():
+ item = QtGui.QTreeWidgetItem()
+ for i in range(len(columns)):
+ item.setText(i, unicode(song.getTag(str(columns[i]))))
+ item.setData(0, QtCore.Qt.UserRole, QVariant(song))
+ self.addTopLevelItem(item)
+
+ def keyPressEvent(self, event):
+ if event.matches(QtGui.QKeySequence.Delete):
+ ids = []
+ for item in self.selectedItems():
+ ids.append(item.data(0, QtCore.Qt.UserRole).toPyObject().getID())
+
+ self.plugin.mpclient.deleteFromPlaylist(ids)
+ else:
+ QtGui.QTreeWidget.keyPressEvent(self, event)
+
+ def fill_playlist(self):
+ self.playlist.fill()
diff --git a/nephilim/plugins/Systray.py b/nephilim/plugins/Systray.py
new file mode 100644
index 0000000..49a1414
--- /dev/null
+++ b/nephilim/plugins/Systray.py
@@ -0,0 +1,125 @@
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import QVariant
+
+from ..clPlugin import Plugin
+from ..misc import sec2min, ORGNAME, APPNAME, appIcon
+
+class pluginSystray(Plugin):
+ DEFAULTS = {'format': '$track - $title by $artist on $album ($length)'}
+ 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.addListener('onSongChange', self.update)
+ self.addListener('onReady', self.update)
+ self.addListener('onConnect', self.update)
+ self.addListener('onDisconnect', self.update)
+ self.addListener('onTimeChange', self.update) # TODO only update this when necessary, i.e. mouse-hover etc
+ self.appIcon=QtGui.QIcon(appIcon)
+
+ def _load(self):
+ self.format = self.settings.value(self.name + '/format').toString()
+ 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
+ self.plugin.mpclient.setVolume(self.plugin.mpclient.getVolume() + numSteps)
+ event.accept()
+ return True
+ return False
+
+ self.o=QtGui.QSystemTrayIcon(QtGui.QIcon(appIcon), self.winMain)
+ self.eventObj=SystrayWheelEventObject()
+ self.eventObj.plugin = self
+ self.o.installEventFilter(self.eventObj)
+ self.winMain.connect(self.o, QtCore.SIGNAL('activated (QSystemTrayIcon::ActivationReason)')
+ , self.onSysTrayClick)
+ self.o.show()
+
+ 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 mpclientpc icon in the systray."
+
+ def update(self, params):
+ status = self.mpclient.getStatus()
+ if not status:
+ return
+ song = self.mpclient.getCurrentSong()
+
+ values={'state':''}
+ values['state']={'play':'playing', 'stop':'stopped', 'pause':'paused'}[status['state']]
+ if 'time' in status:
+ values['length']=sec2min(status['length'])
+ values['time']=sec2min(status['time'])
+
+ if song:
+ self.o.setToolTip(song.expand_tags(self.format))
+ else:
+ self.o.setToolTip("mpclientpc 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, self.winMain.palette().brush(QtGui.QPalette.Base))
+ self.appIcon.paint(painter, 1, 0, 63, 64)
+ self.o.setIcon(QtGui.QIcon(self.pixmap))
+ elif not song:
+ self.time=None
+ self.o.setIcon(QtGui.QIcon(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 self.mpclient.isPlaying():
+ self.mpclient.pause()
+ else:
+ self.mpclient.resume()
+
+ class SettingsWidgetSystray(Plugin.SettingsWidget):
+ format = None
+
+ def __init__(self, plugin):
+ Plugin.SettingsWidget.__init__(self, plugin)
+
+ self.format = QtGui.QLineEdit(self.settings.value(self.plugin.getName() + '/format').toString())
+
+ self.setLayout(QtGui.QVBoxLayout())
+ self._add_widget(self.format, 'Tooltip format')
+
+ def save_settings(self):
+ self.settings.beginGroup(self.plugin.getName())
+ self.settings.setValue('format', QVariant(self.format.text()))
+ self.settings.endGroup()
+
+ def get_settings_widget(self):
+ return self.SettingsWidgetSystray(self)
diff --git a/nephilim/plugins/__init__.py b/nephilim/plugins/__init__.py
new file mode 100644
index 0000000..affbf45
--- /dev/null
+++ b/nephilim/plugins/__init__.py
@@ -0,0 +1,97 @@
+import os
+import sys
+import logging
+
+# { className => [module, className, instance, msg] }
+_plugins=None
+PLUGIN_MODULE=0
+PLUGIN_CLASS=1
+PLUGIN_INSTANCE=2
+PLUGIN_MSG=3
+
+
+class IPlaylist:
+ def ensureVisible(self, song_id):
+ raise Exception("TODO implement")
+
+def loadPlugins():
+ """(Re)load all modules in the plugins directory."""
+ global _plugins
+ _plugins={}
+ for file in os.listdir('nephilim/plugins'):
+ if file[-3:]=='.py' and file!='__init__.py':
+ name=file[:-3] # name without ext
+ mod='nephilim.plugins.%s'%(name) # mod name
+ className='plugin%s'%(name) # classname
+
+ _plugins[className.lower()]=[mod, className, None, None]
+ loadPlugin(className, None)
+
+def setPluginMessage(name, msg):
+ global _plugins
+ try:
+ _plugins[name.lower()][PLUGIN_MSG]=msg
+ except:
+ try:
+ _plugins["plugin%s"%(name.lower())][PLUGIN_MODULE]=msg
+ except:
+ pass
+
+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
+
+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
+ logging.warning("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 listImplementors(interface, loaded = True):
+ """Return a list of plugin-instances that implement an interface"""
+ global _plugins
+ return map(lambda plugin: plugin[PLUGIN_INSTANCE]
+ , filter(lambda plugin: isinstance(plugin[PLUGIN_INSTANCE], interface)
+ and ((loaded != None and plugin[PLUGIN_INSTANCE].loaded == loaded) or (loaded == None)), _plugins.values()))
+
+def listPlugins():
+ """Get the list of plugins available as { className => [mod, className, instance, msg] }."""
+ global _plugins
+ return _plugins
+
+
+loadPlugins()
diff --git a/nephilim/winConnect.py b/nephilim/winConnect.py
new file mode 100644
index 0000000..45789ac
--- /dev/null
+++ b/nephilim/winConnect.py
@@ -0,0 +1,88 @@
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import QVariant
+import time
+from misc import *
+from traceback import print_exc
+
+class winConnect(QtGui.QWidget):
+ txtHost=None
+ txtPort=None
+ lblInfo=None
+ _timerID=None
+ mpclient = None
+ settings = None
+
+
+ def __init__(self,parent):
+ QtGui.QWidget.__init__(self, parent)
+ self.settings = QtCore.QSettings(ORGNAME, APPNAME)
+ self.txtHost = QtGui.QLineEdit(self.settings.value('MPD/host', QVariant('localhost')).toString())
+ self.txtPort = QtGui.QLineEdit(self.settings.value('MPD/port', QVariant('6600')).toString())
+ self.txtPort.setValidator(QtGui.QIntValidator(1, 65535, self.txtPort))
+ self.lblInfo = QtGui.QLabel("connecting ...")
+ self.mpclient = parent.mpclient
+
+ frame=QtGui.QVBoxLayout()
+ inputs=QtGui.QHBoxLayout()
+
+ frame.addLayout(inputs)
+ frame.addWidget(self.lblInfo)
+
+ inputs.addWidget(self.txtHost)
+ inputs.addWidget(self.txtPort)
+
+ self.setWindowIcon(QtGui.QIcon(appIcon))
+ self.setWindowTitle('Connect to mpd')
+ self.setLayout(frame)
+ self.resize(200,80)
+ self.center()
+ doEvents()
+
+ self.mpclient.add_listener('onReady', self.onReady)
+ self.mpclient.add_listener('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 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()
+ self.settings.setValue('MPD/host', QVariant(self.txtHost.text()))
+ self.settings.setValue('MPD/port', QVariant(self.txtPort.text()))
+ self.txtHost.setEnabled(False)
+ self.txtPort.setEnabled(False)
+ doEvents()
+
+ def onReady(self, params):
+ self.hide()
+
+ def timerEvent(self, event):
+ host = str(self.txtHost.text())
+ port = int(self.txtPort.text()) if self.txtPort.text() else None
+
+ self.lblInfo.setText('Trying to connect to '+host+':'+str(port)+' ...')
+ doEvents()
+ self.mpclient.connect(host, port)
+ doEvents()
+
+ def windowActivationChange(self, bool):
+ self.activateWindow()
+ self.raise_()
+ doEvents()
diff --git a/nephilim/winMain.py b/nephilim/winMain.py
new file mode 100644
index 0000000..accf864
--- /dev/null
+++ b/nephilim/winMain.py
@@ -0,0 +1,285 @@
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import QVariant
+from traceback import print_exc
+
+from misc import *
+from mpclient import MPClient
+
+import plugins
+
+from winConnect import winConnect
+from winSettings import winSettings
+import logging
+
+DEFAULT_LAYOUT_FILE = 'default_layout'
+
+class winMain(QtGui.QMainWindow):
+ """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
+
+ " MPD object"
+ mpclient = None
+
+ " Statusbar objects"
+ statuslabel = None
+ time_slider = None
+ time_label = None
+
+ settings = None
+ def __init__(self, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.settings = QtCore.QSettings(ORGNAME, APPNAME)
+ self.mpclient = MPClient()
+
+ self.wConnect=winConnect(self)
+
+ # statusbar
+ self.statusBar()
+ self.statuslabel = QtGui.QLabel()
+ self.time_slider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
+ self.time_slider.setMaximumWidth(self.width()/4)
+ self.connect(self.time_slider, QtCore.SIGNAL('sliderReleased()'), self.on_time_slider_change)
+ self.time_label = QtGui.QLabel()
+ self.time_label.duration = '0:00'
+
+ self.statusBar().addWidget(self.statuslabel)
+ self.statusBar().addPermanentWidget(self.time_label)
+ self.statusBar().addPermanentWidget(self.time_slider)
+
+ mBar = QtGui.QMenuBar() # create a menubar
+ # File menu
+ m = mBar.addMenu("File")
+ m.setTearOffEnabled(True)
+ # connect
+ self.mConnect=m.addAction('Connect ...', self.wConnect.monitor)
+ self.mConnect.setIcon(QtGui.QIcon(appIcon))
+ # disconnect
+ self.mDisconnect=m.addAction('Disconnect', self.mpclient.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 layout
+ self.mLayout=mBar.addMenu("Layout")
+ self.mLayout.setTearOffEnabled(True)
+
+ # create a toolbar for the main menu
+ menu_toolbar = QtGui.QToolBar()
+ menu_toolbar.addWidget(mBar)
+ self.addToolBar(QtCore.Qt.TopToolBarArea, menu_toolbar)
+
+ showWinSettings = 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:
+ if self.settings.value(plugin.getName() + '/load') == None:
+ showWinSettings = True
+ if self.settings.value(plugin.getName() + '/load', QVariant(True)).toBool():
+ # load new plugins by default
+ try:
+ plugin.load()
+ except Exception, e:
+ plugins.setPluginMessage(plugin.getName(), "Exception while loading %s: %s"%(plugin.getName(), str(e)))
+ showWinSettings=True
+
+ self.updateLayoutMenu()
+ self.setDockOptions(QtGui.QMainWindow.AllowNestedDocks \
+ |QtGui.QMainWindow.AllowTabbedDocks \
+ |QtGui.QMainWindow.VerticalTabs)
+ self.setDockNestingEnabled(True)
+ self.restoreGeometry(self.settings.value('geometry').toByteArray())
+ self.restoreLayout()
+
+ " add event handlers"
+ self.mpclient.add_listener('onReady', self.onReady)
+ self.mpclient.add_listener('onConnect', self.onConnect)
+ self.mpclient.add_listener('onDisconnect', self.onDisconnect)
+ self.mpclient.add_listener('onUpdateDBStart', self.onUpdateDBStart)
+ self.mpclient.add_listener('onUpdateDBFinish', self.onUpdateDBFinish)
+ self.mpclient.add_listener('onSongChange', self.on_song_change)
+ self.mpclient.add_listener('onStateChange', self.update_state_messages)
+ self.mpclient.add_listener('onTimeChange', self.on_time_change)
+
+ self.enableAll(True)
+ self.setWindowIcon(QtGui.QIcon(appIcon))
+ # set icon in system tray
+ self.wConnect.monitor()
+
+ self.update_state_messages()
+ self.show()
+ if showWinSettings:
+ 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()
+
+ self.settings.setValue('geometry', QVariant(self.saveGeometry()))
+ self.settings.sync()
+ 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(self.settings.value('show_titlebars', QVariant(True)).toBool())
+ 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:
+ self.settings.setValue('show_titlebars', QVariant(True))
+ else:
+ self.settings.setValue('show_titlebars', QVariant(False))
+ 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:
+ if dock in self.docks:
+ 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()
+ menu = QtGui.QMainWindow.createPopupMenu(self)
+ if menu:
+ actions = menu.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, 5000)
+ logging.info(status)
+
+ def saveLayout(self):
+ self.settings.setValue('layout', QVariant(self.saveState()))
+ def restoreLayout(self):
+ layout = self.settings.value('layout').toByteArray()
+ if not layout:
+ try:
+ layout = open(DEFAULT_LAYOUT_FILE, 'rb').read()
+ except IOError:
+ logging.error("Error reading default layout.")
+ return
+ self.restoreState(layout)
+
+ def showWinSettings(self):
+ if not self.wSettings:
+ self.wSettings=winSettings(self)
+ self.wSettings.show()
+ self.wSettings.raise_()
+
+ def onReady(self, params):
+ self.initialiseData()
+
+ def onConnect(self, params):
+ logging.info("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 initialiseData(self):
+ self.enableAll(True)
+ self.setStatus("")
+ doEvents
+
+ def onDisconnect(self, params):
+ logging.info("Disconnected from MPD")
+ self.mDisconnect.setEnabled(False)
+ self.mConnect.setEnabled(True)
+ self.enableAll(False)
+ self.setStatus("You are disconnected. Choose File->Connect to reconnect!")
+
+ def onUpdateDBFinish(self, params):
+ self.setStatus('')
+ def onUpdateDBStart(self, params):
+ self.setStatus('Updating the database. Please wait ...')
+
+ def update_state_messages(self, params = None):
+ song = self.mpclient.getCurrentSong()
+ if song and self.mpclient.isPlaying():
+ self.setWindowTitle(song.getTitle() + " by " + song.getArtist())
+ self.statuslabel.setText("Now playing " + song.getTitle() + " by " + song.getArtist() + " on " + song.getAlbum())
+ else:
+ self.setWindowTitle(APPNAME)
+ self.statuslabel.setText("")
+
+ def on_time_slider_change(self):
+ self.mpclient.seek(self.time_slider.value())
+
+ def on_song_change(self, params):
+ status = self.mpclient.getStatus()
+ self.time_slider.setMaximum(status['length'])
+ self.time_slider.setEnabled(True)
+ self.time_label.duration = sec2min(status['length'])
+ self.update_state_messages(params)
+
+ def on_time_change(self, params):
+ if not self.time_slider.isSliderDown():
+ self.time_slider.setValue(params['newTime'])
+ self.time_label.setText(sec2min(params['newTime']) + '/' + self.time_label.duration)
diff --git a/nephilim/winSettings.py b/nephilim/winSettings.py
new file mode 100644
index 0000000..08873df
--- /dev/null
+++ b/nephilim/winSettings.py
@@ -0,0 +1,176 @@
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import QVariant
+import os
+
+from misc import ORGNAME, APPNAME, Button, appIcon, doEvents
+import plugins
+
+
+class winSettings(QtGui.QWidget):
+ btnSave=None
+ btnClose=None
+ lstPlugins=None
+
+ winMain=None
+ settings = None
+ settings_wg = []
+
+ class SettingsWidgetMPD(QtGui.QWidget):
+ mpclient = None
+ settings = None
+ host_txt = None
+ port_txt = None
+ lib_txt = None
+ update = None
+ outputs = None
+
+ def __init__(self, mpclient):
+ QtGui.QWidget.__init__(self)
+ self.settings = QtCore.QSettings(ORGNAME, APPNAME)
+ self.mpclient = mpclient
+
+ self.settings.beginGroup('MPD')
+ self.host_txt = QtGui.QLineEdit(self.settings.value('host', QVariant('localhost')).toString())
+ self.port_txt = QtGui.QLineEdit(self.settings.value('port', QVariant('6600')).toString())
+ self.lib_txt = QtGui.QLineEdit(self.settings.value('music_dir', QVariant(os.path.expanduser('~/music/'))).toString())
+ self.settings.endGroup()
+
+ self.update = QtGui.QPushButton('Update MPD database')
+ self.connect(self.update, QtCore.SIGNAL('clicked()'), self.update_db)
+
+ self.outputs = QtGui.QGroupBox('Audio outputs')
+ self.outputs.setLayout(QtGui.QVBoxLayout())
+ class Output(QtGui.QCheckBox):
+ id = None
+ mpclient = None
+ def change_state(self, state):
+ self.mpclient.set_output(self.id, state)
+
+ for output in self.mpclient.get_outputs():
+ box = Output(output['outputname'])
+ if output['outputenabled'] == '1':
+ box.setChecked(True)
+ else:
+ box.setChecked(False)
+ box.id = int(output['outputid'])
+ box.mpclient = self.mpclient
+ self.connect(box, QtCore.SIGNAL('stateChanged(int)'), box.change_state)
+ self.outputs.layout().addWidget(box)
+
+ self.setLayout(QtGui.QVBoxLayout())
+ self.layout().addWidget(self.host_txt)
+ self.layout().addWidget(self.port_txt)
+ self.layout().addWidget(self.lib_txt)
+ self.layout().addWidget(self.update)
+ self.layout().addWidget(self.outputs)
+
+ def save_settings(self):
+ self.settings.beginGroup('MPD')
+ self.settings.setValue('host', QVariant(self.host_txt.text()))
+ self.settings.setValue('port', QVariant(self.port_txt.text()))
+ self.settings.setValue('music_dir', QVariant(self.lib_txt.text()))
+ self.settings.endGroup()
+
+ def update_db(self):
+ self.mpclient.updateDB()
+
+ def __init__(self, winMain, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.settings = QtCore.QSettings(ORGNAME, APPNAME)
+ self.winMain = winMain
+
+ self.btnSave = Button('save all', self.onBtnSaveClick)
+ self.btnClose = Button('close', self.onBtnCloseClick)
+
+ tabWidget = QtGui.QTabWidget(parent)
+ self.settings_wg.append(self.SettingsWidgetMPD(self.winMain.mpclient))
+ tabWidget.addTab(self.settings_wg[-1], 'MPD settings')
+
+ self.lstPlugins = QtGui.QListWidget(self)
+ tabWidget.addTab(self.lstPlugins, 'plugins')
+ for k,entry in plugins.listPlugins().iteritems():
+ plugin=entry[plugins.PLUGIN_INSTANCE]
+ if plugin:
+ wg = plugin.get_settings_widget()
+ if wg:
+ self.settings_wg.append(wg)
+ tabWidget.addTab(self.settings_wg[-1], plugin.getName())
+ self.fillList()
+
+ self.setLayout(QtGui.QVBoxLayout())
+ self.layout().addWidget(tabWidget)
+
+ layoutButtons = QtGui.QHBoxLayout()
+ layoutButtons.addStretch()
+ layoutButtons.addWidget(self.btnSave)
+ layoutButtons.addWidget(self.btnClose)
+ self.layout().addLayout(layoutButtons)
+
+ self.connect(self.lstPlugins, QtCore.SIGNAL('itemChanged (QListWidgetItem*)'), self.onlstPluginItemChanged)
+
+ self.setWindowIcon(QtGui.QIcon(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:
+ if entry[plugins.PLUGIN_MSG]:
+ item=QtGui.QListWidgetItem("%s\t%s"%(entry[plugins.PLUGIN_CLASS], entry[plugins.PLUGIN_MSG]))
+ item.setCheckState(QtCore.Qt.Unchecked)
+ item.setTextColor(QtCore.Qt.red)
+ else:
+ 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 self.settings.value(plugin.getName() + '/load') == None:
+ # load new plugins by default
+ item.setTextColor(QtCore.Qt.blue)
+ self.settings.setValue(plugin.getName() + '/load', QtCore.QVariant(True))
+
+ 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):
+ for wg in self.settings_wg:
+ wg.save_settings()
+
+ def onBtnCloseClick(self):
+ self.close()
+ def onlstPluginItemChanged(self, item):
+ # check here if we have to load or unload the plugin!
+ toload = (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()
+ self.winMain.restoreLayout()
+ else:
+ plugin=plugins.getPlugin(className)
+ if plugin:
+ plugin.unload()
+ if plugin:
+ self.settings.setValue(plugin.getName() + '/load', QtCore.QVariant(toload))
+ def closeEvent(self, event):
+ map(lambda entry: entry[plugins.PLUGIN_INSTANCE] and entry[plugins.PLUGIN_INSTANCE].resetSettingCache(), plugins.listPlugins().values())
+ self.settings_wg = None
+ self.winMain.wSettings=None