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