From: Kristoffer Grönlund Date: Tue, 5 Jan 2010 00:08:34 +0000 (+0100) Subject: Initial playlist support X-Git-Url: http://git.maemo.org/git/?p=jamaendo;a=commitdiff_plain;h=593d25b94de93d4d989e1a5d5fce1f1aab2baa2f Initial playlist support --- diff --git a/jamaendo/api.py b/jamaendo/api.py index 07a04e1..feeb17d 100644 --- a/jamaendo/api.py +++ b/jamaendo/api.py @@ -129,6 +129,9 @@ class Artist(LazyQuery): def _set_from(self, other): return self._set_from_impl(other, 'name', 'image', 'albums') + def get_data(self): + return {'name':self.name, 'image':self.image} + class Album(LazyQuery): def __init__(self, ID, json=None): self.ID = int(ID) @@ -151,6 +154,12 @@ class Album(LazyQuery): def _set_from(self, other): return self._set_from_impl(other, 'name', 'image', 'artist_name', 'artist_id', 'license_url', 'tracks') + def get_data(self): + return {'name':self.name, 'image':self.image, + 'artist_name':self.artist_name, + 'artist_id':self.artist_id, + 'license_url':self.license_url} + class Track(LazyQuery): def __init__(self, ID, json=None): self.ID = int(ID) @@ -171,6 +180,16 @@ class Track(LazyQuery): def ogg_url(self): return _OGGURL%(self.ID) + def get_data(self): + return {'name':self.name, + 'artist_id':self.artist_id, + 'artist_name':self.artist_name, + 'album_image':self.album_image, + 'album_name':self.album_name, + 'album_id':self.album_id, + 'numalbum':self.numalbum, + 'duration':self.duration} + def _needs_load(self): return self._needs_load_impl('name', 'artist_name', 'artist_id', 'album_name', 'album_id', 'numalbum', 'duration') diff --git a/jamaui/listbox.py b/jamaui/listbox.py new file mode 100644 index 0000000..edef878 --- /dev/null +++ b/jamaui/listbox.py @@ -0,0 +1,71 @@ +# what the fuck, GTK +import gtk +import hildon + +class ListBox(gtk.TreeView): + def __init__(self): + gtk.TreeView.__init__(self) + self.store = gtk.ListStore(str) + column = gtk.TreeViewColumn("Text") + textRenderer = gtk.CellRendererText() + column.pack_start(textRenderer, True) + column.set_attributes(textRenderer, text = 0) + self.append_column(column) + self.set_model(self.store) + + def get_text(self, path): + it = self.store.get_iter(path) + if not it: + return None + ret = self.store.get(it, 0) + return ret[0] + + def get_selected_text(self): + model, it = self.get_selection().get_selected() + if not it: + return None + ret = self.store.get(it, 0) + return ret[0] + + def append(self, text): + self.store.append([text]) + + def on_select(self, callback, *args): + def cb(lb, path, col): + ret = self.get_text(path) + if ret: + callback(ret, *args) + self.connect('row-activated', cb) + +class ListDialog(gtk.Dialog): + def __init__(self, title, parent=None): + gtk.Dialog.__init__(self, title, parent) + self.listbox = ListBox() + panarea = hildon.PannableArea() + panarea.add(self.listbox) + panarea.set_size_request(800, 300) + self.vbox.pack_start(panarea, True, True, 0) + + self.selected = None + + def selector(selected, dialog): + if selected: + self.selected = selected + dialog.response(gtk.RESPONSE_OK) + self.listbox.on_select(selector, self) + +class ButtonListDialog(gtk.Dialog): + def __init__(self, title, parent=None): + gtk.Dialog.__init__(self, title, parent) + self.panarea = hildon.PannableArea() + self.panarea.set_size_request(800, 400) + self.buttons = gtk.VBox(False, 0) + self.panarea.add_with_viewport(self.buttons) + self.vbox.pack_start(self.panarea, True, True, 0) + + def add_button(self, label, clickcb, *args): + btn = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH|gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL) + btn.set_label(label) + btn.connect('clicked', clickcb, *args) + self.buttons.pack_end(btn, False, False, 0) + diff --git a/jamaui/playerwindow.py b/jamaui/playerwindow.py index 16bfffe..2bb6598 100644 --- a/jamaui/playerwindow.py +++ b/jamaui/playerwindow.py @@ -34,7 +34,7 @@ import logging import cgi from songposition import SongPosition - +from listbox import ListDialog log = logging.getLogger(__name__) class PlayerWindow(hildon.StackableWindow): @@ -134,6 +134,11 @@ class PlayerWindow(hildon.StackableWindow): b.connect("clicked", to_album) self.menu.append(b) + b = hildon.GtkButton(gtk.HILDON_SIZE_AUTO) + b.set_label("Add to playlist") + b.connect("clicked", self.on_add_to_playlist) + self.menu.append(b) + self.menu.show_all() self.set_app_menu(self.menu) @@ -203,6 +208,14 @@ class PlayerWindow(hildon.StackableWindow): self.artist.set_markup('%s'%(cgi.escape(artist))) self.album.set_markup('%s'%(cgi.escape(album))) + def show_banner(self, message, timeout = 2000): + banner = hildon.hildon_banner_show_information(self, '', message) + banner.set_timeout(2000) + + def on_add_to_playlist(self, button, user_data=None): + track = self.player.playlist.current() + from playlists import add_to_playlist + add_to_playlist(self, track) def volume_changed_hildon(self, widget): settings.volume = widget.get_level()/100.0 diff --git a/jamaui/playlists.py b/jamaui/playlists.py new file mode 100644 index 0000000..714ec55 --- /dev/null +++ b/jamaui/playlists.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +# +# This file is part of Jamaendo. +# Copyright (c) 2010 Kristoffer Gronlund +# +# Jamaendo 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 3 of the License, or +# (at your option) any later version. +# +# Jamaendo 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 Jamaendo. If not, see . +# +# Player code heavily based on http://thpinfo.com/2008/panucci/: +# A resuming media player for Podcasts and Audiobooks +# Copyright (c) 2008-05-26 Thomas Perl +# (based on http://pygstdocs.berlios.de/pygst-tutorial/seeking.html) +# +import gtk +import hildon +import jamaendo +from settings import settings +import logging + +log = logging.getLogger(__name__) + +def _alist(l, match): + for key, value in l: + if key == match: + return value + return None + +def _show_banner(parent, message, timeout = 2000): + banner = hildon.hildon_banner_show_information(parent, '', message) + banner.set_timeout(2000) + +from listbox import ListDialog + +def add_to_playlist(wnd, track): + if not track: + _show_banner(wnd, "Nothing to add") + return + + dialog = ListDialog('Add to playlist', wnd) + for name,_ in settings.playlists.iteritems(): + dialog.listbox.append(name) + dialog.listbox.append("New...") + try: + dialog.show_all() + if dialog.run() == gtk.RESPONSE_OK: + selected_playlist = dialog.selected + if selected_playlist == "New...": + dialog.hide() + selected_playlist = create_new_playlist(wnd) + if track and selected_playlist: + if isinstance(track, (list, tuple)): + for t in track: + settings.add_to_playlist(selected_playlist, {'id':t.ID, 'data':t.get_data()}) + else: + settings.add_to_playlist(selected_playlist, {'id':track.ID, 'data':track.get_data()}) + settings.save() + _show_banner(wnd, "Added to playlist '%s'" % (selected_playlist)) + finally: + dialog.destroy() + +def create_new_playlist(wnd): + dia_name = gtk.Dialog() + dia_name.set_title("New playlist") + dia_name.add_button( gtk.STOCK_OK, gtk.RESPONSE_OK ) + entry = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT) + entry.set_placeholder("Enter name") + entry.set_max_length(32) + entry.connect('activate', lambda entry, dialog: dialog.response(gtk.RESPONSE_OK), dia_name) + dia_name.vbox.pack_start(entry, True, True, 0) + dia_name.show_all() + if dia_name.run() != gtk.RESPONSE_OK: + return False + selected_playlist = entry.get_text() + dia_name.destroy() + if selected_playlist == '' or selected_playlist == 'New...': + return False + elif settings.get_playlist(selected_playlist): + _show_banner(wnd, "Playlist '%s' already exists!" % (selected_playlist)) + return False + return selected_playlist + + +class PlaylistsWindow(hildon.StackableWindow): + def __init__(self): + hildon.StackableWindow.__init__(self) + self.set_title("Playlists") + + self.panarea = hildon.PannableArea() + + (self.COL_NAME, self.COL_INFO) = range(2) + self.store = gtk.ListStore(str, str) + self.treeview = gtk.TreeView() + self.treeview.set_model(self.store) + + col = gtk.TreeViewColumn('Name') + self.treeview.append_column(col) + cell = gtk.CellRendererText() + col.pack_start(cell, True) + col.add_attribute(cell, 'text', self.COL_NAME) + self.treeview.set_search_column(self.COL_NAME) + col.set_sort_column_id(self.COL_NAME) + + col = gtk.TreeViewColumn('Info') + self.treeview.append_column(col) + cell = gtk.CellRendererText() + col.pack_start(cell, True) + col.add_attribute(cell, 'text', self.COL_INFO) + + self.treeview.connect('row-activated', self.row_activated) + + self.panarea.add(self.treeview) + + self.add(self.panarea) + + def trackcount(lst): + ln = len(lst) + if ln > 1: + return "(%d tracks)"%(ln) + elif ln == 1: + return "(1 track)" + return "(empty)" + + for key, lst in sorted(list(settings.playlists.iteritems())): + self.store.append([key, trackcount(lst)]) + + def row_activated(self, treeview, path, view_column): + name = self.store.get(self.store.get_iter(path), self.COL_NAME)[0] + pl = settings.get_playlist(name) + if pl: + from playerwindow import open_playerwindow + wnd = open_playerwindow() + wnd.play_tracks(pl) diff --git a/jamaui/settings.py b/jamaui/settings.py index 04dce11..a9c86ed 100644 --- a/jamaui/settings.py +++ b/jamaui/settings.py @@ -23,6 +23,8 @@ # import cPickle, os import logging +import jamaendo +import datetime from postoffice import postoffice @@ -33,7 +35,8 @@ class Settings(object): defaults = { 'volume':0.1, 'user':None, - 'favorites':set([]) # local favorites - until we can sync back + 'favorites':set([]), # local favorites - until we can sync back + 'playlists':{}, } def __init__(self): @@ -52,6 +55,27 @@ class Settings(object): def favorite(self, album): self.favorites.add(('album', album.ID)) self.save() + postoffice.notify('settings-changed', 'favorites', self.favorites) + + def get_playlist(self, playlist, get_track_objects=True): + entry = self.playlists.get(playlist) + if entry: + if get_track_objects: + return [jamaendo.Track(item['id'], item['data']) for item in entry] + return entry + return None + + def add_to_playlist(self, playlist, track): + if isinstance(track, jamaendo.Track): + track = {'id':track.ID, 'data':track.get_data()} + assert(isinstance(track, dict)) + lst = self.playlists.get(playlist) + if not lst: + lst = [] + self.playlists[playlist] = lst + lst.append(track) + postoffice.notify('settings-changed', 'playlists', self.playlists) + log.debug("playlists is now %s", self.playlists) def load(self): if not os.path.isfile(self.__savename): @@ -67,7 +91,10 @@ class Settings(object): for k in self.defaults.keys(): if k in settings: + if k == 'playlists' and not isinstance(k, dict): + continue setattr(self, k, settings[k]) + print settings except Exception, e: log.exception('failed to load settings') @@ -81,6 +108,7 @@ class Settings(object): f = open(self.__savename, 'w') cPickle.dump(settings, f) f.close() + print settings except Exception, e: log.exception('failed to save settings') diff --git a/jamaui/showalbum.py b/jamaui/showalbum.py index 688022d..a13ffe8 100644 --- a/jamaui/showalbum.py +++ b/jamaui/showalbum.py @@ -32,6 +32,8 @@ from postoffice import postoffice import util import logging from albumlist import TrackList +from playlists import add_to_playlist + import webbrowser log = logging.getLogger(__name__) @@ -97,6 +99,10 @@ class ShowAlbum(hildon.StackableWindow): player.set_label("Open player") player.connect("clicked", on_player) self.menu.append(player) + player = hildon.GtkButton(gtk.HILDON_SIZE_AUTO) + player.set_label("Add to playlist") + player.connect("clicked", self.on_add_to_playlist) + self.menu.append(player) self.menu.show_all() self.set_app_menu(self.menu) @@ -107,6 +113,10 @@ class ShowAlbum(hildon.StackableWindow): if albumid == self.album.ID and size == 300: self.cover.set_from_file(cover) + + def on_add_to_playlist(self, button, user_data=None): + add_to_playlist(self, self.tracklist) + def make_imagebutton(self, name, cb): btn = hildon.GtkButton(gtk.HILDON_SIZE_AUTO) btn.set_relief(gtk.RELIEF_NONE) diff --git a/jamaui/ui.py b/jamaui/ui.py index 2c0ad65..2eb3261 100644 --- a/jamaui/ui.py +++ b/jamaui/ui.py @@ -62,16 +62,8 @@ from search import SearchWindow from featured import FeaturedWindow from radios import RadiosWindow from favorites import FavoritesWindow - -class PlaylistsWindow(hildon.StackableWindow): - def __init__(self): - hildon.StackableWindow.__init__(self) - self.set_title("Playlists") - - label = gtk.Label("Playlists") - vbox = gtk.VBox(False, 0) - vbox.pack_start(label, True, True, 0) - self.add(vbox) +from playlists import PlaylistsWindow +from listbox import ButtonListDialog class Jamaui(object): def __init__(self): @@ -98,9 +90,6 @@ class Jamaui(object): postoffice.connect('request-images', self, self.on_request_images) log.debug("Created main window.") - def save_settings(self): - settings.save() - def create_menu(self): self.menu = hildon.AppMenu() @@ -119,10 +108,10 @@ class Jamaui(object): player.connect("clicked", self.on_favorites) self.menu.append(player) - #player = hildon.GtkButton(gtk.HILDON_SIZE_AUTO) - #player.set_label("Playlists") - #player.connect("clicked", self.on_playlists) - #self.menu.append(player) + player = hildon.GtkButton(gtk.HILDON_SIZE_AUTO) + player.set_label("Playlists") + player.connect("clicked", self.on_playlists) + self.menu.append(player) player = hildon.GtkButton(gtk.HILDON_SIZE_AUTO) player.set_label("Settings") @@ -201,7 +190,7 @@ class Jamaui(object): def destroy(self, widget): postoffice.disconnect(['request-album-cover', 'request-images'], self) - self.save_settings() + settings.save() from player import the_player if the_player: the_player.stop() @@ -267,17 +256,15 @@ JAMENDO is an online platform that distributes musical works under Creative Comm # dialog.hide() def on_featured(self, button): - dialog = hildon.PickerDialog(self.window) - sel = hildon.TouchSelectorEntry(text=True) - for feature, _ in FeaturedWindow.features: - sel.append_text(feature) - dialog.set_selector(sel) - dialog.set_title("Featured") - sel.unselect_all(0) - if dialog.run() == gtk.RESPONSE_OK: - txt = sel.get_current_text() - self.featuredwnd = FeaturedWindow(txt) + dialog = ButtonListDialog('Featured', self.window) + def fn(btn, feature): + self.featuredwnd = FeaturedWindow(feature) self.featuredwnd.show_all() + dialog.response(gtk.RESPONSE_OK) + for feature, _ in FeaturedWindow.features: + dialog.add_button(feature, fn, feature) + dialog.show_all() + dialog.run() dialog.destroy() def on_radios(self, button): @@ -313,7 +300,7 @@ JAMENDO is an online platform that distributes musical works under Creative Comm if val and result == gtk.RESPONSE_OK: #print "new user name:", val settings.user = val - self.save_settings() + settings.save() def on_favorites(self, button): diff --git a/scripts/jamaendo b/scripts/jamaendo index 063064e..debdf19 100755 --- a/scripts/jamaendo +++ b/scripts/jamaendo @@ -11,7 +11,7 @@ import os, sys local_module_dir = os.path.join(os.path.dirname(sys.argv[0]), '..') if os.path.isdir(local_module_dir): - sys.path.append(local_module_dir) + sys.path = [local_module_dir] + sys.path def main(): from jamaui.ui import Jamaui