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