Most of the work I had indented for 0.2 done, only missing playlist management
authorKristoffer Grönlund <kristoffer.gronlund@purplescout.se>
Wed, 6 Jan 2010 02:26:12 +0000 (03:26 +0100)
committerKristoffer Grönlund <kristoffer.gronlund@purplescout.se>
Wed, 6 Jan 2010 13:56:42 +0000 (14:56 +0100)
14 files changed:
data/bg.png
data/bg.xcf
jamaui/__init__.py
jamaui/albumlist.py
jamaui/colors.py
jamaui/draw.py [new file with mode: 0644]
jamaui/listbox.py
jamaui/playerwindow.py
jamaui/playlists.py
jamaui/search.py
jamaui/settings.py
jamaui/songposition.py
jamaui/ui.py
jamaui/util.py

index ce8b7d6..76fa7f1 100644 (file)
Binary files a/data/bg.png and b/data/bg.png differ
index a017eca..5f520bb 100644 (file)
Binary files a/data/bg.xcf and b/data/bg.xcf differ
index 819b0d7..5bf0b3e 100644 (file)
@@ -26,7 +26,7 @@ import logging
 import sys
 
 LOG_FILENAME = '/tmp/jamaendo.log'
-LOG_LEVEL = logging.DEBUG
+LOG_LEVEL = logging.INFO
 LOG_FORMAT = "%(asctime)s %(name)-10s: [%(lineno)4d] %(levelname)-5s %(message)s"
 
 _rootlogger = logging.getLogger()
index dbaa8ff..c1dc2ab 100644 (file)
@@ -7,7 +7,8 @@ except:
     import helldon as hildon
 import jamaendo
 import util
-from settings import settings
+import draw
+import colors
 from postoffice import postoffice
 import logging
 
@@ -24,6 +25,10 @@ class _BaseList(gtk.TreeView):
         self.__store = None
         self.default_pixbuf = util.find_resource('album.png')
         self.connect('destroy', self.on_destroy)
+        self.connect('expose-event', self.on_treeview_expose_event)
+        self.loading_message = "Loading..."
+        self.empty_message = "Empty."
+        self._is_loading = True
 
     def get_pixbuf(self, img):
         try:
@@ -48,6 +53,32 @@ class _BaseList(gtk.TreeView):
     def on_destroy(self, wnd):
         pass
 
+    def set_loading(self, loading):
+        self._is_loading = loading
+        self.queue_draw()
+
+    def on_treeview_expose_event(self, treeview, event):
+        if event.window == treeview.get_bin_window():
+            model = treeview.get_model()
+            if (model is not None and model.get_iter_first() is not None):
+                return False
+
+            ctx = event.window.cairo_create()
+            ctx.rectangle(event.area.x, event.area.y,
+                    event.area.width, event.area.height)
+            ctx.clip()
+            x, y, width, height, depth = event.window.get_geometry()
+
+            if self._is_loading:
+                text = self.loading_message
+            else:
+                text = self.empty_message
+
+            desc = colors.get_font_desc('LargeSystemFont')
+            draw.text_box_centered(ctx, treeview, width, height, text, desc)
+
+        return False
+
 class MusicList(_BaseList):
     def __init__(self):
         _BaseList.__init__(self)
@@ -192,6 +223,66 @@ class TrackList(_BaseList):
         _, _, _id = self.__store.get(treeiter, 0, 1, 2)
         return _id
 
