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