New action to 'queue' a song to play next.
[python-gnt.git] / example / xmms / xmms_pl.py
blobba775a6183aea5d66ba24da5a57400a96e892bab
1 #!/usr/bin/env python
3 """
4 xmms-pl : Playlist explorer for XMMS2.
6 Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
8 This application is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
13 This application is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public
19 License along with this application; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301
21 USA
22 """
24 import xmmsclient
25 import xmmsclient.glib as xg
26 import xmmsclient.collections as xc
27 import gobject
28 import gnt
29 import os
30 import sys
31 import itertools
33 import treerow
34 import songedit
35 import common
36 from xmms_list import *
38 __version__ = "0.0.1alpha"
39 __author__ = "Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>"
40 __copyright__ = "Copyright 2007, Sadrul Habib Chowdhury"
41 __license__ = "GPL"
43 reload(sys)
44 sys.setdefaultencoding("utf-8")
46 class XPList(XList):
47 __gproperties__ = {
48 'name' : (gobject.TYPE_STRING, 'name of the playlist',
49 'name of the playlist', None, gobject.PARAM_READWRITE)
52 __gntbindings__ = {
53 'add-entry' : ('add_entry', gnt.KEY_INS),
54 'del-entry' : ('del_entry', gnt.KEY_DEL),
55 'switch-playlist' : ('switch_playlist', 'w'),
56 'create-playlist' : ('create_playlist', 'c'),
57 'place-tagged' : ('place_tagged', gnt.KEY_CTRL_P),
58 'clear-playlist' : ('clear_playlist', gnt.KEY_CTRL_U),
61 def __init__(self, xmms, name):
62 """The xmms connection, and the name of the playlist."""
63 XList.__init__(self, xmms)
64 self.name = name
65 self.win = None
66 self.init_playlist()
67 self.connect('context-menu', self.context_menu)
69 def do_get_property(self, prop):
70 if prop.name == 'name':
71 return self.name
72 raise AttributeError, 'unknown property %s' % prop.name
74 def do_set_property(self, prop, value):
75 if prop.name == 'name':
76 self.name = value
77 else:
78 raise AttributeError, 'unknown property %s' % prop.name
80 def context_menu(self, null):
81 """Popup a context menu with the available options."""
82 def perform_action(item, data):
83 # This is a little weird, so pay attention ...
84 # The callback can either bring out a new window, or a new menu.
85 # In the first scenario, we want the new window to be given focus.
86 # So we perform the action immediately.
87 # In the latter case, we need to first let the active menu hide,
88 # then perform the action, so we call the callback in the next
89 # iteration of the mainloop.
90 action, wait = data
91 def really_perform_action():
92 action(None)
93 return False
94 if wait:
95 gobject.timeout_add(0, really_perform_action)
96 else:
97 action(None)
98 menu = gnt.Menu(gnt.MENU_POPUP)
99 for text, action, wait in (('Edit Info', self.edit_entry, False),
100 ('Remove', self.del_entry, False),
101 ('Add Files...', self.add_entry, False),
102 ('Edit Columns ...', self.edit_columns, True),
103 ('Set Search Column ...', self.search_column, True),
104 ('Switch Playlist...', self.switch_playlist, True)
106 item = gnt.MenuItem(text)
107 item.connect('activate', perform_action, [action, wait])
108 menu.add_item(item)
109 self.position_menu(menu)
110 gnt.show_menu(menu)
112 def init_playlist(self):
113 self.load_playlist()
115 # Refresh the playlist if an entry is added/removed etc.
116 def refresh_playlist(result):
117 info = result.value()
118 if info['name'] != self.name: return # This playlist didn't change
119 if info['type'] == xmmsclient.PLAYLIST_CHANGED_REMOVE:
120 # Some entry was removed
121 rows = self.get_rows()
122 row = rows[info['position']]
123 self.remove(row)
124 elif info['type'] == xmmsclient.PLAYLIST_CHANGED_ADD or info['type'] == xmmsclient.PLAYLIST_CHANGED_INSERT:
125 # Some entry was added
126 position = info['position']
127 self.add_mlib_song(int(position), info['id'])
128 elif info['type'] == xmmsclient.PLAYLIST_CHANGED_MOVE:
129 old = info['position']
130 new = info['newposition']
131 # First, remove the entry
132 rows = self.get_rows()
133 row = rows[old]
134 self.remove(row)
135 # Now, find the new entry position
136 rows = self.get_rows()
137 if new > 0:
138 after = rows[new - 1]
139 else:
140 after = None
141 info = row.get_data('song-info')
142 [title, artist, album, time] = common.str_info(info)
143 # Add it back in the new position
144 self.add_row_after(row, [str(new + 1), title, artist, album, time], None, after)
145 elif info['type'] == xmmsclient.PLAYLIST_CHANGED_CLEAR:
146 self.remove_all()
147 self.draw()
148 else:
149 sys.stderr.write("Unhandled playlist update type " + str(info['type']) + " -- Please file a bug report.\n")
150 # XXX: just go ahead and refresh the entire list
151 self.load_playlist()
152 return
153 # Make sure the entry in the 'position' column is correct for the entries
154 rows = self.get_rows()
155 if rows is not None:
156 num = 1
157 for row in rows:
158 self.change_text(row, self.POSITION, str(num))
159 num = num + 1
160 self.xmms.broadcast_playlist_changed(refresh_playlist)
162 def load_playlist(self):
163 """Get the entries in the list, and populate the tree."""
164 def got_list_of_songs_in_pl(result):
165 list = result.value()
166 pos = itertools.count(1)
167 for song in list:
168 self.add_mlib_song(int(pos.next()), song)
169 if self.win:
170 self.win.set_title("XMMS2 Playlist - " + str(self.name))
171 self.win.draw()
172 self.remove_all()
173 self.draw()
174 self.xmms.playlist_list_entries(self.name, got_list_of_songs_in_pl)
176 def show(self):
177 """Show the playlist inside a window."""
178 win = self.win = gnt.Box(homo = False, vert = True)
179 win.set_toplevel(True)
180 win.set_title("XMMS2 Playlist - " + str(self.name))
181 win.add_widget(self)
182 width, height = gnt.screen_size()
183 self.set_size(width, height)
184 win.show()
186 def add_entry(self, null):
187 """Add new song(s) in the playlist."""
188 fl = gnt.FileSel()
189 fl.set_multi_select(True)
190 def destroy_fl(b, dlg):
191 dlg.destroy()
192 fl.cancel_button().connect('activate', destroy_fl, fl)
193 def add_files(fl, path, files, dlg):
194 for file in dlg.get_selected_multi_files():
195 self.xmms.playlist_add_url('file://' + file, self.name)
196 dlg.destroy()
197 fl.connect('file_selected', add_files, fl)
198 fl.show()
200 def del_entry(self, null):
201 """Delete selected entry from the playlist."""
202 sel = self.get_selection_data()
203 if not sel:
204 return
205 index = self.get_rows().index(sel)
206 self.xmms.playlist_remove_entry(index, self.name)
208 def switch_playlist(self, null):
209 """Switch the playlist to show in the list."""
210 def show_list_menu(res):
211 def _load_playlist(item):
212 name = item.get_data('playlist-name')
213 self.set_property('name', name)
214 self.load_playlist()
215 list = res.value()
216 menu = gnt.Menu(gnt.MENU_POPUP)
217 for pl in list:
218 name = str(pl)
219 item = gnt.MenuItemCheck(name)
220 if name == self.name:
221 item.set_checked(True)
222 item.set_data('playlist-name', name)
223 item.connect('activate', _load_playlist)
224 menu.add_item(item)
225 self.position_menu(menu)
226 gnt.show_menu(menu)
227 self.xmms.playlist_list(show_list_menu)
228 return True
230 def create_playlist(self, null):
231 """Create a new playlist."""
232 win = gnt.Window()
233 win.set_title('Create Playlist')
234 win.set_fill(False)
235 win.set_alignment(gnt.ALIGN_MID)
236 entry = gnt.Entry('')
237 button = gnt.Button('Create')
239 def create_playlist_cb(w, entry):
240 name = entry.get_text()
241 def _switch_list(res):
242 self.name = name
243 self.load_playlist()
244 self.xmms.playlist_create(name, _switch_list)
245 win.destroy()
246 entry.connect('activate', create_playlist_cb, entry)
247 button.connect('activate', create_playlist_cb, entry)
249 win.add_widget(gnt.Label('Name'))
250 win.add_widget(entry)
251 win.add_widget(button)
252 win.show()
253 return True
255 def place_tagged(self, null):
256 """Move the tagged entries immediately after the selected entry."""
257 entry = self.get_selection_data()
258 if not entry:
259 return True
260 all = self.get_rows()
261 index = all.index(entry)
262 pos = -1
263 for row in all:
264 pos = pos + 1
265 if not row.tagged:
266 continue
267 row.toggle_tag()
268 if pos > index:
269 index = index + 1
270 self.xmms.playlist_move(pos, index, self.name)
271 if pos < index:
272 pos = pos - 1
274 def clear_playlist(self, null):
275 """Clear the playlist. Prompt first."""
276 win = gnt.Window()
277 win.set_title("Confirm")
278 win.set_property('vertical', True)
279 win.set_fill(False)
280 win.set_alignment(gnt.ALIGN_MID)
281 clr = gnt.Button("Clear")
282 def really_clear_playlist(b, window):
283 self.xmms.playlist_clear(self.name)
284 window.destroy()
285 clr.connect('activate', really_clear_playlist, win)
286 cnc = gnt.Button("Cancel")
287 def close_window(b, window):
288 window.destroy()
289 cnc.connect('activate', close_window, win)
290 win.add_widget(gnt.Label("Do you really want to remove all entries from this playlist?"))
291 win.add_widget(common.pack_widget(False, [clr, cnc]))
292 win.show()
293 return True
295 gobject.type_register(XPList)
296 gnt.register_bindings(XPList)
298 class XPListCurrent(XPList):
299 __gntbindings__ = {
300 'play-selected' : ('play_selected', gnt.KEY_ENTER),
301 'play' : ('play', 'x'),
302 'pause' : ('pause', 'c'),
303 'stop' : ('stop', 'v'),
304 'jump-to-current' : ('jump_to_current', gnt.KEY_CTRL_J),
305 'selected-next' : ('selected_next', 'q'),
308 def __init__(self, xmms):
309 XPList.__init__(self, xmms, None)
311 def do_set_property(self, prop, value):
312 XPList.do_set_property(self, prop, value)
313 self.xmms.playlist_load(self.name)
315 def play_selected(self, null):
316 song = self.get_selection_data()
317 if song is None:
318 return False
319 index = self.get_rows().index(song)
320 self.xmms.playback_start()
321 def cb(res):
322 self.xmms.playback_tickle()
323 self.xmms.playlist_set_next(index, cb)
324 return True
326 def play(self, null):
327 self.xmms.playback_start()
328 return True
330 def pause(self, null):
331 self.xmms.playback_pause()
332 return True
334 def stop(self, null):
335 self.xmms.playback_stop()
336 return True
338 def jump_to_current(self, null):
339 def got_current_pos(result):
340 current = result.value()
341 rows = self.get_rows()
342 self.set_selected(rows[current])
343 self.xmms.playlist_current_pos(cb = got_current_pos)
344 return True
346 def selected_next(self, null):
347 song = self.get_selection_data()
348 if song is None:
349 return False
350 index = self.get_rows().index(song)
351 self.xmms.playlist_set_next(index)
352 return True
354 gobject.type_register(XPListCurrent)
355 gnt.register_bindings(XPListCurrent)
357 def setup_xmms():
358 xmms = xmmsclient.XMMS("pygnt-playlist")
359 try:
360 xmms.connect(os.getenv("XMMS_PATH"))
361 conn = xg.GLibConnector(xmms)
362 xqs = XPListCurrent(xmms)
363 xqs.show()
364 except IOError, detail:
365 common.show_error("Connection failed: " + str(detail))
367 if __name__ == '__main__':
368 setup_xmms()
369 gnt.gnt_main()
370 gnt.gnt_quit()