Use mediaplayer icon in the edit-panel controls
[mussorgsky] / src / edit_panel_tm.py
1 #!/usr/bin/env python2.5
2 import hildon
3 import gtk, gobject
4 from mutagen_backend import MutagenBackend
5 from player_backend import MediaPlayer
6 from utils import escape_html
7 import album_art_spec
8 import os
9
10 # Fields in the tuple!
11 # Shared with browse_panel
12 URI_COLUMN = 0
13 ARTIST_COLUMN = 2
14 TITLE_COLUMN = 3
15 ALBUM_COLUMN = 4
16 MIME_COLUMN = 5
17 UI_COLUMN = 6
18 SEARCH_COLUMN = 7
19
20 THEME_PATH = "/usr/share/themes/default"
21
22 class MussorgskyEditPanel (hildon.StackableWindow):
23
24     def __init__ (self):
25         hildon.StackableWindow.__init__ (self)
26         self.set_border_width (12)
27         self.album_change_handler = -1
28         self.artist_change_handler = -1
29         self.writer = MutagenBackend ()
30         self.player = MediaPlayer ()
31         self.__create_view ()
32         self.data_loaded = False
33         self.artist_list = None
34         self.albums_list = None
35         self.connect ("delete-event", self.close_function)
36
37     def close_function (self, widget, event):
38         if (not self.data_loaded):
39             return
40         
41         if self.__is_view_dirty ():
42             self.save_metadata ()
43
44         
45     def update_title (self):
46         self.set_title ("Edit (%d/%d)" % (self.model.get_path (self.current)[0] + 1,
47                                           len (self.model)))
48
49     def get_current_row (self):
50         if (not self.current):
51             return 6 * (None,)
52         return self.model.get (self.current, 0, 1, 2, 3, 4, 5)
53
54     def set_model (self, model, current=None):
55         try:
56             if self.artists_list or self.albums_list:
57                 pass
58         except AttributeError, e:
59             print "**** Set album and artist alternatives before setting a model"
60             raise e
61         
62         self.model = model
63         if (current):
64             self.current = current
65         else:
66             self.current = self.model.get_iter_first ()
67         self.data_loaded = True
68         self.set_data_in_view (self.get_current_row ())
69         self.update_title ()
70
71     def set_current (self, current):
72         """
73         Iterator to current element
74         """
75         self.current = current
76         self.set_data_in_view (self.get_current_row ())
77         self.update_title ()
78
79
80     def set_artist_alternatives (self, alternatives):
81         self.artists_list = alternatives
82         artist_selector = hildon.TouchSelectorEntry (text=True)
83         for a in self.artists_list:
84             artist_selector.append_text (a)
85         self.artist_button.set_selector (artist_selector)
86
87     def set_album_alternatives (self, alternatives):
88         self.albums_list = alternatives
89         album_selector = hildon.TouchSelectorEntry (text=True)
90         for a in self.albums_list:
91             album_selector.append_text (a)
92         self.album_button.set_selector (album_selector)
93
94
95     def press_back_cb (self, widget):
96         if (self.player.is_playing ()):
97             self.player.stop ()
98
99         if self.__is_view_dirty ():
100             print "Modified data. Save!"
101             self.save_metadata ()
102             
103         path = self.model.get_path (self.current)
104         if (path[0] == 0):
105             self.destroy ()
106         else:
107             new_path = ( path[0] -1, )
108             self.current = self.model.get_iter (new_path)
109             self.set_data_in_view (self.get_current_row ())
110             self.update_title ()
111
112     def press_next_cb (self, widget):
113         if (self.player.is_playing ()):
114             self.player.stop ()
115
116         if self.__is_view_dirty ():
117             print "Modified data. Save!"
118             self.save_metadata ()
119
120         self.current = self.model.iter_next (self.current)
121         if (not self.current):
122             self.destroy ()
123         else:
124             self.set_data_in_view (self.get_current_row ())
125             self.update_title ()
126
127             
128     def save_metadata (self):
129         # Save the data in the online model to show the appropiate data
130         # in the UI while tracker process the update.
131
132         # 0 - filename -> doesn't change
133         # 1 - "Music"  -> doesn't change
134         # 5 - mimetype -> doesn't change
135         if (type (self.model) == gtk.TreeModelFilter):
136             m = self.model.get_model ()
137             c = self.model.convert_iter_to_child_iter (self.current)
138         else:
139             # Very unlikely
140             m = self.model
141             c = self.current
142
143         artist = self.artist_button.get_value ()
144         title = self.title_entry.get_text ()
145         album = self.album_button.get_value ()
146         text = "<b>%s</b>\n<small>%s</small>" % (escape_html (title),
147                                                  escape_html (artist) + " / " + escape_html (album))
148         search_str = artist.lower () + " " + title.lower () + " " + album.lower ()
149
150         m.set (c,
151                ARTIST_COLUMN, artist,
152                TITLE_COLUMN, title,
153                ALBUM_COLUMN, album,
154                UI_COLUMN, text,
155                SEARCH_COLUMN, search_str)
156         new_song = self.get_current_row ()
157         try:
158             self.writer.save_metadata_on_file (new_song[URI_COLUMN],
159                                                new_song[MIME_COLUMN],
160                                                self.artist_button.get_value (),
161                                                self.title_entry.get_text (),
162                                                self.album_button.get_value ())
163         except IOError, e:
164             # This error in case of tracker returning unexistent files.
165             # Uhm.... for instance after removing a memory card we are editing!
166             pass
167             #dialog = gtk.MessageDialog (self,
168             #                            gtk.DIALOG_DESTROY_WITH_PARENT,
169             #                            gtk.MESSAGE_ERROR,
170             #                            gtk.BUTTONS_CLOSE,
171             #                            "%s" % str(e));
172             #dialog.run ()
173         
174
175     def __is_view_dirty (self):
176         """
177         True if the data has been modified in the widgets
178         """
179         song = self.get_current_row ()
180
181         return not (self.filename_data.get_text() == song[URI_COLUMN] and
182                     self.artist_button.get_value () == song[ARTIST_COLUMN] and
183                     self.title_entry.get_text () == song[TITLE_COLUMN] and
184                     self.album_button.get_value () == song[ALBUM_COLUMN] )
185         
186
187     def __create_view (self):
188         view_vbox = gtk.VBox (homogeneous=False, spacing = 12)
189
190         filename_row = gtk.HBox ()
191         self.filename_data = gtk.Label ("")
192         filename_row.pack_start (self.filename_data, expand=True)
193
194         #filename_row.pack_start (play_button, expand=False, fill=False)
195         view_vbox.pack_start (filename_row, expand=False);
196
197         central_panel = gtk.HBox (spacing=12)
198
199         table = gtk.Table (3, 2, False)
200         table.set_col_spacings (12)
201         table.set_row_spacings (12)
202
203         central_panel.pack_start (table, fill=True)
204         view_vbox.pack_start (central_panel, expand=True, fill=True)
205
206         # Title row
207         label_title = gtk.Label ("Title:")
208         table.attach (label_title, 0, 1, 0, 1, 0)
209         self.title_entry = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
210         table.attach (self.title_entry, 1, 2, 0, 1)
211
212         # Artist row
213         self.artist_button = hildon.PickerButton (gtk.HILDON_SIZE_THUMB_HEIGHT,
214                                                   hildon.BUTTON_ARRANGEMENT_HORIZONTAL)
215         self.artist_button.set_title ("Artist: ")
216         # Set data will set the selector
217         table.attach (self.artist_button, 0, 2, 1, 2)
218
219
220         # Album row
221         self.album_button = hildon.PickerButton (gtk.HILDON_SIZE_THUMB_HEIGHT,
222                                                  hildon.BUTTON_ARRANGEMENT_HORIZONTAL)
223         self.album_button.set_title ("Album: ")
224         # set_data will set the selector
225         table.attach (self.album_button, 0, 2, 2, 3)
226         
227
228         # Album art space
229         self.album_art = gtk.Image ()
230         self.album_art.set_size_request (124, 124)
231         central_panel.pack_start (self.album_art, expand=False, fill=False)
232         
233         # Buttons row
234         button_box = gtk.Toolbar ()
235         play_image = os.path.join (THEME_PATH, "mediaplayer", "Play.png")
236         play_button = gtk.ToolButton (gtk.image_new_from_file (play_image))
237         play_button.connect ("clicked", self.clicked_play)                   
238         play_button.set_expand (True)
239         button_box.insert (play_button, -1)
240         
241         separator = gtk.SeparatorToolItem ()
242         separator.set_expand (True)
243         button_box.insert  (separator, -1)
244
245         back_image = os.path.join (THEME_PATH, "mediaplayer", "Back.png")
246         back_button = gtk.ToolButton (gtk.image_new_from_file (back_image))
247         back_button.connect ("clicked", self.press_back_cb)
248         back_button.set_expand (True)
249         button_box.insert (back_button, -1)
250
251         next_image = os.path.join (THEME_PATH, "mediaplayer", "Forward.png")
252         next_button = gtk.ToolButton (gtk.image_new_from_file (next_image))
253         next_button.connect ("clicked", self.press_next_cb)
254         next_button.set_expand (True)
255         button_box.insert (next_button, -1)
256
257         self.add_toolbar (button_box)
258         
259         self.add (view_vbox)
260
261
262     def set_data_in_view (self, song):
263         """
264         Place in the screen the song information.
265         Song is a tuple like (filename, 'Music', title, artist, album, mime)
266         """
267         assert len (song) == 6
268         
269         self.filename_data.set_markup ("<small>" + song[URI_COLUMN] + "</small>")
270         self.title_entry.set_text (song[TITLE_COLUMN])
271         
272
273         # Disconnect the value-change signal to avoid extra album art retrievals
274         if (self.album_button.handler_is_connected (self.album_change_handler)):
275             self.album_button.disconnect (self.album_change_handler)
276             
277         if (self.artist_button.handler_is_connected (self.artist_change_handler)):
278             self.artist_button.disconnect (self.artist_change_handler)
279
280         # Set values in the picker buttons
281         try:
282             self.artist_button.set_active (self.artists_list.index(song[ARTIST_COLUMN]))
283         except ValueError:
284             print "'%s' not in artist list!?" % (song[ARTIST_COLUMN])
285             self.artist_button.set_value ("")
286         except AttributeError:
287             print "WARNING: Use set_artist_alternatives method to set a list of artists"
288             
289         try:
290             self.album_button.set_active (self.albums_list.index (song[ALBUM_COLUMN]))
291         except ValueError:
292             print "'%s' is not in the album list!?" % (song[ALBUM_COLUMN])
293             self.album_button.set_value ("")
294         except AttributeError:
295             print "WARNING: Use set_album_alternatives method to set a list of artists"
296
297         # Reconnect the signals!
298         self.album_change_handler = self.album_button.connect ("value-changed",
299                                                                self.album_selection_cb)
300
301         self.artist_change_handler = self.artist_button.connect ("value-changed",
302                                                                  self.artist_selection_cb)
303
304         # Set the album art given the current data
305         self.set_album_art (song[ALBUM_COLUMN])
306
307         if (not song[MIME_COLUMN] in self.writer.get_supported_mimes ()):
308             self.artist_button.set_sensitive (False)
309             self.album_button.set_sensitive (False)
310             self.title_entry.set_sensitive (False)
311         else:
312             self.artist_button.set_sensitive (True)
313             self.album_button.set_sensitive (True)
314             self.title_entry.set_sensitive (True)
315
316     def set_album_art (self, album):
317         has_album = False
318         if (album):
319             thumb = album_art_spec.getCoverArtThumbFileName (album)
320             print "%s -> %s" % (album, thumb)
321             if (os.path.exists (thumb)):
322                 self.album_art.set_from_file (thumb)
323                 has_album = True
324             
325         if (not has_album):
326             self.album_art.set_from_stock (gtk.STOCK_CDROM, gtk.ICON_SIZE_DIALOG)
327
328         
329     def clicked_play (self, widget):
330         if (self.player.is_playing ()):
331             self.player.stop ()
332         else:
333             song = self.get_current_row ()
334             self.player.play ("file://" + song[URI_COLUMN])
335
336     def album_selection_cb (self, widget):
337         """
338         On album change, add the album the local list of albums and the selector
339         if it doesn't exist already. So we show the new entry in the selector next time.
340         """
341         song = self.get_current_row ()
342         if (not widget.get_value () in self.albums_list):
343             print "Inserting ", widget.get_value ()
344             widget.get_selector ().prepend_text (widget.get_value ())
345             self.albums_list.insert (0, widget.get_value ())
346         self.set_album_art (widget.get_value ())
347
348     def artist_selection_cb (self, widget):
349         """
350         On artist change, add the artist the local list of artists and the selector
351         if it doesn't exist already. So we show the new entry in the selector next time
352         """
353         song = self.get_current_row ()
354         if (not widget.get_value () in self.artists_list):
355             print "Inserting artist", widget.get_value ()
356             widget.get_selector ().prepend_text (widget.get_value ())
357             self.artists_list.insert (0, widget.get_value ())
358
359 # Testing porpuses
360 if __name__ == "__main__":
361
362     TEST_DATA = [("/home/user/Music/dylan.mp3", "Music", "Bob Dylan", "Subterranean homesick blues", "Bring it all back home", "audio/mpeg"),
363                  ("/home/user/mufix/a.mp3", "Music", "", "title", "Album 2", "a/b"),
364                  ("/media/mmc1/Attachments/b.m4a", "Music", "", "b", "Album 9", "audio/mpeg"),
365                  ("/home/user/mufix/3.mp2", "Music", "", "titlex", "Album 3", "audio/mpeg")]
366
367     model = gtk.ListStore (str, str, str, str, str, str)
368     for t in TEST_DATA:
369         model.append (t)
370
371     window = MussorgskyEditPanel ()
372     window.set_artist_alternatives (["", "Bob Dylan"])
373     window.set_album_alternatives (["", "Bring it all back home", "Album 2", "Album 9", "Album 3"])
374     window.set_model (model)
375     window.connect ("destroy", gtk.main_quit)
376     window.show_all ()
377     gtk.main ()