+
+class PlaylistList(_BaseList):
+    def __init__(self):
+        _BaseList.__init__(self)
+        (self.COL_ICON, self.COL_NAME, self.COL_INFO, self.COL_ID) = range(4)
+        self.__store = gtk.ListStore(gtk.gdk.Pixbuf, str, str, int)
+
+        self.set_model(self.__store)
+
+        icon = gtk.TreeViewColumn('Icon')
+        self.append_column(icon)
+        cell = gtk.CellRendererPixbuf()
+        icon.pack_start(cell, True)
+        icon.add_attribute(cell, 'pixbuf', self.COL_ICON)
+
+        col = gtk.TreeViewColumn('Name')
+        self.append_column(col)
+        cell = gtk.CellRendererText()
+        col.pack_start(cell, True)
+        col.add_attribute(cell, 'text', self.COL_NAME)
+
+        col = gtk.TreeViewColumn('Info')
+        self.append_column(col)
+        cell = gtk.CellRendererText()
+        cell.set_property('xalign', 1.0)
+        col.pack_start(cell, True)
+        col.add_attribute(cell, 'text', self.COL_INFO)
+
+        self.set_search_column(self.COL_NAME)
+        col.set_sort_column_id(self.COL_NAME)
+
+        postoffice.connect('album-cover', self, self.on_album_cover)
+
+    def on_destroy(self, wnd):
+        _BaseList.on_destroy(self, wnd)
+        postoffice.disconnect('album-cover', self)
+
+    def on_album_cover(self, albumid, size, cover):
+        if size == self.ICON_SIZE:
+            for row in self.__store:
+                if row[self.COL_ID] == albumid:
+                    row[self.COL_ICON] = self.get_pixbuf(cover)
+
+    def add_playlist(self, name, tracks):
+        def trackcount(lst):
+            ln = len(lst)
+            if ln > 1:
+                return "(%d tracks)"%(ln)
+            elif ln == 1:
+                return "(1 track)"
+            return "(empty)"
+        track = tracks[0] if len(tracks) else None
+        track_album_id = int(track['data']['album_id']) if track else 0
+        self.__store.append([self.get_default_pixbuf(), name, trackcount(tracks), track_album_id])
+        if track_album_id:
+            postoffice.notify('request-album-cover', track_album_id, self.ICON_SIZE)
+
+    def get_playlist_name(self, path):
+        return self.__store.get(self.__store.get_iter(path), self.COL_NAME)[0]
+
 class RadioList(_BaseList):
     def __init__(self):
         _BaseList.__init__(self)
index 391d18a..62ab8c9 100644 (file)
@@ -64,13 +64,18 @@ if _using_helldon:
         return "#333333"
 else:
     def color(name):
-        return get_color('SecondaryTextColor').to_string()
+        return get_color(name).to_string()
 
 import sys
 current_module = sys.modules[__name__]
 
+def mk_font_fun(name):
+    def inner():
+        return font(name)
+    return inner
+
 for fnt in logical_font_names:
-    setattr(current_module, fnt, lambda: font(fnt))
+    setattr(current_module, fnt.replace('-', ''), mk_font_fun(fnt))
 
 for clr in logical_color_names:
     setattr(current_module, clr, lambda: color(clr))
diff --git a/jamaui/draw.py b/jamaui/draw.py
new file mode 100644 (file)
index 0000000..400111d
--- /dev/null
@@ -0,0 +1,23 @@
+import gtk
+import pango
+
+def text_box_centered(ctx, widget, w_width, w_height, text, font_desc=None):
+    style = widget.rc_get_style()
+    text_color = style.text[gtk.STATE_PRELIGHT]
+    red, green, blue = text_color.red, text_color.green, text_color.blue
+    text_color = [float(x)/65535. for x in (red, green, blue)]
+    text_color.append(.5)
+
+    if font_desc is None:
+        font_desc = style.font_desc
+        font_desc.set_size(14*pango.SCALE)
+
+    pango_context = widget.create_pango_context()
+    layout = pango.Layout(pango_context)
+    layout.set_font_description(font_desc)
+    layout.set_text(text)
+    width, height = layout.get_pixel_size()
+
+    ctx.move_to(w_width/2-width/2, w_height/2-height/2)
+    ctx.set_source_rgba(*text_color)
+    ctx.show_layout(layout)
index c74f1d8..dad31ef 100644 (file)
@@ -48,7 +48,7 @@ class ListDialog(gtk.Dialog):
         self.listbox = ListBox()
         panarea = hildon.PannableArea()
         panarea.add(self.listbox)
-        panarea.set_size_request(800, 300)
+        panarea.set_size_request_policy(hildon.SIZE_REQUEST_CHILDREN)
         self.vbox.pack_start(panarea, True, True, 0)
 
         self.selected = None
@@ -69,8 +69,9 @@ class ButtonListDialog(gtk.Dialog):
         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 = 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)
+        self.buttons.pack_start(btn, False, False, 0)
 
index 1ff65f8..72472d5 100644 (file)
@@ -28,6 +28,7 @@ try:
 except:
     import helldon as hildon
 import util
+import datetime
 import pango
 import jamaendo
 from settings import settings
@@ -75,6 +76,9 @@ class PlayerWindow(hildon.StackableWindow):
         self.album = gtk.Label()
         self.album.set_alignment(0,0)
         self.album.set_ellipsize(pango.ELLIPSIZE_END)
