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