diff options
author | Manuel QuiƱones <manuq@laptop.org> | 2011-10-25 22:02:26 (GMT) |
---|---|---|
committer | Gonzalo Odiard <godiard@gmail.com> | 2011-10-26 12:53:53 (GMT) |
commit | 7944c353e8d6bc3ff5d235db8ffbd5763bf40d6b (patch) | |
tree | 8c93046e145ddb4502ea38adc55c28d5a15f75a2 | |
parent | fb4b389e0bb2334eac8268eed842161bc6ff8b41 (diff) |
Playlist widget
This adds a playlist widget to Jukebox. The activity already has the
ability to add several media and going back and forward through them.
Now this information is shown in a list. Also, activating a row
starts playing the corresponding media.
Signed-off-by: Manuel QuiƱones <manuq@laptop.org>
-rw-r--r-- | ControlToolbar.py | 16 | ||||
-rw-r--r-- | ViewToolbar.py | 71 | ||||
-rw-r--r-- | jukeboxactivity.py | 107 | ||||
-rw-r--r-- | widgets.py | 87 |
4 files changed, 174 insertions, 107 deletions
diff --git a/ControlToolbar.py b/ControlToolbar.py index 8d63761..7ef8a5b 100644 --- a/ControlToolbar.py +++ b/ControlToolbar.py @@ -21,6 +21,7 @@ import gobject import gtk from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.toggletoolbutton import ToggleToolButton class ViewToolbar(gtk.Toolbar): @@ -29,7 +30,10 @@ class ViewToolbar(gtk.Toolbar): __gsignals__ = { 'go-fullscreen': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([])) + ([])), + 'toggle-playlist': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) } def __init__(self): @@ -41,9 +45,19 @@ class ViewToolbar(gtk.Toolbar): self.insert(self._fullscreen, -1) self._fullscreen.show() + self._show_playlist = ToggleToolButton('view-list') + self._show_playlist.set_active(True) + self._show_playlist.set_tooltip(_('Show Playlist')) + self._show_playlist.connect('toggled', self._playlist_toggled_cb) + self.insert(self._show_playlist, -1) + self._show_playlist.show() + def _fullscreen_cb(self, button): self.emit('go-fullscreen') + def _playlist_toggled_cb(self, button): + self.emit('toggle-playlist') + class Control(gobject.GObject): """Class to create the Control (play) toolbar""" diff --git a/ViewToolbar.py b/ViewToolbar.py deleted file mode 100644 index d884a5a..0000000 --- a/ViewToolbar.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (C) 2008 Kushal Das <kushal@fedoraproject.org> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -from gettext import gettext as _ - -import gobject -import gtk - -from sugar.graphics.toolbutton import ToolButton - - -class ViewToolbar(gtk.Toolbar): - """Class to create the view toolbar""" - - __gsignals__ = { - 'go-fullscreen': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([])) - } - - def __init__(self, toolbox, jukebox): - gtk.Toolbar.__init__(self) - self.toolbox = toolbox - self.jukebox = jukebox - - self._zoom_tofit = ToolButton('zoom-best-fit') - self._zoom_tofit.set_tooltip(_('Fit to window')) - self._zoom_tofit.connect('clicked', self._zoom_tofit_cb) - self.insert(self._zoom_tofit, -1) - self._zoom_tofit.show() - - self._zoom_original = ToolButton('zoom-original') - self._zoom_original.set_tooltip(_('Original size')) - self._zoom_original.connect('clicked', self._zoom_original_cb) - self.insert(self._zoom_original, -1) - self._zoom_original.show() - - spacer = gtk.SeparatorToolItem() - spacer.props.draw = False - self.insert(spacer, -1) - spacer.show() - - self._fullscreen = ToolButton('view-fullscreen') - self._fullscreen.set_tooltip(_('Fullscreen')) - self._fullscreen.connect('clicked', self._fullscreen_cb) - self.insert(self._fullscreen, -1) - self._fullscreen.show() - - def _zoom_tofit_cb(self, button): - pass - #self.jukebox.player.set_fit_to_screen_cb() - - def _zoom_original_cb(self, button): - pass - #self.jukebox.player.set_original_to_size_cb() - - def _fullscreen_cb(self, button): - self.emit('go-fullscreen') diff --git a/jukeboxactivity.py b/jukeboxactivity.py index 52195a9..4864a65 100644 --- a/jukeboxactivity.py +++ b/jukeboxactivity.py @@ -60,6 +60,10 @@ from ControlToolbar import Control, ViewToolbar from ConfigParser import ConfigParser cf = ConfigParser() +from widgets import PlayListWidget + +PLAYLIST_WIDTH_PROP = 1.0 / 3 + class JukeboxActivity(activity.Activity): UPDATE_INTERVAL = 500 @@ -82,6 +86,8 @@ class JukeboxActivity(activity.Activity): _view_toolbar = ViewToolbar() _view_toolbar.connect('go-fullscreen', self.__go_fullscreen_cb) + _view_toolbar.connect('toggle-playlist', + self.__toggle_playlist_cb) toolbox.add_toolbar(_('View'), _view_toolbar) _view_toolbar.show() @@ -108,6 +114,8 @@ class JukeboxActivity(activity.Activity): _view_toolbar = ViewToolbar() _view_toolbar.connect('go-fullscreen', self.__go_fullscreen_cb) + _view_toolbar.connect('toggle-playlist', + self.__toggle_playlist_cb) view_toolbar_button = ToolbarButton( page=_view_toolbar, icon_name='toolbar-view') @@ -142,7 +150,10 @@ class JukeboxActivity(activity.Activity): self.seek_timeout_id = -1 self.player = None self.uri = None + + # {'url': 'file://.../media.ogg', 'title': 'My song'} self.playlist = [] + self.jobjectlist = [] self.playpath = None self.currentplaying = None @@ -155,12 +166,19 @@ class JukeboxActivity(activity.Activity): self.p_duration = gst.CLOCK_TIME_NONE self.bin = gtk.HBox() + self.bin.show() + self.playlist_widget = PlayListWidget(self.play) + self.playlist_widget.update(self.playlist) + self.playlist_widget.show() + self.bin.pack_start(self.playlist_widget, expand=False) self._empty_widget = gtk.Label("") self._empty_widget.show() self.videowidget = VideoWidget() self._switch_canvas(show_video=False) self.set_canvas(self.bin) self.show_all() + self.bin.connect('size-allocate', self.__size_allocate_cb) + #From ImageViewer Activity self._want_document = True if self._object_id is None: @@ -169,7 +187,7 @@ class JukeboxActivity(activity.Activity): if handle.uri: self.uri = handle.uri - gobject.idle_add(self._start, self.uri) + gobject.idle_add(self._start, self.uri, handle.title) def _switch_canvas(self, show_video): """Show or hide the video visualization in the canvas. @@ -180,12 +198,17 @@ class JukeboxActivity(activity.Activity): """ if show_video: self.bin.remove(self._empty_widget) - self.bin.add(self.videowidget) + self.bin.pack_end(self.videowidget) else: - self.bin.add(self._empty_widget) + self.bin.pack_end(self._empty_widget) self.bin.remove(self.videowidget) self.bin.queue_draw() + def __size_allocate_cb(self, widget, allocation): + canvas_size = self.bin.get_allocation() + playlist_width = int(canvas_size.width * PLAYLIST_WIDTH_PROP) + self.playlist_widget.set_size_request(playlist_width, 0) + def open_button_clicked_cb(self, widget): """ To open the dialog to select a new file""" #self.player.seek(0L) @@ -219,38 +242,35 @@ class JukeboxActivity(activity.Activity): # return self.player.seek(0L) if direction == "prev" and self.currentplaying > 0: - self.currentplaying -= 1 - self.player.stop() - self._switch_canvas(show_video=True) - self.player = GstPlayer(self.videowidget) - self.player.connect("error", self._player_error_cb) - self.player.connect("tag", self._player_new_tag_cb) - self.player.connect("stream-info", self._player_stream_info_cb) - self.player.set_uri(self.playlist[self.currentplaying]) - logging.info("prev: " + self.playlist[self.currentplaying]) + self.play(self.currentplaying - 1) + logging.info("prev: " + self.playlist[self.currentplaying]['url']) #self.playflag = True - self.play_toggled() - self.player.connect("eos", self._player_eos_cb) elif direction == "next" and \ self.currentplaying < len(self.playlist) - 1: - self.currentplaying += 1 - self.player.stop() - self._switch_canvas(show_video=True) - self.player = GstPlayer(self.videowidget) - self.player.connect("error", self._player_error_cb) - self.player.connect("tag", self._player_new_tag_cb) - self.player.connect("stream-info", self._player_stream_info_cb) - self.player.set_uri(self.playlist[self.currentplaying]) - logging.info("NExt: " + self.playlist[self.currentplaying]) + self.play(self.currentplaying + 1) + logging.info("next: " + self.playlist[self.currentplaying]['url']) #self.playflag = True - self.play_toggled() - self.player.connect("eos", self._player_eos_cb) else: self.play_toggled() self.player.stop() self._switch_canvas(show_video=False) self.player.set_uri(None) + self.check_if_next_prev() + + def play(self, media_index): + self._switch_canvas(show_video=True) + self.currentplaying = media_index + self.player.stop() + self.player = GstPlayer(self.videowidget) + self.player.connect("eos", self._player_eos_cb) + self.player.connect("error", self._player_error_cb) + self.player.connect("tag", self._player_new_tag_cb) + self.player.connect("stream-info", self._player_stream_info_cb) + self.player.set_uri(self.playlist[self.currentplaying]['url']) + + self.play_toggled() self.check_if_next_prev() + self.playlist_widget.set_cursor(self.currentplaying) def _player_eos_cb(self, widget): self.songchange('next') @@ -323,7 +343,8 @@ class JukeboxActivity(activity.Activity): jobject = chooser.get_selected_object() if jobject and jobject.file_path: self.jobjectlist.append(jobject) - self._start(jobject.file_path) + title = jobject.metadata.get('title', None) + self._start(jobject.file_path, title) finally: #chooser.destroy() #del chooser @@ -333,7 +354,8 @@ class JukeboxActivity(activity.Activity): self.uri = os.path.abspath(file_path) if os.path.islink(self.uri): self.uri = os.path.realpath(self.uri) - gobject.idle_add(self._start, self.uri) + title = self.metadata.get('title', None) + gobject.idle_add(self._start, self.uri, title) def getplaylist(self, links): result = [] @@ -347,28 +369,34 @@ class JukeboxActivity(activity.Activity): urllib.quote(os.path.join(self.playpath, x))) return result - def _start(self, uri=None): + def _start(self, uri=None, title=None): self._want_document = False self.playpath = os.path.dirname(uri) if not uri: return False # FIXME: parse m3u files and extract actual URL if uri.endswith(".m3u") or uri.endswith(".m3u8"): - self.playlist.extend(self.getplaylist([line.strip() - for line in open(uri).readlines()])) + for line in open(uri).readlines(): + url = line.strip() + self.playlist.extend({'url': url, 'title': title}) elif uri.endswith('.pls'): try: cf.readfp(open(uri)) x = 1 while True: - self.playlist.append(cf.get("playlist", 'File' + str(x))) + url = cf.get("playlist", 'File' + str(x)) + self.playlist.append({'url': url, 'title': title}) x += 1 except: #read complete pass + + elif uri.startswith("file://"): + self.playlist.append({'url': uri, 'title': title}) + else: - self.playlist.append("file://" + - urllib.quote(os.path.abspath(uri))) + url = "file://" + urllib.quote(os.path.abspath(uri)) + self.playlist.append({'url': url, 'title': title}) if not self.player: # lazy init the player so that videowidget is realized # and has a valid widget allocation @@ -379,10 +407,12 @@ class JukeboxActivity(activity.Activity): self.player.connect("tag", self._player_new_tag_cb) self.player.connect("stream-info", self._player_stream_info_cb) + self.playlist_widget.update(self.playlist) + try: if not self.currentplaying: - logging.info("Playing: " + self.playlist[0]) - self.player.set_uri(self.playlist[0]) + logging.info("Playing: " + self.playlist[0]['url']) + self.player.set_uri(self.playlist[0]['url']) self.currentplaying = 0 self.play_toggled() self.show_all() @@ -469,6 +499,13 @@ class JukeboxActivity(activity.Activity): def __go_fullscreen_cb(self, toolbar): self.fullscreen() + def __toggle_playlist_cb(self, toolbar): + if self.playlist_widget.get_visible(): + self.playlist_widget.hide() + else: + self.playlist_widget.show_all() + self.bin.queue_draw() + class GstPlayer(gobject.GObject): __gsignals__ = { diff --git a/widgets.py b/widgets.py new file mode 100644 index 0000000..67c5bc4 --- /dev/null +++ b/widgets.py @@ -0,0 +1,87 @@ +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + +import logging +from gettext import gettext as _ + +import pygtk +pygtk.require('2.0') + +import gobject +import gtk +import pango + + +COLUMNS_NAME = ('index', 'media') +COLUMNS = dict((name, i) for i, name in enumerate(COLUMNS_NAME)) + + +class PlayListWidget(gtk.ScrolledWindow): + def __init__(self, play_callback): + self._playlist = None + self._play_callback = play_callback + + gtk.ScrolledWindow.__init__(self, hadjustment=None, + vadjustment=None) + self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.listview = gtk.TreeView() + self.treemodel = gtk.ListStore(int, object) + self.listview.set_model(self.treemodel) + selection = self.listview.get_selection() + selection.set_mode(gtk.SELECTION_SINGLE) + + renderer_idx = gtk.CellRendererText() + treecol_idx = gtk.TreeViewColumn(_('No.')) + treecol_idx.pack_start(renderer_idx, True) + treecol_idx.set_cell_data_func(renderer_idx, self._set_number) + self.listview.append_column(treecol_idx) + + renderer_title = gtk.CellRendererText() + renderer_title.set_property('ellipsize', pango.ELLIPSIZE_END) + treecol_title = gtk.TreeViewColumn(_('Play List')) + treecol_title.pack_start(renderer_title, True) + treecol_title.set_cell_data_func(renderer_title, self._set_title) + self.listview.append_column(treecol_title) + + # we don't support search in the playlist for the moment: + self.listview.set_enable_search(False) + + self.listview.connect('row-activated', self.__on_row_activated) + + self.add(self.listview) + + def __on_row_activated(self, treeview, path, col): + model = treeview.get_model() + media_idx = path[COLUMNS['index']] + self._play_callback(media_idx) + + def _set_number(self, column, cell, model, it): + idx = model.get_value(it, COLUMNS['index']) + cell.set_property('text', idx + 1) + + def _set_title(self, column, cell, model, it): + playlist_item = model.get_value(it, COLUMNS['media']) + cell.set_property('text', playlist_item['title']) + + def update(self, playlist): + self.treemodel.clear() + self._playlist = playlist + pl = list(enumerate(playlist)) + for i, media in pl: + self.treemodel.append((i, media)) + self.set_cursor(0) + + def set_cursor(self, index): + self.listview.set_cursor((index,)) |