Most of the work I had indented for 0.2 done, only missing playlist management
[jamaendo] / jamaui / albumlist.py
1 import os
2 import gtk
3 import gobject
4 try:
5     import hildon
6 except:
7     import helldon as hildon
8 import jamaendo
9 import util
10 import draw
11 import colors
12 from postoffice import postoffice
13 import logging
14
15 log = logging.getLogger(__name__)
16
17 class _BaseList(gtk.TreeView):
18     """
19     TODO: unify the different lists into one
20     """
21     ICON_SIZE = 50
22
23     def __init__(self):
24         gtk.TreeView.__init__(self)
25         self.__store = None
26         self.default_pixbuf = util.find_resource('album.png')
27         self.connect('destroy', self.on_destroy)
28         self.connect('expose-event', self.on_treeview_expose_event)
29         self.loading_message = "Loading..."
30         self.empty_message = "Empty."
31         self._is_loading = True
32
33     def get_pixbuf(self, img):
34         try:
35             return gtk.gdk.pixbuf_new_from_file_at_size(img,
36                                                         self.ICON_SIZE,
37                                                         self.ICON_SIZE)
38         except gobject.GError:
39             log.error("Broken image in cache: %s", img)
40             try:
41                 os.unlink(img)
42             except OSError, e:
43                 log.warning("Failed to unlink broken image.")
44             if img != self.default_pixbuf:
45                 return self.get_default_pixbuf()
46             else:
47                 return None
48
49     def get_default_pixbuf(self):
50         if self.default_pixbuf:
51             return self.get_pixbuf(self.default_pixbuf)
52
53     def on_destroy(self, wnd):
54         pass
55
56     def set_loading(self, loading):
57         self._is_loading = loading
58         self.queue_draw()
59
60     def on_treeview_expose_event(self, treeview, event):
61         if event.window == treeview.get_bin_window():
62             model = treeview.get_model()
63             if (model is not None and model.get_iter_first() is not None):
64                 return False
65
66             ctx = event.window.cairo_create()
67             ctx.rectangle(event.area.x, event.area.y,
68                     event.area.width, event.area.height)
69             ctx.clip()
70             x, y, width, height, depth = event.window.get_geometry()
71
72             if self._is_loading:
73                 text = self.loading_message
74             else:
75                 text = self.empty_message
76
77             desc = colors.get_font_desc('LargeSystemFont')
78             draw.text_box_centered(ctx, treeview, width, height, text, desc)
79
80         return False
81
82 class MusicList(_BaseList):
83     def __init__(self):
84         _BaseList.__init__(self)
85         (self.COL_ICON, self.COL_NAME, self.COL_ID, self.COL_IMAGE) = range(4)
86         self.__store = gtk.ListStore(gtk.gdk.Pixbuf, str, int, str)
87
88         self.set_model(self.__store)
89
90         icon = gtk.TreeViewColumn('Icon')
91         self.append_column(icon)
92         cell = gtk.CellRendererPixbuf()
93         icon.pack_start(cell, True)
94         icon.add_attribute(cell, 'pixbuf', self.COL_ICON)
95
96         col = gtk.TreeViewColumn('Name')
97         self.append_column(col)
98         cell = gtk.CellRendererText()
99         col.pack_start(cell, True)
100         col.add_attribute(cell, 'text', self.COL_NAME)
101         self.set_search_column(self.COL_NAME)
102         col.set_sort_column_id(self.COL_NAME)
103
104         postoffice.connect('images', self, self.on_images)
105
106     def get_item_id(self, path):
107         return self.__store.get(self.__store.get_iter(path), self.COL_ID)[0]
108
109     def on_destroy(self, wnd):
110         postoffice.disconnect('images', self)
111
112     def on_images(self, images):
113         for url, image in images:
114             for row in self.__store:
115                 if row[self.COL_IMAGE] == url:
116                     pb = self.get_pixbuf(image)
117                     if pb:
118                         row[self.COL_ICON] = pb
119
120     def add_items(self, items):
121         images = [x for x in (self.get_item_image(item) for item in items) if x]
122         for item in items:
123             txt = self.get_item_text(item)
124             self.__store.append([self.get_default_pixbuf(), txt, item.ID, self.get_item_image(item)])
125         if images:
126             postoffice.notify('request-images', images)
127
128     def get_item_text(self, item):
129         if isinstance(item, jamaendo.Album):
130             return "%s - %s" % (item.artist_name, item.name)
131         elif isinstance(item, jamaendo.Track):
132             return "%s - %s" % (item.artist_name, item.name)
133         else:
134             return item.name
135
136     def get_item_image(self, item):
137         ret = None
138         if isinstance(item, jamaendo.Track):
139             ret = item.album_image
140         elif hasattr(item, 'image'):
141             ret = item.image
142         if ret:
143             ret = ret.replace('1.100.jpg', '1.%d.jpg'%(self.ICON_SIZE))
144         return ret
145
146 class AlbumList(_BaseList):
147     def __init__(self):
148         _BaseList.__init__(self)
149         (self.COL_ICON, self.COL_NAME, self.COL_ID) = range(3)
150         self.__store = gtk.ListStore(gtk.gdk.Pixbuf, str, int)
151         self.__show_artist = True
152
153         self.set_model(self.__store)
154
155         icon = gtk.TreeViewColumn('Icon')
156         self.append_column(icon)
157         cell = gtk.CellRendererPixbuf()
158         icon.pack_start(cell, True)
159         icon.add_attribute(cell, 'pixbuf', self.COL_ICON)
160
161         col = gtk.TreeViewColumn('Name')
162         self.append_column(col)
163         cell = gtk.CellRendererText()
164         col.pack_start(cell, True)
165         col.add_attribute(cell, 'text', self.COL_NAME)
166         self.set_search_column(self.COL_NAME)
167         col.set_sort_column_id(self.COL_NAME)
168
169         postoffice.connect('album-cover', self, self.on_album_cover)
170
171     def on_destroy(self, wnd):
172         _BaseList.on_destroy(self, wnd)
173         postoffice.disconnect('album-cover', self)
174
175     def on_album_cover(self, albumid, size, cover):
176         if size == self.ICON_SIZE:
177             for row in self.__store:
178                 if row[self.COL_ID] == albumid:
179                     row[self.COL_ICON] = self.get_pixbuf(cover)
180
181     def add_album(self, album):
182         if self.__show_artist:
183             txt = "%s - %s" % (album.artist_name, album.name)
184         else:
185             txt = album.name
186         self.__store.append([self.get_default_pixbuf(), txt, album.ID])
187         postoffice.notify('request-album-cover', album.ID, self.ICON_SIZE)
188
189     def get_album_id(self, path):
190         return self.__store.get(self.__store.get_iter(path), self.COL_ID)[0]
191
192     def show_artist(self, show):
193         self.__show_artist = show
194
195 class TrackList(_BaseList):
196     def __init__(self, numbers = True):
197         _BaseList.__init__(self)
198         self.track_numbers = numbers
199         self.__store = gtk.ListStore(int, str, int)
200         self.set_model(self.__store)
201
202         if numbers:
203             col0 = gtk.TreeViewColumn('Num')
204             self.append_column(col0)
205             cell0 = gtk.CellRendererText()
206             col0.pack_start(cell0, True)
207             col0.add_attribute(cell0, 'text', 0)
208
209         col = gtk.TreeViewColumn('Name')
210         self.append_column(col)
211         cell = gtk.CellRendererText()
212         col.pack_start(cell, True)
213         col.add_attribute(cell, 'text', 1)
214
215         self.set_search_column(1 if numbers else 0)
216         col.set_sort_column_id(0)
217
218     def add_track(self, track):
219         self.__store.append([track.numalbum, track.name, track.ID])
220
221     def get_track_id(self, path):
222         treeiter = self.__store.get_iter(path)
223         _, _, _id = self.__store.get(treeiter, 0, 1, 2)
224         return _id
225
226
227 class PlaylistList(_BaseList):
228     def __init__(self):
229         _BaseList.__init__(self)
230         (self.COL_ICON, self.COL_NAME, self.COL_INFO, self.COL_ID) = range(4)
231         self.__store = gtk.ListStore(gtk.gdk.Pixbuf, str, str, int)
232
233         self.set_model(self.__store)
234
235         icon = gtk.TreeViewColumn('Icon')
236         self.append_column(icon)
237         cell = gtk.CellRendererPixbuf()
238         icon.pack_start(cell, True)
239         icon.add_attribute(cell, 'pixbuf', self.COL_ICON)
240
241         col = gtk.TreeViewColumn('Name')
242         self.append_column(col)
243         cell = gtk.CellRendererText()
244         col.pack_start(cell, True)
245         col.add_attribute(cell, 'text', self.COL_NAME)
246
247         col = gtk.TreeViewColumn('Info')
248         self.append_column(col)
249         cell = gtk.CellRendererText()
250         cell.set_property('xalign', 1.0)
251         col.pack_start(cell, True)
252         col.add_attribute(cell, 'text', self.COL_INFO)
253
254         self.set_search_column(self.COL_NAME)
255         col.set_sort_column_id(self.COL_NAME)
256
257         postoffice.connect('album-cover', self, self.on_album_cover)
258
259     def on_destroy(self, wnd):
260         _BaseList.on_destroy(self, wnd)
261         postoffice.disconnect('album-cover', self)
262
263     def on_album_cover(self, albumid, size, cover):
264         if size == self.ICON_SIZE:
265             for row in self.__store:
266                 if row[self.COL_ID] == albumid:
267                     row[self.COL_ICON] = self.get_pixbuf(cover)
268
269     def add_playlist(self, name, tracks):
270         def trackcount(lst):
271             ln = len(lst)
272             if ln > 1:
273                 return "(%d tracks)"%(ln)
274             elif ln == 1:
275                 return "(1 track)"
276             return "(empty)"
277         track = tracks[0] if len(tracks) else None
278         track_album_id = int(track['data']['album_id']) if track else 0
279         self.__store.append([self.get_default_pixbuf(), name, trackcount(tracks), track_album_id])
280         if track_album_id:
281             postoffice.notify('request-album-cover', track_album_id, self.ICON_SIZE)
282
283     def get_playlist_name(self, path):
284         return self.__store.get(self.__store.get_iter(path), self.COL_NAME)[0]
285
286 class RadioList(_BaseList):
287     def __init__(self):
288         _BaseList.__init__(self)
289         (self.COL_ICON, self.COL_NAME, self.COL_ID, self.COL_IMAGE) = range(4)
290         self.__store = gtk.ListStore(gtk.gdk.Pixbuf, str, int, str)
291         self.set_model(self.__store)
292
293         icon = gtk.TreeViewColumn('Icon')
294         self.append_column(icon)
295         cell = gtk.CellRendererPixbuf()
296         icon.pack_start(cell, True)
297         icon.add_attribute(cell, 'pixbuf', self.COL_ICON)
298
299         col = gtk.TreeViewColumn('Name')
300         self.append_column(col)
301         cell = gtk.CellRendererText()
302         col.pack_start(cell, True)
303         col.add_attribute(cell, 'text', self.COL_NAME)
304         self.set_search_column(self.COL_NAME)
305         col.set_sort_column_id(self.COL_NAME)
306
307         postoffice.connect('images', self, self.on_images)
308
309     def on_destroy(self, wnd):
310         postoffice.disconnect('images', self)
311
312     def add_radios(self, radios):
313         for radio in radios:
314             self.__store.append([self.get_default_pixbuf(), self.radio_name(radio), radio.ID, radio.image])
315         postoffice.notify('request-images', [radio.image for radio in radios])
316
317
318     def get_radio_id(self, path):
319         treeiter = self.__store.get_iter(path)
320         name, _id = self.__store.get(treeiter, self.COL_NAME, self.COL_ID)
321         return name, _id
322
323     def on_images(self, images):
324         for url, image in images:
325             for row in self.__store:
326                 if row[self.COL_IMAGE] == url:
327                     pb = self.get_pixbuf(image)
328                     if pb:
329                         row[self.COL_ICON] = pb
330
331     def radio_name(self, radio):
332         if radio.idstr:
333             return radio.idstr.capitalize()
334         elif radio.name:
335             return radio.name
336         else:
337             return "Radio #%s" % (radio.ID)