1# Copyright (C) 2009-2010 Aren Olson 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2, or (at your option) 6# any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software 15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16# 17# 18# The developers of the Exaile media player hereby grant permission 19# for non-GPL compatible GStreamer and Exaile plugins to be used and 20# distributed together with GStreamer and Exaile. This permission is 21# above and beyond the permissions granted by the GPL license by which 22# Exaile is covered. If you modify this code, you may extend this 23# exception to your version of the code, but you are not obligated to 24# do so. If you do not wish to do so, delete this exception statement 25# from your version. 26 27from gi.repository import Gtk 28from gi.repository import GLib 29 30from xl.nls import gettext as _ 31from xl import common, event, player, providers 32from xl import settings as xl_settings 33from xl import lyrics as xl_lyrics 34from xlgui import guiutil, panel 35from xlgui.preferences import lyrics as lyricsprefs 36 37 38class LyricsPanel(panel.Panel): 39 40 # public variable for xlgui.panel.Panel 41 ui_info = ('lyrics.ui', 'LyricsPanel') 42 43 def __init__(self, parent, name): 44 panel.Panel.__init__(self, parent, name, _('Lyrics')) 45 46 self.__lyrics_found = [] 47 self.__css_provider = Gtk.CssProvider() 48 49 WIDGET_LIST = [ 50 'lyrics_top_box', 51 'refresh_button', 52 'refresh_button_stack', 53 'refresh_icon', 54 'refresh_spinner', 55 'track_text', 56 'scrolled_window', 57 'lyrics_text', 58 'lyrics_source_label', 59 'track_text_buffer', 60 'lyrics_text_buffer', 61 ] 62 for name in WIDGET_LIST: 63 setattr( 64 self, 65 '_' + LyricsPanel.__name__ + '__' + name, 66 self.builder.get_object(name), 67 ) 68 self.__initialize_widgets() 69 70 event.add_ui_callback(self.__on_playback_track_start, 'playback_track_start') 71 event.add_ui_callback(self.__on_track_tags_changed, 'track_tags_changed') 72 event.add_ui_callback(self.__on_playback_player_end, 'playback_player_end') 73 event.add_ui_callback( 74 self.__on_lyrics_search_method_added, 'lyrics_search_method_added' 75 ) 76 event.add_ui_callback(self.__on_option_set, 'plugin_lyricsviewer_option_set') 77 78 self.__update_lyrics() 79 80 def __initialize_widgets(self): 81 lyrics_methods_combo = LyricsMethodsComboBox() 82 self.__lyrics_top_box.pack_start(lyrics_methods_combo, True, True, 0) 83 lyrics_methods_combo.connect('changed', self.__on_combo_active_changed) 84 lyrics_methods_combo.show() 85 self.__lyrics_methods_combo = lyrics_methods_combo 86 87 track_text_style = Gtk.CssProvider() 88 style_context = self.__track_text.get_style_context() 89 style_context.add_provider( 90 track_text_style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION 91 ) 92 track_text_style.load_from_data(b"textview {font-weight: bold; }") 93 94 style_context = self.__lyrics_text.get_style_context() 95 style_context.add_provider( 96 self.__css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION 97 ) 98 99 lyricsprefs.DEFAULT_FONT = ( 100 self.__lyrics_text.get_default_attributes().font.to_string() 101 ) 102 # trigger initial setup through options 103 self.__on_option_set(None, xl_settings, 'plugin/lyricsviewer/lyrics_font') 104 105 self.__refresh_button.connect('clicked', self.__on_refresh_button_clicked) 106 107 def __on_option_set(self, _event, settings, option): 108 if option == 'plugin/lyricsviewer/lyrics_font': 109 pango_font_str = settings.get_option(option) 110 css_from_pango = guiutil.css_from_pango_font_description(pango_font_str) 111 data_str = "textview { " + css_from_pango + "; }\n" 112 self.__css_provider.load_from_data(data_str.encode('utf-8')) 113 114 def __on_lyrics_search_method_added(self, _eventtype, _lyrics, _provider): 115 self.__update_lyrics() 116 117 def __on_track_tags_changed(self, _eventtype, track, tags): 118 if player.PLAYER.current == track and tags & {"artist", "title"}: 119 self.__update_lyrics() 120 121 def __on_playback_track_start(self, _eventtype, _player, _data): 122 self.__update_lyrics() 123 124 def __on_playback_player_end(self, _eventtype, _player, _data): 125 self.__update_lyrics() 126 127 def __on_refresh_button_clicked(self, _button): 128 """ 129 Called when the refresh button is clicked 130 """ 131 self.__update_lyrics(refresh=True) 132 133 def __on_combo_active_changed(self, _combobox): 134 """ 135 Catches when the user selects an item of the combo. 136 """ 137 if self.__lyrics_found: 138 self.__update_lyrics_text() 139 140 def __update_lyrics(self, refresh=False): 141 self.__track_text_buffer.set_text("") 142 self.__lyrics_text_buffer.set_text("") 143 self.__lyrics_source_label.set_text("") 144 self.__lyrics_found = [] 145 if player.PLAYER.current: 146 self.__set_top_box_widgets(False) 147 self.__get_lyrics(player.PLAYER.current, refresh) 148 else: 149 self.__lyrics_text_buffer.set_text(_('Not playing.')) 150 self.__set_top_box_widgets(False, True) 151 152 @common.threaded 153 def __get_lyrics(self, track, refresh=False): 154 lyrics_found = [] 155 track_text = '' 156 try: 157 try: 158 track_text = ( 159 track.get_tag_raw('artist')[0] 160 + " - " 161 + track.get_tag_raw('title')[0] 162 ) 163 except Exception: 164 raise xl_lyrics.LyricsNotFoundException 165 lyrics_found = xl_lyrics.MANAGER.find_all_lyrics(track, refresh) 166 except xl_lyrics.LyricsNotFoundException: 167 lyrics_found = [] 168 finally: 169 self.__get_lyrics_finish(track, track_text, lyrics_found) 170 171 @common.idle_add() 172 def __get_lyrics_finish(self, track, track_text, lyrics_found): 173 '''Only called from __get_lyrics thread, thunk to ui thread''' 174 175 if track != player.PLAYER.current: 176 return 177 178 self.__lyrics_found = lyrics_found 179 180 self.__track_text_buffer.set_text(track_text) 181 self.__update_lyrics_text() 182 self.__set_top_box_widgets(True) 183 184 def __update_lyrics_text(self): 185 lyrics = _("No lyrics found.") 186 source = "" 187 url = "" 188 if self.__lyrics_found: 189 (index, selected_method) = self.__lyrics_methods_combo.get_active_item() 190 for (name, i_lyrics, i_source, i_url) in self.__lyrics_found: 191 if name == selected_method or index == 0: 192 lyrics, source, url = i_lyrics, i_source, i_url 193 break 194 self.__lyrics_text_buffer.set_text(lyrics) 195 196 if url != "": 197 url_text = '<a href="' + url + '">' + source + '</a>' 198 self.__lyrics_source_label.set_markup(_("Source: ") + url_text) 199 else: 200 self.__lyrics_source_label.set_text("") 201 202 def __set_top_box_widgets(self, state, init=False): 203 if state or init: 204 self.__refresh_spinner.stop() 205 self.__refresh_button_stack.set_visible_child(self.__refresh_icon) 206 else: 207 self.__refresh_button_stack.set_visible_child(self.__refresh_spinner) 208 self.__refresh_spinner.start() 209 210 self.__refresh_button.set_sensitive(state) 211 self.__lyrics_methods_combo.set_sensitive(state) 212 213 214class LyricsMethodsComboBox(Gtk.ComboBoxText, providers.ProviderHandler): 215 """ 216 An extended Gtk.ComboBox class. 217 Shows lyrics methods search registered 218 """ 219 220 def __init__(self): 221 Gtk.ComboBoxText.__init__(self) 222 providers.ProviderHandler.__init__(self, 'lyrics') 223 224 self.model = self.get_model() 225 # Default value, any registered lyrics provider 226 self.append_text(_("Any source")) 227 228 for provider in self.get_providers(): 229 self.append_text(provider.display_name) 230 231 self.set_active(0) 232 233 def remove_item(self, name): 234 index = self.search_item(name) 235 if index: 236 GLib.idle_add(self.remove, index) 237 return True 238 return False 239 240 def append_item(self, name): 241 if not self.search_item(name): 242 GLib.idle_add(self.append_text, name) 243 return True 244 return False 245 246 def search_item(self, name): 247 index = 0 248 for item in self.model: 249 if item[0] == name: 250 return index 251 index += 1 252 return False 253 254 def get_active_item(self): 255 return self.get_active(), self.get_active_text() 256 257 def on_provider_added(self, provider): 258 self.append_item(provider.display_name) 259 260 def on_provider_removed(self, provider): 261 self.remove_item(provider.display_name) 262