+        self.playtime = gtk.Label()
+        self.playtime.set_alignment(1,0)
+        self.playtime.set_ellipsize(pango.ELLIPSIZE_END)
         self.progress = SongPosition()
 
         self.set_labels('', '', '', 0, 0)
@@ -85,6 +89,7 @@ class PlayerWindow(hildon.StackableWindow):
         vbox2.pack_start(self.track, True)
         vbox2.pack_start(self.artist, True)
         vbox2.pack_start(self.album, True)
+        vbox2.pack_start(self.playtime, False, True)
         vbox2.pack_start(self.progress, False, True)
 
         hbox.set_border_width(8)
@@ -252,10 +257,31 @@ class PlayerWindow(hildon.StackableWindow):
     def clear_position(self):
         self.progress.set_position(0)
 
+    def nanosecs_to_str(self, ns):
+        time_secs = int(float(ns)/1000000000.0)
+        if time_secs <= 59:
+            return "00:%02d"%(time_secs)
+        time_mins = int(time_secs/60.0)
+        time_secs -= time_mins*60
+        if time_mins <= 59:
+            return "%02d:%02d"%(time_mins, time_secs)
+
+        time_hrs = int(time_mins/60.0)
+        time_mins -= time_hrs*60
+        return "%d:%02d:%02d"%(time_hrs, time_secs, time_mins)
+
     def set_song_position(self, time_elapsed, total_time):
         value = (float(time_elapsed) / float(total_time)) if total_time else 0
         self.progress.set_position(value)
 
+
+        txt = '<span font_desc="%s" foreground="%s">%s</span>' % \
+            (colors.XLargeSystemFont(),
+             colors.SecondaryTextColor(),
+             self.nanosecs_to_str(time_elapsed)
+             )
+        self.playtime.set_markup(txt)
+
     def set_default_cover(self):
         tmp = util.find_resource('album.png')
         if tmp:
index 3012e29..9f70de4 100644 (file)
@@ -29,6 +29,7 @@ except:
 import jamaendo
 from settings import settings
 import logging
+from albumlist import PlaylistList
 
 log = logging.getLogger(__name__)
 
@@ -99,47 +100,40 @@ class PlaylistsWindow(hildon.StackableWindow):
         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()
-        cell.set_property('xalign', 1.0)
-        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.playlistlist = PlaylistList()
+        self.playlistlist.empty_message = "No playlists"
+        self.playlistlist.connect('row-activated', self.row_activated)
+        self.panarea.add(self.playlistlist)
         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)])
+            self.playlistlist.add_playlist(key, lst)
+        self.playlistlist.set_loading(False)
+
+        self.create_menu()
+
+    def create_menu(self):
+        def on_player(*args):
+            from playerwindow import open_playerwindow
+            open_playerwindow()
+        self.menu = hildon.AppMenu()
+        player = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        player.set_label("Open player")
+        player.connect("clicked", on_player)
+        self.menu.append(player)
+        player = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        player.set_label("Delete playlists")
+        player.connect("clicked", self.on_delete_playlists)
+        self.menu.append(player)
+        self.menu.show_all()
+        self.set_app_menu(self.menu)
+
+    def on_delete_playlists(self, *args):
+        _show_banner(self, "TODOO")
 
     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)
+        sel = self.playlistlist.get_playlist_name(path)
+        pl = settings.get_playlist(sel)
         if pl:
             from playerwindow import open_playerwindow
             wnd = open_playerwindow()
index 4ea8dd3..52d74f7 100644 (file)
@@ -44,19 +44,10 @@ class SearchWindow(hildon.StackableWindow):
         # Results list
         self.panarea = hildon.PannableArea()
         self.musiclist = MusicList()
-        self.result_store = gtk.ListStore(str, int)
-        #self.result_store.append(['red'])
-        self.result_view = gtk.TreeView(self.result_store)
-        col = gtk.TreeViewColumn('Name')
-        self.result_view.append_column(col)
-        cell = gtk.CellRendererText()
-        col.pack_start(cell, True)
-        col.add_attribute(cell, 'text', 0)
-        self.result_view.set_search_column(0)
-        col.set_sort_column_id(0)
-        self.result_view.connect('row-activated', self.row_activated)
-
-        self.panarea.add(self.result_view)
+        self.musiclist.loading_message = "Nothing found yet"
+        self.musiclist.empty_message = "No matching results"
+        self.musiclist.connect('row-activated', self.row_activated)
+        self.panarea.add(self.musiclist)
         vbox.pack_start(self.panarea, True, True, 0)
 
 
