Merge lp:~linkinpark342/exaile/352683 into lp:exaile/0.3.3

Proposed by Abhishek Mukherjee
Status: Merged
Merged at revision: not available
Proposed branch: lp:~linkinpark342/exaile/352683
Merge into: lp:exaile/0.3.3
Diff against target: None lines
To merge this branch: bzr merge lp:~linkinpark342/exaile/352683
Reviewer Review Type Date Requested Status
reacocard (community) Approve
Review via email: mp+5646@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Abhishek Mukherjee (linkinpark342) wrote :

Adds queue managing functionality as a popup. This could later be extended to include Queue Manager as a pane if enough support is gathered.

Revision history for this message
reacocard (reacocard) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/glade/main.glade'
2--- data/glade/main.glade 2009-03-28 20:56:12 +0000
3+++ data/glade/main.glade 2009-04-13 02:38:36 +0000
4@@ -151,6 +151,23 @@
5 </widget>
6 </child>
7
8+ <child>
9+ <widget class="GtkImageMenuItem" id="queue_manager_item">
10+ <property name="label" translatable="yes">_Queue Manager</property>
11+ <property name="visible">True</property>
12+ <property name="use_underline">True</property>
13+ <property name="use_stock">False</property>
14+ <signal name="activate" handler="on_queue_manager_item_activate"/>
15+ <child internal-child="image">
16+ <widget class="GtkImage" id="queue_manager_icon">
17+ <property name="visible">True</property>
18+ <property name="stock">gtk-add</property>
19+ <property name="icon-size">1</property>
20+ </widget>
21+ </child>
22+ </widget>
23+ </child>
24+
25 <child>
26 <widget class="GtkImageMenuItem" id="plugins1">
27 <property name="visible">True</property>
28
29=== added file 'data/glade/queue_dialog.glade'
30--- data/glade/queue_dialog.glade 1970-01-01 00:00:00 +0000
31+++ data/glade/queue_dialog.glade 2009-04-14 17:10:16 +0000
32@@ -0,0 +1,187 @@
33+<?xml version="1.0"?>
34+<glade-interface>
35+ <!-- interface-requires gtk+ 2.16 -->
36+ <!-- interface-naming-policy project-wide -->
37+ <widget class="GtkWindow" id="QueueManagerDialog">
38+ <property name="width_request">300</property>
39+ <property name="height_request">400</property>
40+ <property name="title" translatable="yes">Queue Manager - Exaile</property>
41+ <child>
42+ <widget class="GtkVBox" id="vbox">
43+ <property name="visible">True</property>
44+ <property name="orientation">vertical</property>
45+ <child>
46+ <widget class="GtkHBox" id="hbox1">
47+ <property name="visible">True</property>
48+ <child>
49+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
50+ <property name="visible">True</property>
51+ <property name="can_focus">True</property>
52+ <property name="hscrollbar_policy">automatic</property>
53+ <property name="vscrollbar_policy">automatic</property>
54+ <child>
55+ <widget class="GtkTreeView" id="queue_tree">
56+ <property name="visible">True</property>
57+ <property name="can_focus">True</property>
58+ </widget>
59+ </child>
60+ </widget>
61+ <packing>
62+ <property name="padding">5</property>
63+ <property name="position">0</property>
64+ </packing>
65+ </child>
66+ <child>
67+ <widget class="GtkVButtonBox" id="vbuttonbox1">
68+ <property name="visible">True</property>
69+ <property name="layout_style">spread</property>
70+ <child>
71+ <widget class="GtkButton" id="top_button">
72+ <property name="label" translatable="yes">gtk-goto-top</property>
73+ <property name="visible">True</property>
74+ <property name="can_focus">True</property>
75+ <property name="receives_default">True</property>
76+ <property name="tooltip" translatable="yes">Move song to top in queue</property>
77+ <property name="use_stock">True</property>
78+ <signal name="clicked" handler="on_top_button_clicked"/>
79+ </widget>
80+ <packing>
81+ <property name="expand">False</property>
82+ <property name="fill">False</property>
83+ <property name="position">0</property>
84+ </packing>
85+ </child>
86+ <child>
87+ <widget class="GtkButton" id="up_button">
88+ <property name="label" translatable="yes">gtk-go-up</property>
89+ <property name="visible">True</property>
90+ <property name="can_focus">True</property>
91+ <property name="receives_default">True</property>
92+ <property name="tooltip" translatable="yes">Move song up in queue</property>
93+ <property name="use_stock">True</property>
94+ <signal name="clicked" handler="on_up_button_clicked"/>
95+ </widget>
96+ <packing>
97+ <property name="expand">False</property>
98+ <property name="fill">False</property>
99+ <property name="position">1</property>
100+ </packing>
101+ </child>
102+ <child>
103+ <widget class="GtkButton" id="remove_button">
104+ <property name="label" translatable="yes">gtk-remove</property>
105+ <property name="visible">True</property>
106+ <property name="can_focus">True</property>
107+ <property name="receives_default">True</property>
108+ <property name="tooltip" translatable="yes">Remove song from queue</property>
109+ <property name="use_stock">True</property>
110+ <signal name="clicked" handler="on_remove_button_clicked"/>
111+ </widget>
112+ <packing>
113+ <property name="expand">False</property>
114+ <property name="fill">False</property>
115+ <property name="position">2</property>
116+ </packing>
117+ </child>
118+ <child>
119+ <widget class="GtkButton" id="down_button">
120+ <property name="label" translatable="yes">gtk-go-down</property>
121+ <property name="visible">True</property>
122+ <property name="can_focus">True</property>
123+ <property name="receives_default">True</property>
124+ <property name="tooltip" translatable="yes">Move song down in queue</property>
125+ <property name="use_stock">True</property>
126+ <signal name="clicked" handler="on_down_button_clicked"/>
127+ </widget>
128+ <packing>
129+ <property name="expand">False</property>
130+ <property name="fill">False</property>
131+ <property name="position">3</property>
132+ </packing>
133+ </child>
134+ <child>
135+ <widget class="GtkButton" id="bottom_button">
136+ <property name="label" translatable="yes">gtk-goto-bottom</property>
137+ <property name="visible">True</property>
138+ <property name="can_focus">True</property>
139+ <property name="receives_default">True</property>
140+ <property name="tooltip" translatable="yes">Move song to bottom of queue</property>
141+ <property name="use_stock">True</property>
142+ <signal name="clicked" handler="on_bottom_button_clicked"/>
143+ </widget>
144+ <packing>
145+ <property name="expand">False</property>
146+ <property name="fill">False</property>
147+ <property name="position">4</property>
148+ </packing>
149+ </child>
150+ </widget>
151+ <packing>
152+ <property name="expand">False</property>
153+ <property name="padding">5</property>
154+ <property name="position">1</property>
155+ </packing>
156+ </child>
157+ </widget>
158+ <packing>
159+ <property name="padding">5</property>
160+ <property name="position">0</property>
161+ </packing>
162+ </child>
163+ <child>
164+ <widget class="GtkHSeparator" id="hseparator1">
165+ <property name="visible">True</property>
166+ </widget>
167+ <packing>
168+ <property name="expand">False</property>
169+ <property name="padding">5</property>
170+ <property name="position">1</property>
171+ </packing>
172+ </child>
173+ <child>
174+ <widget class="GtkHButtonBox" id="button_box">
175+ <property name="visible">True</property>
176+ <property name="spacing">5</property>
177+ <property name="layout_style">end</property>
178+ <child>
179+ <widget class="GtkButton" id="ok_button">
180+ <property name="label" translatable="yes">gtk-ok</property>
181+ <property name="visible">True</property>
182+ <property name="can_focus">True</property>
183+ <property name="receives_default">True</property>
184+ <property name="tooltip" translatable="yes">Close this dialog</property>
185+ <property name="use_stock">True</property>
186+ <signal name="clicked" handler="close_dialog"/>
187+ </widget>
188+ <packing>
189+ <property name="expand">False</property>
190+ <property name="fill">False</property>
191+ <property name="position">1</property>
192+ </packing>
193+ </child>
194+ <child>
195+ <widget class="GtkButton" id="remove_all_button">
196+ <property name="label" translatable="yes" comments="Remove all from queue">Remove _All</property>
197+ <property name="visible">True</property>
198+ <property name="can_focus">True</property>
199+ <property name="receives_default">True</property>
200+ <property name="use_underline">True</property>
201+ <signal name="clicked" handler="on_remove_all_button_clicked"/>
202+ </widget>
203+ <packing>
204+ <property name="expand">False</property>
205+ <property name="fill">False</property>
206+ <property name="position">0</property>
207+ </packing>
208+ </child>
209+ </widget>
210+ <packing>
211+ <property name="expand">False</property>
212+ <property name="padding">5</property>
213+ <property name="position">2</property>
214+ </packing>
215+ </child>
216+ </widget>
217+ </child>
218+ </widget>
219+</glade-interface>
220
221=== modified file 'xlgui/__init__.py'
222--- xlgui/__init__.py 2009-03-15 04:05:49 +0000
223+++ xlgui/__init__.py 2009-04-13 02:38:36 +0000
224@@ -18,7 +18,7 @@
225 import gtk, gtk.glade, gobject, logging
226 from xl import xdg, common, event, metadata
227
228-from xlgui import guiutil, prefs, plugins, cover, commondialogs
229+from xlgui import guiutil, prefs, plugins, cover, commondialogs, queue
230
231 gtk.window_set_default_icon_from_file(xdg.get_data_path("images/icon.png"))
232 logger = logging.getLogger(__name__)
233@@ -84,6 +84,7 @@
234 'on_about_item_activate': self.show_about_dialog,
235 'on_scan_collection_item_activate': self.on_rescan_collection,
236 'on_collection_manager_item_activate': self.collection_manager,
237+ 'on_queue_manager_item_activate': self.queue_manager,
238 'on_preferences_item_activate': lambda *e: self.show_preferences(),
239 'on_plugins_item_activate': self.show_plugins,
240 'on_album_art_item_activate': self.show_cover_manager,
241@@ -199,6 +200,10 @@
242 plugin_page=plugin_page)
243 dialog.run()
244
245+ def queue_manager(self, *e):
246+ dialog = queue.QueueManager(self.exaile.queue)
247+ dialog.show()
248+
249 def collection_manager(self, *e):
250 """
251 Invokes the collection manager dialog
252
253=== added file 'xlgui/queue.py'
254--- xlgui/queue.py 1970-01-01 00:00:00 +0000
255+++ xlgui/queue.py 2009-04-16 20:54:18 +0000
256@@ -0,0 +1,200 @@
257+"""
258+ Queue manager dialog
259+"""
260+
261+from xl import xdg
262+from xl.nls import gettext as _
263+from operator import itemgetter
264+from copy import copy
265+import xl.event
266+import os
267+import gtk
268+import gtk.glade
269+import logging
270+
271+LOG = logging.getLogger('exaile.xlgui.queue')
272+
273+class QueueManager(object):
274+
275+ """
276+ GUI to manage a queue
277+ """
278+
279+ def __init__(self, queue):
280+ self._queue = queue
281+
282+ self._xml = gtk.glade.XML(
283+ xdg.get_data_path(os.path.join('glade', 'queue_dialog.glade')),
284+ 'QueueManagerDialog', 'exaile')
285+ self._xml.get_widget('remove_all_button').set_image(
286+ gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_BUTTON))
287+
288+ self._dialog = self._xml.get_widget('QueueManagerDialog')
289+ self._dialog.connect('destroy', self.destroy)
290+ self._xml.signal_autoconnect({
291+ 'close_dialog': self.destroy,
292+ 'on_top_button_clicked': self.selected_to_top,
293+ 'on_up_button_clicked': self.selected_up,
294+ 'on_remove_button_clicked': self.remove_selected,
295+ 'on_down_button_clicked': self.selected_down,
296+ 'on_bottom_button_clicked': self.selected_to_bottom,
297+ 'on_remove_all_button_clicked': self.remove_all,
298+ })
299+
300+ self.__setup_callbacks()
301+
302+ self._model = gtk.ListStore(int, str, object)
303+ self._queue_view = self._xml.get_widget('queue_tree')
304+ self._queue_view.set_model(self._model)
305+ self.__last_tracks = []
306+ self.__populate_queue()
307+ self.__setup_queue()
308+
309+ def __setup_callbacks(self):
310+ for callback in self.__callbacks():
311+ xl.event.add_callback(*callback)
312+
313+ def __teardown_callbacks(self):
314+ for callback in self.__callbacks():
315+ xl.event.remove_callback(*callback)
316+
317+ def __populate_queue_cb(self, *e):
318+ self.__populate_queue()
319+
320+ def __callbacks(self):
321+ yield (self.__populate_queue_cb, 'playback_start')
322+ yield (self.__populate_queue_cb, 'tracks_added', self._queue)
323+
324+ def __setup_queue(self):
325+ """Adds columns to _queue_view"""
326+ renderer = gtk.CellRendererText()
327+ col = gtk.TreeViewColumn(_('#'), renderer, text=0)
328+ col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
329+ self._queue_view.append_column(col)
330+
331+ renderer = gtk.CellRendererText()
332+ col = gtk.TreeViewColumn(_('Title'), renderer, text=1)
333+ col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
334+ self._queue_view.append_column(col)
335+
336+ def __populate_queue(self):
337+ """Populates the _model with tracks"""
338+ tracks = self._queue.get_ordered_tracks()
339+ if tracks == self.__last_tracks:
340+ LOG.debug(_("Tracks did not change, no need to update"))
341+ return
342+ # Find the row that will be selected
343+
344+ if self._queue_view is not None:
345+ model, iter = self._queue_view.get_selection().get_selected()
346+ if iter:
347+ target = model.get_value(iter, 2)
348+ try:
349+ new_cursor_pos = tracks.index(target)
350+ except ValueError:
351+ pass
352+ if 'new_cursor_pos' not in locals():
353+ new_cursor_pos = None
354+ self.__last_tracks = copy(tracks)
355+ self._model.clear()
356+ # Add the rows
357+ for i, track in zip(xrange(1, len(tracks) + 1), tracks):
358+ self._model.append((i, unicode(track), track))
359+ # Select new row
360+ if new_cursor_pos is not None and hasattr(self, '_queue_view'):
361+ self._queue_view.set_cursor((new_cursor_pos,))
362+
363+ def show(self):
364+ """
365+ Displays this window
366+ """
367+ self._dialog.show_all()
368+
369+ def destroy(self, *e):
370+ """
371+ Destroys this window
372+ """
373+ self._dialog.destroy()
374+ self.__teardown_callbacks()
375+
376+# removing items
377+ def remove_selected(self, button, *userparams):
378+ model, iter = self._queue_view.get_selection().get_selected()
379+ if not iter:
380+ return
381+ i = model.get_value(iter, 0) - 1
382+ self.remove(i)
383+
384+ def remove_all(self, button, *userparams):
385+ while len(self._queue.get_ordered_tracks()):
386+ self.remove(0)
387+
388+ def remove(self, i):
389+ """Removes the ith item from the queue, 0-indexed"""
390+ cur_queue = self._queue.get_ordered_tracks()
391+ if i < 0 or i >= len(cur_queue):
392+ LOG.error(_("Gave an invalid number to remove"))
393+ return
394+ cur_queue.pop(i)
395+ self.__populate_queue()
396+
397+# Moving callbacks
398+ def selected_to_top(self, button, *userparams):
399+ self.reorder(lambda x, l: 0)
400+
401+ def selected_up(self, button, *userparams):
402+ self.reorder(lambda x, l: x - 1)
403+
404+ def selected_down(self, button, *userparams):
405+ self.reorder(lambda x, l: x + 1)
406+
407+ def selected_to_bottom(self, button, *userparams):
408+ self.reorder(lambda x, l: l - 1)
409+
410+ def reorder(self, new_loc):
411+ """Reorders the tracks in the queue.
412+
413+ new_loc is a function that takes two variables, x and l. x will be the
414+ the index of the currently selected item. l will be the size of the
415+ queue. The function need not bounds check.
416+
417+ """
418+ model, iter = self._queue_view.get_selection().get_selected()
419+ if not iter:
420+ return
421+
422+ i = model.get_value(iter, 0) - 1
423+ tracks = self._queue.get_ordered_tracks()
424+ if callable(new_loc):
425+ new_loc = new_loc(i, len(tracks))
426+ if new_loc < 0 or new_loc >= len(tracks):
427+ new_loc = i
428+
429+ new_order = list(zip(range(len(tracks)), tracks))
430+ new_order[new_loc], new_order[i] = new_order[i], new_order[new_loc]
431+
432+ self._queue.set_ordered_tracks(list(map(itemgetter(1), new_order)))
433+ self.__populate_queue()
434+
435+def main():
436+ class Track(object):
437+ def __init__(self, title):
438+ self.tags = {'title': title}
439+ def __unicode__(self):
440+ return self.tags['title']
441+ def __str(self):
442+ return str(unicode(self))
443+ class Foo(object):
444+ ordered_tracks = [Track('Track Foo by bar on baz'), Track('bar')]
445+ get_ordered_tracks = lambda self: self.ordered_tracks
446+ def set_ordered_tracks(self, v):
447+ self.ordered_tracks = v
448+ dialog = QueueManager(Foo())
449+ dialog.show()
450+ try:
451+ gtk.main()
452+ except KeyboardInterrupt:
453+ pass
454+
455+if __name__ == "__main__":
456+ main()