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