@@ -110,28 +101,28 @@ class SearchWindow(hildon.StackableWindow):
     def on_search(self, w):
         mode = self.mode.get_active()
         txt = self.entry.get_text()
-        self.result_store.clear()
-        if mode == 0:
-            for artist in jamaendo.search_artists(query=txt):
-                title = artist.name
-                self.idmap[artist.ID] = artist
-                self.result_store.append([title, artist.ID])
-        elif mode == 1:
-            for album in jamaendo.search_albums(query=txt):
-                title = "%s - %s" % (album.artist_name, album.name)
-                self.idmap[album.ID] = album
-                self.result_store.append([title, album.ID])
-        elif mode == 2:
-            for track in jamaendo.search_tracks(query=txt):
-                title = "%s - %s" % (track.artist_name, track.name)
-                self.idmap[track.ID] = track
-                self.result_store.append([title, track.ID])
+        self.musiclist.set_loading(False)
+        self.musiclist.get_model().clear()
+
+        try:
+            if mode == 0:
+                items = jamaendo.search_artists(query=txt)
+            elif mode == 1:
+                items = jamaendo.search_albums(query=txt)
+            elif mode == 2:
+                items = jamaendo.search_tracks(query=txt)
+
+            for item in items:
+                self.idmap[item.ID] = item
+
+            self.musiclist.add_items(items)
+        except jamaendo.JamaendoAPIException:
+            # nothing found, force redraw
+            self.musiclist.queue_draw()
 
     def row_activated(self, treeview, path, view_column):
-        treeiter = self.result_store.get_iter(path)
-        title, _id = self.result_store.get(treeiter, 0, 1)
+        _id = self.musiclist.get_item_id(path)
         item = self.idmap[_id]
-        #print _id, item
         self.open_item(item)
 
     def open_item(self, item):
index a9c86ed..94e10a6 100644 (file)
@@ -91,10 +91,11 @@ class Settings(object):
 
             for k in self.defaults.keys():
                 if k in settings:
-                    if k == 'playlists' and not isinstance(k, dict):
+                    val = settings[k]
+                    if k == 'playlists' and not isinstance(val, dict):
                         continue
-                    setattr(self, k, settings[k])
-            print settings
+                    setattr(self, k, val)
+            log.debug("Loaded settings: %s", settings)
         except Exception, e:
             log.exception('failed to load settings')
 
@@ -108,7 +109,7 @@ class Settings(object):
             f = open(self.__savename, 'w')
             cPickle.dump(settings, f)
             f.close()
-            print settings
+            log.debug("Saved settings: %s", settings)
         except Exception, e:
             log.exception('failed to save settings')
 
index 8aea80e..67e7a12 100644 (file)
@@ -12,7 +12,7 @@ class SongPosition(gtk.DrawingArea):
     def __init__(self):
         gtk.DrawingArea.__init__(self)
         self.connect('expose-event', self.on_expose)
-        self.set_size_request(self.WIDTH, self.HEIGHT)
+        self.set_size_request(int(self.WIDTH), int(self.HEIGHT))
         self.pos = 0.0
 
         orange0 = self.hex_to_flt(0xec, 0xac, 0x1f)
@@ -85,7 +85,7 @@ class SongPosition(gtk.DrawingArea):
             context.fill()
 
         context.rectangle(0, 0, rect.width, rect.height)
-        context.set_source_rgb(0.6, 0.6, 0.6)
+        context.set_source_rgb(0.3, 0.3, 0.3)
         context.stroke()
 
         #context.rectangle(lowx, 0, self.WIDTH, rect.height)
index 5732ee7..777bfd0 100644 (file)
@@ -93,11 +93,6 @@ class Jamaui(object):
     def create_menu(self):
         self.menu = hildon.AppMenu()
 
-        #search = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        #search.set_label("Search")
-        #search.connect("clicked", self.on_search)
-        #self.menu.append(search)
-
         player = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
         player.set_label("Open player")
         player.connect("clicked", self.on_player)
@@ -118,13 +113,6 @@ class Jamaui(object):
         player.connect("clicked", self.on_settings)
         self.menu.append(player)
 
-
-        # Don't use localdb ATM
-        #refresh = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        #refresh.set_label("Refresh")
-        #refresh.connect("clicked", self.on_refresh)
-        #self.menu.append(refresh)
-
         menu_about = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
         menu_about.set_label("About")
         menu_about.connect("clicked", self.show_about, self.window)
