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