Add an entry in the context menu for adding files.
[python-gnt.git] / example / xmms / xmms-pl.py
blob4fb436b4efade1de7c1e84c4d080c78fe8f6284c
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
37 __version__ = "0.0.1alpha"
38 __author__ = "Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>"
39 __copyright__ = "Copyright 2007, Sadrul Habib Chowdhury"
40 __license__ = "GPL"
42 reload(sys)
43 sys.setdefaultencoding("utf-8")
45 def str_info(info):
46 title = str(info.get('title', ''))
47 artist = str(info.get('artist', ''))
48 album = str(info.get('album', ''))
49 time = int(info.get('duration', 0))
50 time = int(time / 1000)
51 time = "%02d:%02d" % (int(time / 60), int(time % 60))
53 return [title, artist, album, time]
55 class XPList(gnt.Tree):
56 """Static Playlist."""
57 POSITION = 0
58 TITLE = 1
59 ARTIST = 2
60 ALBUM = 3
61 TIME = 4
62 COLUMNS = 5
64 __gntbindings__ = {
65 'edit-entry' : ('edit_entry', 'e'),
66 'add-entry' : ('add_entry', gnt.KEY_INS),
67 'del-entry' : ('del_entry', gnt.KEY_DEL),
68 'edit-columns' : ('edit_columns', 'C'),
69 'search-column' : ('search_column', 's'),
70 'switch-playlist' : ('switch_playlist', 'w'),
73 def __init__(self, xmms, name):
74 """The xmms connection, and the name of the playlist."""
75 gnt.Tree.__init__(self)
76 self.xmms = xmms
77 self.name = name
78 self.win = None
79 self.init_tree()
80 self.needupdates = [] # A list of medialib id's which we need to poke information for
81 gobject.timeout_add_seconds(10, self.update_info)
82 self.columns = 0xffff
83 self.searchc = self.TITLE
84 self.set_search_column(self.searchc)
85 self.connect('context-menu', self.context_menu)
87 def context_menu(self, null):
88 def perform_action(item, data):
89 # This is a little weird, so pay attention ...
90 # The callback can either bring out a new window, or a new menu.
91 # In the first scenario, we want the new window to be given focus.
92 # So we perform the action immediately.
93 # In the latter case, we need to first let the active menu hide,
94 # then perform the action, so we call the callback in the next
95 # iteration of the mainloop.
96 action, wait = data
97 def really_perform_action():
98 action(None)
99 return False
100 if wait:
101 gobject.timeout_add(0, really_perform_action)
102 else:
103 action(None)
104 menu = gnt.Menu(gnt.MENU_POPUP)
105 for text, action, wait in (('Edit Info', self.edit_entry, False),
106 ('Remove', self.del_entry, False),
107 ('Add Files...', self.add_entry, False),
108 ('Edit Columns ...', self.edit_columns, True),
109 ('Set Search Column ...', self.search_column, True),
110 ('Switch Playlist...', self.switch_playlist, True)
112 item = gnt.MenuItem(text)
113 item.connect('activate', perform_action, [action, wait])
114 menu.add_item(item)
115 self.position_menu(menu)
116 gnt.show_menu(menu)
118 def show_column(self, col):
119 if self.columns & (1 << col): return
120 self.columns = self.columns | (1 << col)
121 self.set_column_visible(col, True)
122 self.draw()
124 def hide_column(self, col):
125 if not (self.columns & (1 << col)): return
126 self.columns = self.columns ^ (1 << col)
127 self.set_column_visible(col, False)
128 self.draw()
130 def toggle_column(self, col):
131 if self.columns & (1 << col):
132 self.hide_column(col)
133 else:
134 self.show_column(col)
136 def got_song_details(self, result):
137 info = result.value()
138 id = info.get('id', None)
139 if not id:
140 return
142 [title, artist, album, time] = str_info(info)
144 for row in self.get_rows():
145 mlib = row.get_data('song-id')
146 if mlib != id: continue
147 row.set_data('song-info', info)
148 self.change_text(row, self.ARTIST, artist)
149 self.change_text(row, self.ALBUM, album)
150 self.change_text(row, self.TITLE, title)
151 self.change_text(row, self.TIME, time)
153 def update_info(self):
154 # Update information about some songs, if necessary
155 for song in self.needupdates:
156 # song is the medialib id
157 self.xmms.medialib_get_info(song, self.got_song_details)
158 self.needupdates = []
159 return False # That's it! We're done!!
161 def queue_update(self, song):
162 # Instead of immediately updating information about the song,
163 # we wait a little. This is to avoid having to request info
164 # about the song multiple times when more than one property
165 # of the song is changed.
166 if len(self.needupdates) == 0:
167 gobject.timeout_add(500, self.update_info)
168 self.needupdates.append(song)
170 def init_tree(self):
171 self.set_property('columns', self.COLUMNS)
173 self.set_show_title(True)
175 # Position
176 self.set_column_title(self.POSITION, "#")
177 self.set_column_is_right_aligned(self.POSITION, True)
178 self.set_col_width(self.POSITION, 5)
179 self.set_column_resizable(self.POSITION, False)
181 # Title
182 self.set_column_title(self.TITLE, "Title")
183 self.set_col_width(self.POSITION, 20)
185 # Artist
186 self.set_column_title(self.ARTIST, "Artist")
187 self.set_col_width(self.ARTIST, 20)
189 # Album
190 self.set_column_title(self.ALBUM, "Album")
191 self.set_col_width(self.ALBUM, 20)
193 # Time
194 self.set_column_title(self.TIME, "Time")
195 self.set_col_width(self.TIME, 5)
196 self.set_column_resizable(self.TIME, False)
198 self.load_playlist()
200 # Make sure that if some metadata of a song changes, we update the playlist accordingly
201 def medialib_entry_changed(result):
202 # song is the medialib id of the song
203 song = result.value()
204 if song not in self.needupdates:
205 self.queue_update(song)
206 self.xmms.broadcast_medialib_entry_changed(medialib_entry_changed)
208 # Refresh the playlist if an entry is added/removed etc.
209 def refresh_playlist(result):
210 sys.stderr.write(str(result.value()) + "\n")
211 info = result.value()
212 if info['name'] != self.name: return # This playlist didn't change
213 if info['type'] == xmmsclient.PLAYLIST_CHANGED_REMOVE:
214 # Some entry was removed
215 rows = self.get_rows()
216 row = rows[info['position']]
217 self.remove(row)
218 elif info['type'] == xmmsclient.PLAYLIST_CHANGED_ADD or info['type'] == xmmsclient.PLAYLIST_CHANGED_INSERT:
219 # Some entry was added
220 row = treerow.Row()
221 row.set_data('song-id', info['id'])
222 position = info['position']
223 rows = self.get_rows()
224 after = None
225 if len(rows) >= position > 0:
226 after = rows[position - 1]
227 # Add the entry with empty information first. The request the data
228 self.add_row_after(row, [str(position + 1), "", "", "", ""], None, after)
229 self.xmms.medialib_get_info(info['id'], self.got_song_details)
230 elif info['type'] == xmmsclient.PLAYLIST_CHANGED_MOVE:
231 old = info['position']
232 new = info['newposition']
233 # First, remove the entry
234 rows = self.get_rows()
235 row = rows[old]
236 self.remove(row)
237 # Now, find the new entry position
238 rows = self.get_rows()
239 if new > 0:
240 after = rows[new - 1]
241 else:
242 after = None
243 info = row.get_data('song-info')
244 [title, artist, album, time] = str_info(info)
245 # Add it back in the new position
246 self.add_row_after(row, [str(new + 1), title, artist, album, time], None, after)
247 elif info['type'] == xmmsclient.PLAYLIST_CHANGED_CLEAR:
248 self.remove_all()
249 else:
250 sys.stderr.write("Unhandled playlist update type " + str(info['type']) + " -- Please file a bug report.\n")
251 # XXX: just go ahead and refresh the entire list
252 return
253 # Make sure the entry in the 'position' column is correct for the entries
254 rows = self.get_rows()
255 num = 1
256 for row in rows:
257 self.change_text(row, self.POSITION, str(num))
258 num = num + 1
259 self.xmms.broadcast_playlist_changed(refresh_playlist)
261 def load_playlist(self):
262 # Get the entries in the list, and populate the tree
263 def got_list_of_songs_in_pl(result):
264 list = result.value()
265 pos = itertools.count(1)
266 def got_song_details(result):
267 row = treerow.Row()
268 position = str(pos.next())
269 info = result.value()
271 title = artist = album = time = ""
272 if info is None:
273 songid = -1
274 else:
275 songid = info.get('id', None)
276 [title, artist, album, time] = str_info(info)
277 row.set_data('song-id', songid)
278 row.set_data('song-info', info)
279 self.add_row_after(row, [position, title, artist, album, time], None)
281 for song in list:
282 # song is the medialib id
283 self.xmms.medialib_get_info(song, got_song_details)
284 if self.win:
285 self.win.set_title("XMMS2 Playlist - " + str(self.name))
286 self.win.draw()
287 self.remove_all()
288 self.draw()
289 self.xmms.playlist_list_entries(self.name, got_list_of_songs_in_pl)
291 def show(self):
292 win = self.win = gnt.Box(homo = False, vert = True)
293 win.set_toplevel(True)
294 win.set_title("XMMS2 Playlist - " + str(self.name))
295 win.add_widget(self)
296 width, height = gnt.screen_size()
297 self.set_size(width, height)
298 win.show()
300 def edit_entry(self, null):
301 sel = self.get_selection_data()
302 if not sel:
303 return
304 info = sel.get_data('song-info')
305 if not info:
306 common.show_error("Do not have any editable information about this song.")
307 return
308 edit = songedit.SongEdit(self.xmms)
309 win = edit.get_widget()
310 edit.set_info(info)
311 win.set_toplevel(True)
312 win.set_title("Edit Song Information")
313 win.show()
315 def add_entry(self, null):
316 fl = gnt.FileSel()
317 fl.set_multi_select(True)
318 def destroy_fl(b, dlg):
319 dlg.destroy()
320 fl.cancel_button().connect('activate', destroy_fl, fl)
321 def add_files(fl, path, files, dlg):
322 for file in dlg.get_selected_multi_files():
323 self.xmms.playlist_add_url('file://' + file, self.name)
324 dlg.destroy()
325 fl.connect('file_selected', add_files, fl)
326 fl.show()
328 def del_entry(self, null):
329 sel = self.get_selection_data()
330 if not sel:
331 return
332 index = self.get_rows().index(sel)
333 self.xmms.playlist_remove_entry(index, self.name)
335 def position_menu(self, menu):
336 x, y = self.get_position()
337 width, height = self.get_size()
338 menu.set_position(x + width, y + self.get_selection_visible_line() + 3)
340 def desc_columns(self):
341 return [["Position", self.POSITION], ["Title", self.TITLE], ["Artist", self.ARTIST],
342 ["Album", self.ALBUM], ["Time", self.TIME]]
344 def edit_columns(self, null):
345 menu = gnt.Menu(gnt.MENU_POPUP)
346 def toggle_flag(item):
347 flag = item.get_data('column')
348 self.toggle_column(flag)
349 for text, col in self.desc_columns():
350 item = gnt.MenuItemCheck("Show " + text)
351 item.set_data('column', col)
352 if self.columns & (1 << col):
353 item.set_checked(True)
354 item.connect('activate', toggle_flag)
355 menu.add_item(item)
356 self.position_menu(menu)
357 gnt.show_menu(menu)
359 def search_column(self, null):
360 def change_search(item):
361 col = item.get_data('column')
362 self.set_search_column(col)
363 self.searchc = col
364 menu = gnt.Menu(gnt.MENU_POPUP)
365 for text, col in self.desc_columns():
366 item = gnt.MenuItemCheck("Search " + text)
367 item.set_data('column', col)
368 item.connect('activate', change_search)
369 if col == self.searchc:
370 item.set_checked(True)
371 menu.add_item(item)
372 self.position_menu(menu)
373 gnt.show_menu(menu)
375 def switch_playlist(self, null):
376 def show_list_menu(res):
377 def _load_playlist(item):
378 name = item.get_data('playlist-name')
379 self.name = name
380 self.load_playlist()
381 list = res.value()
382 menu = gnt.Menu(gnt.MENU_POPUP)
383 for pl in list:
384 name = str(pl)
385 item = gnt.MenuItemCheck(name)
386 if name == self.name:
387 item.set_checked(True)
388 item.set_data('playlist-name', name)
389 item.connect('activate', _load_playlist)
390 menu.add_item(item)
391 self.position_menu(menu)
392 gnt.show_menu(menu)
393 self.xmms.playlist_list(show_list_menu)
394 return True
396 gobject.type_register(XPList)
397 gnt.register_bindings(XPList)
399 def setup_xmms():
400 xmms = xmmsclient.XMMS("pygnt-playlist")
401 try:
402 xmms.connect(os.getenv("XMMS_PATH"))
403 conn = xg.GLibConnector(xmms)
404 xqs = XPList(xmms, 'Default')
405 xqs.show()
406 except IOError, detail:
407 common.show_error("Connection failed: " + str(detail))
409 if __name__ == '__main__':
410 setup_xmms()
411 gnt.gnt_main()
412 gnt.gnt_quit()