Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 251 lines (212 sloc) 7.203 kB
4e8167f @albertz State: use nice unicode symbols as names
authored
1 # -*- coding: utf-8 -*-
cf3e61d @albertz license information
authored
2 # MusicPlayer, https://github.com/albertz/music-player
3 # Copyright (c) 2012, Albert Zeyer, www.az2000.de
4 # All rights reserved.
5 # This code is under the 2-clause BSD license, see License.txt in the root directory of this project.
4e8167f @albertz State: use nice unicode symbols as names
authored
6
cbb2e48 @albertz distribute code
authored
7 from utils import *
24e529e @albertz more on GUI
authored
8 import Traits
cbb2e48 @albertz distribute code
authored
9 from Song import Song
09f068a @albertz PersistentObject wip
authored
10 from collections import deque
c47aaf0 @albertz gui: buildControlList
authored
11 from threading import RLock
3ab4bfd @albertz some startup cleanup. and register state object in gui
authored
12 import appinfo
13 import gui
09f068a @albertz PersistentObject wip
authored
14
59cb576 @albertz RecentlyplayedList: implement new event interface
authored
15 class RecentlyplayedList(object):
c47aaf0 @albertz gui: buildControlList
authored
16 GuiLimit = 5
d3746f3 @albertz RecentlyplayedList.Limit -> 500
authored
17 Limit = 500
99a39ab @albertz more on persistent state
authored
18 def __init__(self, list=[], previous=None, index=0):
c47aaf0 @albertz gui: buildControlList
authored
19 self.lock = RLock()
99a39ab @albertz more on persistent state
authored
20 self.index = index
09f068a @albertz PersistentObject wip
authored
21 self.list = deque(list)
f8a1821 @albertz small fix. much faster startup
authored
22 # Be careful what we do with `previous` here. If it is not None,
23 # we expect it to be a PersistentObject and we really don't want
24 # to load it here, otherwise *all* of RecentlyplayedList would
25 # be unfolded!
26 # Even `PyObject_IsTrue(previous)` would load it, so just
27 # compare with `None`.
28 if previous is not None:
e1ab727 @albertz some fixes for PersistentObject
authored
29 if not getattr(previous, "_isPersistentObject", False):
30 # This was some bug from earlier... Fix it now.
31 print "Warning: RecentlyplayedList.previous is not a PersistentObject"
32 previous = PersistentObject(RecentlyplayedList, "recentlyplayed-%i.dat" % previous.index, persistentRepr=True)
33 elif not previous._persistentRepr:
34 # This was some bug from earlier... Fix it now.
35 print "Warning: RecentlyplayedList.previous not persistentRepr"
36 previous = PersistentObject(RecentlyplayedList, previous._filename, persistentRepr=True)
37 assert previous._isPersistentObject
38 assert previous._persistentRepr
da3ff1f @albertz small fix on RecentlyplayedList
authored
39 self.previous = previous
09f068a @albertz PersistentObject wip
authored
40 def append(self, song):
99a39ab @albertz more on persistent state
authored
41 if not song: return
c47aaf0 @albertz gui: buildControlList
authored
42 with self.lock:
da3ff1f @albertz small fix on RecentlyplayedList
authored
43 guiOldLen = len(self)
c47aaf0 @albertz gui: buildControlList
authored
44 self.list.append(song)
45 if len(self.list) >= self.Limit:
46 newList = PersistentObject(RecentlyplayedList, "recentlyplayed-%i.dat" % self.index, persistentRepr=True)
47 newList.index = self.index
48 newList.list = self.list
49 newList.previous = self.previous
50 newList.save()
51 self.index += 1
52 self.previous = newList
53 self.list = deque()
59cb576 @albertz RecentlyplayedList: implement new event interface
authored
54 self.onInsert.push(guiOldLen, song)
55 if guiOldLen == self.GuiLimit: self.onRemove.push(0)
ee0b6e1 @albertz RecentlyplayedList.getLastN
authored
56 def getLastN(self, n):
eef858e @albertz RecentlyplayedList better for GUI
authored
57 with self.lock:
58 #return list(self.list)[-n:] # not using this for now as a bit too heavy. I timeit'd it. this is 14 times slower for n=10, len(l)=10000
59 l = self.list
60 if n <= len(l):
61 return [l[-i] for i in range(1,n+1)]
62 else:
63 last = [l[-i] for i in range(1,len(l)+1)]
64 if self.previous:
65 last += self.previous.getLastN(n - len(l))
66 return last
09f068a @albertz PersistentObject wip
authored
67 def __repr__(self):
8a36155 @albertz more on persistent objects. recentlyplayed list and cursong should wo…
authored
68 return "RecentlyplayedList(list=%s, previous=%s, index=%i)" % (
c47aaf0 @albertz gui: buildControlList
authored
69 betterRepr(list(self.list)),
8a36155 @albertz more on persistent objects. recentlyplayed list and cursong should wo…
authored
70 betterRepr(self.previous),
71 self.index)
c47aaf0 @albertz gui: buildControlList
authored
72
59cb576 @albertz RecentlyplayedList: implement new event interface
authored
73 @initBy
74 def onInsert(self): return Event() # (index, value)
75 @initBy
76 def onRemove(self): return Event() # (index)
77 @initBy
78 def onClear(self): return Event() # ()
79
c47aaf0 @albertz gui: buildControlList
authored
80 def __getitem__(self, index):
81 with self.lock:
eef858e @albertz RecentlyplayedList better for GUI
authored
82 return self.getLastN(self.GuiLimit)[-index - 1]
c47aaf0 @albertz gui: buildControlList
authored
83 def __len__(self):
eef858e @albertz RecentlyplayedList better for GUI
authored
84 c = len(self.list)
85 if c >= self.GuiLimit: return self.GuiLimit
86 if self.previous:
87 c += len(self.previous)
88 return min(c, self.GuiLimit)
cbb2e48 @albertz distribute code
authored
89
388b13d @albertz move Actions
authored
90
91
99a39ab @albertz more on persistent state
authored
92 class State(object):
967e0bf @albertz state update handling
authored
93 def _updateCurSongHandler(self):
94 self.__class__.curSongStr.updateEvent(self).push()
95 self.__class__.curSongPos.updateEvent(self).push()
96 self.__class__.curSongDisplay.updateEvent(self).push()
97
98 def __init__(self):
99 self.__class__.curSong.updateEvent(self).register(self._updateCurSongHandler)
100
4c02cab @albertz cleanup and work-in-progress
authored
101 def playPauseUpdate(self, attrib):
a3ae64a @albertz UserAttrib updateHandlers
authored
102 if self.player.playing:
103 attrib.name = "❚❚"
104 else:
105 attrib.name = ""
106
967e0bf @albertz state update handling
authored
107 @UserAttrib(type=Traits.Action, name="", updateHandler=playPauseUpdate, addUpdateEvent=True)
7f14e24 @albertz new TraitType: Action
authored
108 def playPause(self):
109 self.player.playing = not self.player.playing
110
4503f8e @albertz more on GUI
authored
111 @UserAttrib(type=Traits.Action, name="▶▶|", alignRight=True)
7f14e24 @albertz new TraitType: Action
authored
112 def nextSong(self):
113 self.player.nextSong()
114
967e0bf @albertz state update handling
authored
115 @UserAttrib(type=Traits.OneLineText, alignRight=True, variableWidth=True, withBorder=True, addUpdateEvent=True)
4503f8e @albertz more on GUI
authored
116 @property
117 def curSongStr(self):
f0676a3 @albertz use calcBitmapThumbnail in GUI. somewhat hacky atm, though
authored
118 if not self.player.curSong: return ""
4503f8e @albertz more on GUI
authored
119 try: return self.player.curSong.userString
069f2cc @albertz small fix
authored
120 except Exception: return "???"
4503f8e @albertz more on GUI
authored
121
967e0bf @albertz state update handling
authored
122 @UserAttrib(type=Traits.OneLineText, alignRight=True, autosizeWidth=True, withBorder=True, addUpdateEvent=True)
f0676a3 @albertz use calcBitmapThumbnail in GUI. somewhat hacky atm, though
authored
123 @property
124 def curSongPos(self):
125 if not self.player.curSong: return ""
126 try: return formatTime(self.player.curSongPos) + " / " + formatTime(self.player.curSong.duration)
069f2cc @albertz small fix
authored
127 except Exception: return "???"
f0676a3 @albertz use calcBitmapThumbnail in GUI. somewhat hacky atm, though
authored
128
967e0bf @albertz state update handling
authored
129 @UserAttrib(type=Traits.SongDisplay, variableWidth=True, addUpdateEvent=True)
c5454df @albertz debugging and fixing
authored
130 def curSongDisplay(self): pass
f0676a3 @albertz use calcBitmapThumbnail in GUI. somewhat hacky atm, though
authored
131
7ae4ddb @albertz volume control
authored
132 @initBy
133 def _volume(self): return PersistentObject(float, "volume.dat", defaultArgs=(0.9,))
134
135 @UserAttrib(type=Traits.Real(min=0, max=2), alignRight=True, height=80, width=25)
136 @property
137 def volume(self):
138 return self._volume
139
140 @volume.callDeco.setter
141 def volume(self, updateValue):
142 self._volume = updateValue
143 self._volume.save()
144 self.player.volume = updateValue
145
5eeed86 @albertz gui: some focus/selection logic
authored
146 @UserAttrib(type=Traits.List, lowlight=True, autoScrolldown=True)
ed39ecc @albertz reformating State
authored
147 @initBy
148 def recentlyPlayedList(self): return PersistentObject(RecentlyplayedList, "recentlyplayed.dat")
149
967e0bf @albertz state update handling
authored
150 @UserAttrib(type=Traits.Object, spaceY=0, highlight=True, addUpdateEvent=True)
ed39ecc @albertz reformating State
authored
151 @initBy
054562e @albertz some more on UserAttrib
authored
152 def curSong(self): return PersistentObject(Song, "cursong.dat")
ed39ecc @albertz reformating State
authored
153
f1405f7 @albertz preferences window. variableHeight behavior change.
authored
154 @UserAttrib(type=Traits.Object, spaceY=0, variableHeight=True)
ed39ecc @albertz reformating State
authored
155 @initBy
3e9ad9c @albertz some refactoring and restructuring of GUI related stuff
authored
156 def queue(self):
157 import queue
158 return queue.queue
054562e @albertz some more on UserAttrib
authored
159
ed39ecc @albertz reformating State
authored
160 @initBy
161 def updates(self): return OnRequestQueue()
162
163 @initBy
24e529e @albertz more on GUI
authored
164 def player(self):
165 from player import loadPlayer
166 return loadPlayer(self)
eda13ba @albertz correct implementation of song artist, track, duration
authored
167
a0cf62d @albertz more reorganisation
authored
168 def quit(self):
3484a23 @albertz small update
authored
169 # XXX: Is this still used?
55367b3 @albertz comment
authored
170 # XXX: doesn't really work. OSX ignores the SIGINT if the cocoa mainloop runs
b2b6d07 @albertz State.quit always works
authored
171 def doQuit():
172 """ This works in all threads except the main thread. It will quit the whole app.
173 For more information about why we do it this way, read the comment in main.py.
174 """
175 import sys, os, signal
176 os.kill(0, signal.SIGINT)
438fb49 @albertz exit handling
authored
177 sys.stdin.close() # so that the terminal closes, if it is used
b2b6d07 @albertz State.quit always works
authored
178 import thread
179 thread.start_new_thread(doQuit, ())
a0cf62d @albertz more reorganisation
authored
180
ecf892b @albertz prefer higher rated songs
authored
181 # Only init new state if it is new, not at module reload.
182 try:
183 state
184 except NameError:
185 state = State()
7165e49 @albertz possibilities to reload modules at runtime
authored
186
560f652 @albertz window key shortcut
authored
187 gui.registerRootObj(obj=state, name="Main", title=appinfo.progname, priority=0, keyShortcut='1')
3ab4bfd @albertz some startup cleanup. and register state object in gui
authored
188
189
ecf892b @albertz prefer higher rated songs
authored
190 try:
191 modules
192 except NameError:
193 modules = []
194
195 def getModule(modname):
196 for m in modules:
197 if m.name == modname: return m
198 return None
199
200 for modname in [
7165e49 @albertz possibilities to reload modules at runtime
authored
201 "player",
35e5c6b @albertz make the queue handling an own module
authored
202 "queue",
7165e49 @albertz possibilities to reload modules at runtime
authored
203 "tracker",
78a651a @albertz restructuring tracker a bit. now we track skipCount and completedCoun…
authored
204 "tracker_lastfm",
7165e49 @albertz possibilities to reload modules at runtime
authored
205 "mediakeys",
206 "gui",
207 "stdinconsole",
c4c01cf @albertz add socketcontrol module
authored
208 "socketcontrol",
038790b @albertz State: new module mpdBackend
authored
209 "mpdBackend",
35fdc34 @albertz load notifications module
authored
210 "notifications",
74fa129 @albertz initial preloader code
authored
211 "preloader",
8b38b0c @albertz some work on the song db
authored
212 "songdb",
ecf892b @albertz prefer higher rated songs
authored
213 ]:
214 if not getModule(modname):
35fdc34 @albertz load notifications module
authored
215 modules.append(Module(modname))
216
7165e49 @albertz possibilities to reload modules at runtime
authored
217
218 def reloadModules():
ecf892b @albertz prefer higher rated songs
authored
219 # reload some custom random Python modules
3915b6a @albertz module reloading: also reload utils
authored
220 import utils
221 reload(utils)
ecf892b @albertz prefer higher rated songs
authored
222 import Song, State
223 reload(Song)
224 reload(State)
225 # reload all our modules
7165e49 @albertz possibilities to reload modules at runtime
authored
226 for m in modules:
227 m.reload()
58f01c9 @albertz simple about window
authored
228
229
230
231 class About(object):
232 @UserAttrib(type=Traits.OneLineText)
1a0255c @albertz small fix
authored
233 @property
58f01c9 @albertz simple about window
authored
234 def appname(self):
235 import appinfo
236 return appinfo.progname
237
238 @UserAttrib(type=Traits.OneLineText)
1a0255c @albertz small fix
authored
239 @property
58f01c9 @albertz simple about window
authored
240 def developer(self):
241 return "by Albert Zeyer"
242
243 @UserAttrib(type=Traits.Action, name="Homepage")
244 def homepage(self):
245 import gui
246 gui.about()
247
248 about = About()
249 gui.registerRootObj(obj=about, name="About", priority=-9)
250
Something went wrong with that request. Please try again.