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
25 import xmmsclient
.glib
as xg
26 import xmmsclient
.collections
as xc
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"
44 sys
.setdefaultencoding("utf-8")
48 'name' : (gobject
.TYPE_STRING
, 'name of the playlist',
49 'name of the playlist', None, gobject
.PARAM_READWRITE
)
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
)
67 self
.connect('context-menu', self
.context_menu
)
69 def do_get_property(self
, prop
):
70 if prop
.name
== 'name':
72 raise AttributeError, 'unknown property %s' % prop
.name
74 def do_set_property(self
, prop
, value
):
75 if prop
.name
== 'name':
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.
91 def really_perform_action():
95 gobject
.timeout_add(0, really_perform_action
)
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
])
109 self
.position_menu(menu
)
112 def init_playlist(self
):
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']]
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()
135 # Now, find the new entry position
136 rows
= self
.get_rows()
138 after
= rows
[new
- 1]
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
:
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
153 # Make sure the entry in the 'position' column is correct for the entries
154 rows
= self
.get_rows()
158 self
.change_text(row
, self
.POSITION
, str(num
))
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)
168 self
.add_mlib_song(int(pos
.next()), song
)
170 self
.win
.set_title("XMMS2 Playlist - " + str(self
.name
))
174 self
.xmms
.playlist_list_entries(self
.name
, got_list_of_songs_in_pl
)
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
))
182 width
, height
= gnt
.screen_size()
183 self
.set_size(width
, height
)
186 def add_entry(self
, null
):
187 """Add new song(s) in the playlist."""
189 fl
.set_multi_select(True)
190 def destroy_fl(b
, dlg
):
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
)
197 fl
.connect('file_selected', add_files
, fl
)
200 def del_entry(self
, null
):
201 """Delete selected entry from the playlist."""
202 sel
= self
.get_selection_data()
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
)
216 menu
= gnt
.Menu(gnt
.MENU_POPUP
)
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
)
225 self
.position_menu(menu
)
227 self
.xmms
.playlist_list(show_list_menu
)
230 def create_playlist(self
, null
):
231 """Create a new playlist."""
233 win
.set_title('Create Playlist')
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
):
244 self
.xmms
.playlist_create(name
, _switch_list
)
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
)
255 def place_tagged(self
, null
):
256 """Move the tagged entries immediately after the selected entry."""
257 entry
= self
.get_selection_data()
260 all
= self
.get_rows()
261 index
= all
.index(entry
)
270 self
.xmms
.playlist_move(pos
, index
, self
.name
)
274 def clear_playlist(self
, null
):
275 """Clear the playlist. Prompt first."""
277 win
.set_title("Confirm")
278 win
.set_property('vertical', True)
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
)
285 clr
.connect('activate', really_clear_playlist
, win
)
286 cnc
= gnt
.Button("Cancel")
287 def close_window(b
, window
):
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
]))
295 gobject
.type_register(XPList
)
296 gnt
.register_bindings(XPList
)
298 class XPListCurrent(XPList
):
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()
319 index
= self
.get_rows().index(song
)
320 self
.xmms
.playback_start()
322 self
.xmms
.playback_tickle()
323 self
.xmms
.playlist_set_next(index
, cb
)
326 def play(self
, null
):
327 self
.xmms
.playback_start()
330 def pause(self
, null
):
331 self
.xmms
.playback_pause()
334 def stop(self
, null
):
335 self
.xmms
.playback_stop()
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
)
346 def selected_next(self
, null
):
347 song
= self
.get_selection_data()
350 index
= self
.get_rows().index(song
)
351 self
.xmms
.playlist_set_next(index
)
354 gobject
.type_register(XPListCurrent
)
355 gnt
.register_bindings(XPListCurrent
)
358 xmms
= xmmsclient
.XMMS("pygnt-playlist")
360 xmms
.connect(os
.getenv("XMMS_PATH"))
361 conn
= xg
.GLibConnector(xmms
)
362 xqs
= XPListCurrent(xmms
)
364 except IOError, detail
:
365 common
.show_error("Connection failed: " + str(detail
))
367 if __name__
== '__main__':