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