@@ -143,7 +131,7 @@ class Jamaui(object):
             self.window.window.set_back_pixmap(background, False)
 
         bbox = gtk.HButtonBox()
-        alignment = gtk.Alignment(xalign=0.2, yalign=0.925, xscale=1.0)
+        alignment = gtk.Alignment(xalign=0.2, yalign=0.28, xscale=1.0)
         alignment.add(bbox)
         bbox.set_property('layout-style', gtk.BUTTONBOX_SPREAD)
         self.bbox = bbox
@@ -179,19 +167,6 @@ class Jamaui(object):
         postoffice.notify('images', images)
         gtk.gdk.threads_leave()
 
-    #def add_featured_button(self):
-    #    self.featured_sel = hildon.TouchSelector(text=True)
-    #    self.featured_sel.append_text("Albums of the week")
-    #    self.featured_sel.append_text("Tracks of the week")
-    #    self.featured_sel.append_text("New releases")
-    #    btn = hildon.PickerButton(gtk.HILDON_SIZE_THUMB_HEIGHT,
-    #                              hildon.BUTTON_ARRANGEMENT_VERTICAL)
-    #    btn.set_text("Featured", "Most listened to")
-    #    btn.set_property('width-request', 225)
-    #    btn.set_selector(self.featured_sel)
-    #    self.featured_btn = btn
-    #    self.bbox.add(btn)
-
     def destroy(self, widget):
         postoffice.disconnect(['request-album-cover', 'request-images'], self)
         settings.save()
@@ -248,17 +223,9 @@ JAMENDO is an online platform that distributes musical works under Creative Comm
         dialog.destroy()
 
     def open_link(self, d, url, data):
-        #print "url: %s" % (url)
         import webbrowser
         webbrowser.open_new(url)
 
-
-    #def on_refresh(self, button):
-    #    dialog = RefreshDialog()
-    #    dialog.show_all()
-    #    dialog.run()
-    #    dialog.hide()
-
     def on_featured(self, button):
         dialog = ButtonListDialog('Featured', self.window)
         def fn(btn, feature):
@@ -288,21 +255,19 @@ JAMENDO is an online platform that distributes musical works under Creative Comm
         dialog.set_title("Settings")
         dialog.add_button( gtk.STOCK_OK, gtk.RESPONSE_OK )
         vbox = dialog.vbox
-        tbl = gtk.Table(1, 2)
-        tbl.attach(gtk.Label("Username:"), 0, 1, 0, 1)
+        hboxinner = gtk.HBox()
+        hboxinner.pack_start(gtk.Label("Username:"), False, False, 0)
         entry = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
         entry.set_placeholder("jamendo.com username")
         if settings.user:
             entry.set_text(settings.user)
-        tbl.attach(entry, 1, 2, 0, 1)
-        vbox.pack_start(tbl, True, True, 2)
+        hboxinner.pack_start(entry, True, True, 0)
+        vbox.pack_start(hboxinner, True, True, 0)
         dialog.show_all()
         result = dialog.run()
         val = entry.get_text()
         dialog.destroy()
-        #print val, result
         if val and result == gtk.RESPONSE_OK:
-            #print "new user name:", val
             settings.user = val
             settings.save()
 
@@ -314,22 +279,6 @@ JAMENDO is an online platform that distributes musical works under Creative Comm
     def on_player(self, button):
         open_playerwindow()
 
-    '''
-    def on_search(self, button):
-        if self.searchbar:
-            self.searchbar.show()
-        else:
-            self.searchstore = gtk.ListStore(gobject.TYPE_STRING)
-            iter = self.searchstore.append()
-            self.searchstore.set(iter, 0, "Test1")
-            iter = self.searchstore.append()
-            self.searchstore.set(iter, 0, "Test2")
-            self.searchbar = hildon.FindToolbar("Search", self.searchstore, 0)
-            self.searchbar.set_active(0)
-            self.window.add_toolbar(self.searchbar)
-            self.searchbar.show()
-    '''
-
     def run(self):
         ossohelper.application_init('org.jamaendo', '0.1')
         self.create_window()
index bd0c758..3c82f17 100644 (file)
@@ -56,4 +56,3 @@ def find_resource(name):
         return os.path.join('/usr/share/jamaendo', name)
     else:
         return None
-