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