CoverArt Browser  v2.0
Browse your cover-art albums in Rhythmbox
/home/foss/Downloads/coverart-browser/coverart_browser_source.py
00001 # -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
00002 #
00003 # Copyright (C) 2012 - fossfreedom
00004 # Copyright (C) 2012 - Agustin Carrasco
00005 #
00006 # This program is free software; you can redistribute it and/or modify
00007 # it under the terms of thie GNU General Public License as published by
00008 # the Free Software Foundation; either version 2, or (at your option)
00009 # any later version.
00010 #
00011 # This program is distributed in the hope that it will be useful,
00012 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00013 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014 # GNU General Public License for more details.
00015 #
00016 # You should have received a copy of the GNU General Public License
00017 # along with this program; if not, write to the Free Software
00018 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
00019 
00020 import random
00021 from collections import OrderedDict
00022 import unicodedata
00023 import re
00024 
00025 from gi.repository import GObject
00026 from gi.repository import GLib
00027 from gi.repository import Gio
00028 from gi.repository import Gdk
00029 from gi.repository import Gtk
00030 from gi.repository import RB
00031 
00032 import rb
00033 from coverart_album import AlbumManager
00034 from coverart_browser_prefs import GSetting
00035 from coverart_browser_prefs import CoverLocale
00036 from coverart_browser_prefs import Preferences
00037 from coverart_widgets import PanedCollapsible
00038 from coverart_controllers import AlbumQuickSearchController
00039 from coverart_controllers import ViewController
00040 from coverart_export import CoverArtExport
00041 from coverart_rb3compat import Menu
00042 from coverart_rb3compat import ActionGroup
00043 from coverart_covericonview import CoverIconView
00044 from coverart_coverflowview import CoverFlowView
00045 from coverart_artistview import ArtistView
00046 from coverart_listview import ListView
00047 from coverart_queueview import QueueView
00048 from coverart_playsourceview import PlaySourceView
00049 from coverart_toolbar import ToolbarManager
00050 from coverart_artistinfo import ArtistInfoPane
00051 from coverart_external_plugins import CreateExternalPluginMenu
00052 from coverart_playlists import EchoNestPlaylist
00053 from coverart_entryview import EntryViewPane
00054 from coverart_play_source import CoverArtPlaySource
00055 import coverart_rb3compat as rb3compat
00056 
00057 
00058 class CoverArtBrowserSource(RB.Source):
00059     '''
00060     Source utilized by the plugin to show all it's ui.
00061     '''
00062     rating_threshold = GObject.property(type=float, default=0)
00063     artist_paned_pos = GObject.property(type=str)
00064     min_paned_pos = 80
00065 
00066     # unique instance of the source
00067     instance = None
00068 
00069     def __init__(self, **kargs):
00070         '''
00071         Initializes the source.
00072         '''
00073         super(CoverArtBrowserSource, self).__init__(**kargs)
00074 
00075         # create source_source_settings and connect the source's properties
00076         self.gs = GSetting()
00077 
00078         self._connect_properties()
00079 
00080         self.hasActivated = False
00081         self.last_width = 0
00082         self.last_selected_album = None
00083         self.click_count = 0
00084         self.favourites = False
00085         self.follow_song = False
00086         self.task_progress = None
00087         self._from_paned_handle = False
00088 
00089     def _connect_properties(self):
00090         '''
00091         Connects the source properties to the saved preferences.
00092         '''
00093         print("CoverArtBrowser DEBUG - _connect_properties")
00094         setting = self.gs.get_setting(self.gs.Path.PLUGIN)
00095 
00096         setting.bind(
00097             self.gs.PluginKey.RATING,
00098             self,
00099             'rating_threshold',
00100             Gio.SettingsBindFlags.GET)
00101 
00102         print("CoverArtBrowser DEBUG - end _connect_properties")
00103 
00104     def do_get_status(self, *args):
00105         '''
00106         Method called by Rhythmbox to figure out what to show on this source
00107         statusbar.
00108         If the custom statusbar is disabled, the source will
00109         show the selected album info.
00110         Also, it makes sure to show the progress on the album loading
00111         '''
00112 
00113         if not self.task_progress:
00114 
00115             self.task_progress = RB.TaskProgressSimple.new()
00116 
00117         try:
00118             progress = self.album_manager.progress
00119             progress_text = _('Loading...') if progress < 1 else ''
00120 
00121             if progress < 1:
00122                 if self.props.shell.props.task_list.get_model().n_items() == 0:
00123                     self.props.shell.props.task_list.add_task(self.task_progress)
00124 
00125                 self.task_progress.props.task_progress = progress
00126                 self.task_progress.props.task_label = progress_text
00127             else:
00128                 self.task_progress.props.task_outcome = RB.TaskOutcome.COMPLETE
00129 
00130         except:
00131             progress = 1
00132             progress_text = ''
00133 
00134             self.task_progress.props.task_outcome = RB.TaskOutcome.COMPLETE
00135 
00136         return (self.status, progress_text, progress)
00137 
00138     def do_selected(self):
00139         '''
00140         Called by Rhythmbox when the source is selected. It makes sure to
00141         create the ui the first time the source is showed.
00142         '''
00143         print("CoverArtBrowser DEBUG - do_selected")
00144 
00145         # first time of activation -> add graphical stuff
00146         if not self.hasActivated:
00147             self.do_impl_activate()
00148 
00149             # indicate that the source was activated before
00150             self.hasActivated = True
00151 
00152         print("CoverArtBrowser DEBUG - end do_selected")
00153 
00154     def do_impl_activate(self):
00155         '''
00156         Called by do_selected the first time the source is activated.
00157         It creates all the source ui and connects the necessary signals for it
00158         correct behavior.
00159         '''
00160         print("CoverArtBrowser DEBUG - do_impl_activate")
00161 
00162         # initialise some variables
00163         self.plugin = self.props.plugin
00164         self.shell = self.props.shell
00165         self.status = ''
00166         self.search_text = ''
00167         self.actiongroup = ActionGroup(self.shell, 'coverplaylist_submenu')
00168         self._browser_preferences = None
00169         self._search_preferences = None
00170 
00171         # indicate that the source was activated before
00172         self.hasActivated = True
00173 
00174         # define a query model that we'll use for playing
00175         self.source_query_model = RB.RhythmDBQueryModel.new_empty(self.shell.props.db)
00176 
00177         # define the associated playsource so we can interact with this query model
00178         self.playlist_source = GObject.new(
00179                 CoverArtPlaySource,
00180                 name=_("CoverArt Playlist"),
00181                 shell=self.shell,
00182                 plugin=self.plugin,
00183                 entry_type=self.plugin.entry_type)
00184         self.playlist_source.initialise(self.plugin, self.shell, self)
00185         self.shell.append_display_page(self.playlist_source, self.plugin.source)
00186 
00187         self._create_ui()
00188         self._setup_source()
00189         self._apply_settings()
00190 
00191         print("CoverArtBrowser DEBUG - end do_impl_activate")
00192 
00193     def _create_ui(self):
00194         '''
00195         Creates the ui for the source and saves the important widgets onto
00196         properties.
00197         '''
00198         print("CoverArtBrowser DEBUG - _create_ui")
00199 
00200         # dialog has not been created so lets do so.
00201         cl = CoverLocale()
00202         ui = Gtk.Builder()
00203         ui.set_translation_domain(cl.Locale.LOCALE_DOMAIN)
00204         ui.add_from_file(rb.find_plugin_file(self.plugin,
00205                                              'ui/coverart_browser.ui'))
00206         ui.connect_signals(self)
00207 
00208         # load the page and put it in the source
00209         # first setup the notification stuff
00210 
00211         def hide_infobar(widget, *args):
00212             widget.hide()
00213 
00214         overlay = Gtk.Overlay()
00215         overlay.show_all()
00216         self.notification_infobar = Gtk.InfoBar.new()
00217 
00218         self.notification_infobar.set_message_type(Gtk.MessageType.INFO)
00219         self.notification_infobar.set_valign(Gtk.Align.START)
00220         self.notification_infobar.connect('response', hide_infobar)
00221 
00222         self.notification_text = Gtk.Label.new("")
00223         area = self.notification_infobar.get_content_area()
00224         area.props.halign = Gtk.Align.CENTER
00225         area.add(self.notification_text)
00226 
00227         self.notification_infobar.show_all()
00228         self.notification_infobar.set_visible(False)
00229 
00230         self.page = ui.get_object('main_box')
00231         overlay.add(self.page)
00232         overlay.add_overlay(self.notification_infobar)
00233 
00234         self.pack_start(overlay, True, True, 0)
00235         self.page.reorder_child(overlay, 0)
00236 
00237         # get widgets for main icon-view
00238         self.status_label = ui.get_object('status_label')
00239         window = ui.get_object('scrolled_window')
00240 
00241         self.viewmgr = ViewManager(self, window)
00242 
00243         # get widgets for the artist paned
00244         self.artist_paned = ui.get_object('vertical_paned')
00245         self.artist_paned.set_name('vertical_paned')
00246         Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 50, self._change_artist_paned_pos, self.viewmgr.view_name)
00247         self.viewmgr.connect('new-view', self.on_view_changed)
00248         self.artist_treeview = ui.get_object('artist_treeview')
00249         self.artist_scrolledwindow = ui.get_object('artist_scrolledwindow')
00250 
00251         # define menu's
00252         self.popup_menu = Menu(self.plugin, self.shell)
00253         self.popup_menu.load_from_file('ui/coverart_browser_pop_rb2.ui',
00254                                        'ui/coverart_browser_pop_rb3.ui')
00255         self._external_plugins = None
00256 
00257         signals = \
00258             {'play_album_menu_item': self.play_album_menu_item_callback,
00259              'queue_album_menu_item': self.queue_album_menu_item_callback,
00260              'add_to_playing_menu_item': self.add_to_playing_menu_item_callback,
00261              'new_playlist': self.add_playlist_menu_item_callback,
00262              'cover_search_menu_item': self.cover_search_menu_item_callback,
00263              'export_embed_menu_item': self.export_embed_menu_item_callback,
00264              'show_properties_menu_item': self.show_properties_menu_item_callback,
00265              'play_similar_artist_menu_item': self.play_similar_artist_menu_item_callback}
00266 
00267         self.popup_menu.connect_signals(signals)
00268         self.popup_menu.connect('pre-popup', self.pre_popup_menu_callback)
00269 
00270         self.status_label = ui.get_object('status_label')
00271         self.request_status_box = ui.get_object('request_status_box')
00272         self.request_spinner = ui.get_object('request_spinner')
00273         self.request_statusbar = ui.get_object('request_statusbar')
00274         self.request_cancel_button = ui.get_object('request_cancel_button')
00275         self.paned = ui.get_object('paned')
00276         self.entry_view_grid = ui.get_object('bottom_grid')
00277 
00278         #setup Track Pane
00279         setting = self.gs.get_setting(self.gs.Path.PLUGIN)
00280         setting.bind(self.gs.PluginKey.PANED_POSITION,
00281                      self.paned, 'collapsible-y', Gio.SettingsBindFlags.DEFAULT)
00282         self.entryviewpane = EntryViewPane(self.shell,
00283                                            self.plugin,
00284                                            self,
00285                                            self.entry_view_grid,
00286                                            self.viewmgr)
00287 
00288         #---- set up info pane -----#
00289         info_stack = ui.get_object('info_stack')
00290         info_button_box = ui.get_object('info_button_box')
00291         artist_info_paned = ui.get_object('vertical_info_paned')
00292         artist_info_paned.set_name('vertical_paned')
00293 
00294         self.artist_info = ArtistInfoPane(info_button_box,
00295                                           info_stack,
00296                                           artist_info_paned,
00297                                           self)
00298 
00299         # quick search
00300         self.quick_search = ui.get_object('quick_search_entry')
00301 
00302         # theme override option
00303         cssProvider = Gtk.CssProvider()
00304         css = rb.find_plugin_file(self.plugin, 'ui/gtkthemeoverride.css')
00305         cssProvider.load_from_path(css)
00306         screen = Gdk.Screen.get_default()
00307         styleContext = Gtk.StyleContext()
00308         styleContext.add_provider_for_screen(screen, cssProvider,
00309                                              Gtk.STYLE_PROVIDER_PRIORITY_USER)
00310 
00311         print("CoverArtBrowser DEBUG - end _create_ui")
00312 
00313     def _setup_source(self):
00314         '''
00315         Setup the different parts of the source so they are ready to be used
00316         by the user. It also creates and configure some custom widgets.
00317         '''
00318         print("CoverArtBrowser DEBUG - _setup_source")
00319 
00320         cl = CoverLocale()
00321         cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
00322 
00323         # setup iconview popup
00324         self.viewmgr.current_view.set_popup_menu(self.popup_menu)
00325 
00326         # create an album manager
00327         self.album_manager = AlbumManager(self.plugin, self.viewmgr.current_view)
00328 
00329         self.viewmgr.current_view.initialise(self)
00330         # setup cover search pane
00331         self.entryviewpane.setup_source()
00332         self.entry_view = self.entryviewpane.get_entry_view()
00333 
00334         # connect a signal to when the info of albums is ready
00335         self.load_fin_id = self.album_manager.loader.connect(
00336             'model-load-finished', self.load_finished_callback)
00337 
00338         # prompt the loader to load the albums
00339         self.album_manager.loader.load_albums(self.props.base_query_model)
00340 
00341         # initialise the variables of the quick search
00342         self.quick_search_controller = AlbumQuickSearchController(
00343             self.album_manager)
00344         self.quick_search_controller.connect_quick_search(self.quick_search)
00345 
00346         # set sensitivity of export menu item for iconview
00347         self.popup_menu.set_sensitive('export_embed_menu_item',
00348                                       CoverArtExport(self.plugin,
00349                                                      self.shell, self.album_manager).is_search_plugin_enabled())
00350 
00351         # setup the statusbar component
00352         self.statusbar = Statusbar(self)
00353 
00354         # initialise the toolbar manager
00355         self.toolbar_manager = ToolbarManager(self.plugin, self.page,
00356                                               self.viewmgr)
00357         self.viewmgr.current_view.emit('update-toolbar')
00358 
00359         cl.switch_locale(cl.Locale.RB)
00360         # setup the artist paned
00361         artist_pview = None
00362         for view in self.shell.props.library_source.get_property_views():
00363             print(view.props.title)
00364             print(_("Artist"))
00365             if view.props.title == _("Artist"):
00366                 artist_pview = view
00367                 break
00368 
00369         assert artist_pview, "cannot find artist property view"
00370 
00371         self.artist_treeview.set_model(artist_pview.get_model())
00372         setting = self.gs.get_setting(self.gs.Path.PLUGIN)
00373         setting.bind(self.gs.PluginKey.ARTIST_PANED_POSITION,
00374                      self, 'artist-paned-pos', Gio.SettingsBindFlags.DEFAULT)
00375 
00376         self.artist_paned.connect('button-press-event',
00377                                   self.artist_paned_button_press_callback)
00378         self.artist_paned.connect('button-release-event',
00379                                   self.artist_paned_button_release_callback)
00380 
00381         # intercept JumpToPlaying Song action so that we can scroll to the playing album
00382         appshell = rb3compat.ApplicationShell(self.shell)
00383         action = appshell.lookup_action("", "jump-to-playing", "win")
00384         action.action.connect("activate", self.jump_to_playing, None)
00385 
00386         # connect to the playing song event so that if we are following a song, the album
00387         # should be automatically selected
00388 
00389         self.shell.props.shell_player.connect('playing-song-changed', self.playing_song_callback)
00390 
00391         self.echonest_similar_playlist = None
00392 
00393         print("CoverArtBrowser DEBUG - end _setup_source")
00394 
00395     def playing_song_callback(self, *args):
00396         if not self.follow_song:
00397             return
00398 
00399         self.jump_to_playing()
00400 
00401     def pre_popup_menu_callback(self, *args):
00402         '''
00403         Callback when the popup menu is about to be displayed
00404         '''
00405 
00406         state, sensitive = self.shell.props.shell_player.get_playing()
00407         if not state:
00408             sensitive = False
00409 
00410         #self.popup_menu.get_menu_object('add_to_playing_menu_item')
00411         self.popup_menu.set_sensitive('add_to_playing_menu_item', sensitive)
00412 
00413         if not self._external_plugins:
00414             # initialise external plugin menu support
00415             self._external_plugins = \
00416                 CreateExternalPluginMenu("ca_covers_view",
00417                                          8, self.popup_menu)
00418             self._external_plugins.create_menu('popup_menu', True)
00419 
00420         self.playlist_menu_item_callback()
00421 
00422     def jump_to_playing(self, *args):
00423         '''
00424         Callback when the JumpToPlaying action is invoked
00425         This will scroll the view to the playing song
00426         '''
00427 
00428         if not self.shell.props.selected_page.props.name == self.props.name:
00429             # if the source page that was played from is not the plugin then
00430             # nothing to do
00431             return
00432 
00433         album = None
00434 
00435         entry = self.shell.props.shell_player.get_playing_entry()
00436 
00437         if entry:
00438             album = self.album_manager.model.get_from_dbentry(entry)
00439 
00440         self.viewmgr.current_view.scroll_to_album(album)
00441 
00442     def artist_paned_button_press_callback(self, widget, *args):
00443         self._from_paned_handle = True
00444 
00445     def artist_paned_button_release_callback(self, widget, *args):
00446         '''
00447         Callback when the artist paned handle is released from its mouse click.
00448         '''
00449         if not self._from_paned_handle:
00450             return False
00451         else:
00452             self._from_paned_handle = False
00453 
00454         print ("artist_paned_button_release_callback")
00455         child_width = self._get_child_width()
00456         print (child_width)
00457         print (self.artist_paned.get_position())
00458         child_width = self.artist_paned.get_position()
00459         paned_positions = eval(self.artist_paned_pos)
00460 
00461         found = None
00462         for viewpos in paned_positions:
00463             if self.viewmgr.view_name in viewpos:
00464                 found = viewpos
00465                 break
00466 
00467         if not found:
00468             print ("not found %s" % self.viewmgr.view_name)
00469             return True
00470 
00471         print ("current paned_positions %s" % paned_positions)
00472         paned_positions.remove(found)
00473         print ("Child Width %d" % child_width)
00474         if child_width <= self.min_paned_pos:
00475             print (found)
00476             print (found.split(':')[1])
00477             if int(found.split(':')[1]) == 0:
00478                 child_width = self.min_paned_pos + 1
00479                 print ("opening")
00480             else:
00481                 child_width = 0
00482                 print ("smaller")
00483             self.artist_paned.set_position(child_width)
00484 
00485 
00486         print ("Child Width2 %d" % child_width)
00487 
00488         paned_positions.append(self.viewmgr.view_name + ":" + str(child_width))
00489 
00490         print ("after paned positions %s" % paned_positions)
00491         self.artist_paned_pos = repr(paned_positions)
00492         print ("artist_paned_pos %s" % self.artist_paned_pos)
00493 
00494     def on_view_changed(self, widget, view_name):
00495         self._change_artist_paned_pos(view_name)
00496 
00497     def _change_artist_paned_pos(self, view_name):
00498         print ("change artist paned")
00499         paned_positions = eval(self.artist_paned_pos)
00500         print(paned_positions)
00501         found = None
00502         for viewpos in paned_positions:
00503             if view_name in viewpos:
00504                 found = viewpos
00505                 break
00506         print(found)
00507         if not found:
00508             print ("not found %s" % view_name)
00509             return
00510 
00511         child_width = int(found.split(":")[1])
00512         print(child_width)
00513 
00514         # odd case - if the pane is not visible but the position is zero
00515         # then the paned position on visible=true is some large arbitary value
00516         # hence - set it to be 1 px larger than the real value, then set it back
00517         # to its expected value
00518         self.artist_paned.set_position(child_width + 1)
00519         self.artist_paned.set_visible(True)
00520         self.artist_paned.set_position(child_width)
00521 
00522     def _get_child_width(self):
00523         child = self.artist_paned.get_child1()
00524         return child.get_allocated_width()
00525 
00526     def on_artist_treeview_selection_changed(self, view):
00527         model, artist_iter = view.get_selected()
00528         if artist_iter:
00529             artist = model[artist_iter][0]
00530 
00531             cl = CoverLocale()
00532             cl.switch_locale(cl.Locale.RB)
00533             #. TRANSLATORS - "All" is used in the context of "All artist names"
00534             if artist == _('All'):
00535                 self.album_manager.model.remove_filter('quick_artist')
00536             else:
00537                 self.album_manager.model.replace_filter('quick_artist', artist)
00538 
00539             cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
00540 
00541     def _apply_settings(self):
00542         '''
00543         Applies all the settings related to the source and connects those that
00544         must be updated when the preferences dialog changes it's values. Also
00545         enables differents parts of the ui if the settings says so.
00546         '''
00547         print("CoverArtBrowser DEBUG - _apply_settings")
00548 
00549         # connect some signals to the loader to keep the source informed
00550         self.album_mod_id = self.album_manager.model.connect('album-updated',
00551                                                              self.on_album_updated)
00552 
00553         self.notify_prog_id = self.album_manager.connect(
00554             'notify::progress', lambda *args: self.notify_status_changed())
00555 
00556         print("CoverArtBrowser DEBUG - end _apply_settings")
00557 
00558     def load_finished_callback(self, _):
00559         '''
00560         Callback called when the loader finishes loading albums into the
00561         covers view model.
00562         '''
00563         print("CoverArtBrowser DEBUG - load_finished_callback")
00564 
00565         #if not self.request_status_box.get_visible():
00566         # it should only be enabled if no cover request is going on
00567         #self.source_menu_search_all_item.set_sensitive(True)
00568 
00569         # enable sorting on the entryview
00570         self.entry_view.set_columns_clickable(True)
00571         self.shell.props.library_source.get_entry_view().set_columns_clickable(
00572             True)
00573 
00574         print("CoverArtBrowser DEBUG - end load_finished_callback")
00575 
00576     def get_entry_view(self):
00577         return self.entry_view
00578 
00579     def on_album_updated(self, model, path, tree_iter):
00580         '''
00581         Callback called by the album loader when one of the albums managed
00582         by him gets modified in some way.
00583         '''
00584         album = model.get_from_path(path)
00585         selected = self.viewmgr.current_view.get_selected_objects()
00586 
00587         if album in selected:
00588             # update the selection since it may have changed
00589             self.viewmgr.current_view.selectionchanged_callback()
00590 
00591             if album is selected[0]:
00592                 self.entryviewpane.update_cover(album,
00593                                                 self.album_manager)
00594 
00595     def play_similar_artist_menu_item_callback(self, *args):
00596         '''
00597         Callback called when the play similar artist option is selected from
00598         the cover view popup. It plays similar artists music.
00599         '''
00600 
00601     def play_similar_artist_menu_item_callback(self, *args):
00602         if not self.echonest_similar_playlist:
00603             self.echonest_similar_playlist = \
00604                 EchoNestPlaylist(self.shell,
00605                                  self.shell.props.queue_source)
00606 
00607         selected_albums = self.viewmgr.current_view.get_selected_objects()
00608         album = selected_albums[0]
00609         tracks = album.get_tracks()
00610 
00611         entry = tracks[0].entry
00612         self.echonest_similar_playlist.start(entry, reinitialise=True)
00613 
00614     def show_properties_menu_item_callback(self, *args):
00615         '''
00616         Callback called when the show album properties option is selected from
00617         the cover view popup. It shows a SongInfo dialog showing the selected
00618         albums' entries info, which can be modified.
00619         '''
00620         print("CoverArtBrowser DEBUG - show_properties_menu_item_callback")
00621 
00622         self.entry_view.select_all()
00623 
00624         info_dialog = RB.SongInfo(source=self, entry_view=self.entry_view)
00625 
00626         info_dialog.show_all()
00627 
00628         print("CoverArtBrowser DEBUG - end show_properties_menu_item_callback")
00629 
00630     def play_selected_album(self, favourites=False):
00631         '''
00632         Utilitary method that plays all entries from an album into the play
00633         queue.
00634         '''
00635         # callback when play an album
00636         print("CoverArtBrowser DEBUG - play_selected_album")
00637 
00638         for row in self.source_query_model:
00639             self.source_query_model.remove_entry(row[0])
00640 
00641         self.queue_selected_album(self.source_query_model, favourites)
00642 
00643         if len(self.source_query_model) > 0:
00644             self.props.query_model = self.source_query_model
00645 
00646             # Start the music
00647             player = self.shell.props.shell_player
00648 
00649             player.play_entry(self.source_query_model[0][0], self)
00650 
00651         print("CoverArtBrowser DEBUG - end play_selected_album")
00652 
00653     def queue_selected_album(self, source, favourites=False):
00654         '''
00655         Utilitary method that queues all entries from an album into the play
00656         queue.
00657         '''
00658         print("CoverArtBrowser DEBUG - queue_selected_album")
00659 
00660         if source == None:
00661             source = self.source_query_model
00662 
00663         selected_albums = self.viewmgr.current_view.get_selected_objects()
00664         threshold = self.rating_threshold if favourites else 0
00665 
00666         total = 0
00667         for album in selected_albums:
00668             # Retrieve and sort the entries of the album
00669             tracks = album.get_tracks(threshold)
00670             total = total + len(tracks)
00671             # Add the songs to the play queue
00672             for track in tracks:
00673                 source.add_entry(track.entry, -1)
00674 
00675         if total == 0 and threshold:
00676             dialog = Gtk.MessageDialog(None,
00677                                        Gtk.DialogFlags.MODAL,
00678                                        Gtk.MessageType.INFO,
00679                                        Gtk.ButtonsType.OK,
00680                                        _(
00681                                            "No tracks have been added because no tracks meet the favourite rating threshold"))
00682 
00683             dialog.run()
00684             dialog.destroy()
00685         print("CoverArtBrowser DEBUG - end queue_select_album")
00686 
00687     def play_album_menu_item_callback(self, *args):
00688         '''
00689         Callback called when the play album item from the cover view popup is
00690         selected. It cleans the play queue and queues the selected album.
00691         '''
00692         print("CoverArtBrowser DEBUG - play_album_menu_item_callback")
00693 
00694         self.play_selected_album(self.favourites)
00695 
00696         print("CoverArtBrowser DEBUG - end play_album_menu_item_callback")
00697 
00698     def add_to_playing_menu_item_callback(self, *args):
00699         '''
00700         Callback called when the add-to-playing item from the cover view popup is
00701         selected. It adds the selected album at the end of the currently playing source.
00702         '''
00703 
00704         self.queue_selected_album(None, self.favourites)
00705 
00706     def queue_album_menu_item_callback(self, *args):
00707         '''
00708         Callback called when the queue album item from the cover view popup is
00709         selected. It queues the selected album at the end of the play queue.
00710         '''
00711         print("CoverArtBrowser DEBUG - queue_album_menu_item_callback()")
00712         self.queue_selected_album(self.shell.props.queue_source, self.favourites)
00713 
00714         print("CoverArtBrowser DEBUG - end queue_album_menu_item_callback()")
00715 
00716     def playlist_menu_item_callback(self, *args):
00717         print("CoverArtBrowser DEBUG - playlist_menu_item_callback")
00718 
00719         self.playlist_fillmenu(self.popup_menu, 'playlist_submenu', 'playlist_section',
00720                                self.actiongroup,
00721                                self.add_to_static_playlist_menu_item_callback,
00722                                self.favourites)
00723 
00724     def playlist_fillmenu(self, popup_menu, menubar, section_name,
00725                           actiongroup, func, favourite=False):
00726         print("CoverArtBrowser DEBUG - playlist_fillmenu")
00727 
00728         playlist_manager = self.shell.props.playlist_manager
00729         playlists_entries = playlist_manager.get_playlists()
00730 
00731         # tidy up old playlists menu items before recreating the list
00732         actiongroup.remove_actions()
00733         popup_menu.remove_menu_items(menubar, section_name)
00734 
00735         if playlists_entries:
00736             for playlist in playlists_entries:
00737                 if playlist.props.is_local and \
00738                         isinstance(playlist, RB.StaticPlaylistSource):
00739                     args = (playlist, favourite)
00740 
00741                     # take the name of the playlist, strip out non-english characters and reduce the string
00742                     # to just a-to-z characters i.e. this will make the action_name valid in RB3
00743 
00744                     ascii_name = unicodedata.normalize('NFKD', \
00745                                                        rb3compat.unicodestr(playlist.props.name, 'utf-8')).encode(
00746                         'ascii', 'ignore')
00747                     ascii_name = ascii_name.decode(encoding='UTF-8')
00748                     ascii_name = re.sub(r'[^a-zA-Z]', '', ascii_name)
00749                     action = actiongroup.add_action(func=func,
00750                                                     action_name=ascii_name,
00751                                                     playlist=playlist, favourite=favourite,
00752                                                     label=playlist.props.name)
00753 
00754                     popup_menu.add_menu_item(menubar, section_name,
00755                                              action)
00756 
00757     def add_to_static_playlist_menu_item_callback(self, action, param, args):
00758         print('''CoverArtBrowser DEBUG -
00759             add_to_static_playlist_menu_item_callback''')
00760 
00761         playlist = args['playlist']
00762         favourite = args['favourite']
00763 
00764         self.queue_selected_album(playlist, favourite)
00765 
00766     def add_playlist_menu_item_callback(self, *args):
00767         print('''CoverArtBrowser DEBUG - add_playlist_menu_item_callback''')
00768         playlist_manager = self.shell.props.playlist_manager
00769         playlist = playlist_manager.new_playlist(_('New Playlist'), False)
00770 
00771         self.queue_selected_album(playlist, self.favourites)
00772 
00773     def play_random_album_menu_item_callback(self, favourites=False):
00774         print('''CoverArtBrowser DEBUG - play_random_album_menu_item_callback''')
00775         query_model = RB.RhythmDBQueryModel.new_empty(self.shell.props.db)
00776 
00777         num_albums = len(self.album_manager.model.store)
00778 
00779         #random_list = []
00780         selected_albums = []
00781 
00782         gs = GSetting()
00783         settings = gs.get_setting(gs.Path.PLUGIN)
00784         to_queue = settings[gs.PluginKey.RANDOM]
00785 
00786         if num_albums <= to_queue:
00787             dialog = Gtk.MessageDialog(None,
00788                                        Gtk.DialogFlags.MODAL,
00789                                        Gtk.MessageType.INFO,
00790                                        Gtk.ButtonsType.OK,
00791                                        _("The number of albums to randomly play is less than that displayed."))
00792 
00793             dialog.run()
00794             dialog.destroy()
00795             return
00796 
00797         album_col = self.album_manager.model.columns['album']
00798 
00799         chosen = {}
00800 
00801         # now loop through finding unique random albums
00802         # i.e. ensure we dont queue the same album twice
00803 
00804         for loop in range(0, to_queue):
00805             while True:
00806                 pos = random.randint(0, num_albums - 1)
00807                 if pos not in chosen:
00808                     chosen[pos] = None
00809                     selected_albums.append(self.album_manager.model.store[pos][album_col])
00810                     break
00811 
00812         threshold = self.rating_threshold if favourites else 0
00813 
00814         total = 0
00815         for album in selected_albums:
00816             # Retrieve and sort the entries of the album
00817             tracks = album.get_tracks(threshold)
00818             total = total + len(tracks)
00819             # Add the songs to the play queue
00820             for track in tracks:
00821                 query_model.add_entry(track.entry, -1)
00822 
00823         if total == 0 and threshold:
00824             dialog = Gtk.MessageDialog(None,
00825                                        Gtk.DialogFlags.MODAL,
00826                                        Gtk.MessageType.INFO,
00827                                        Gtk.ButtonsType.OK,
00828                                        _(
00829                                            "No tracks have been added because no tracks meet the favourite rating threshold"))
00830 
00831             dialog.run()
00832             dialog.destroy()
00833 
00834         self.props.query_model = query_model
00835 
00836         # Start the music
00837         player = self.shell.props.shell_player
00838 
00839         player.play_entry(query_model[0][0], self)
00840 
00841         print("CoverArtBrowser DEBUG - end play_selected_album")
00842 
00843     def cover_search_menu_item_callback(self, *args):
00844         '''
00845         Callback called when the search cover option is selected from the
00846         cover view popup. It prompts the album loader to retrieve the selected
00847         album cover
00848         '''
00849         print("CoverArtBrowser DEBUG - cover_search_menu_item_callback()")
00850         selected_albums = self.viewmgr.current_view.get_selected_objects()
00851 
00852         self.request_status_box.show_all()
00853 
00854         self.album_manager.cover_man.search_covers(selected_albums,
00855                                                    self.update_request_status_bar)
00856 
00857         print("CoverArtBrowser DEBUG - end cover_search_menu_item_callback()")
00858 
00859     def export_embed_menu_item_callback(self, *args):
00860         '''
00861         Callback called when the export and embed coverart option
00862         is selected from the cover view popup.
00863         It prompts the exporter to copy and embed art for the albums chosen
00864         '''
00865         print("CoverArtBrowser DEBUG - export_embed_menu_item_callback()")
00866         selected_albums = self.viewmgr.current_view.get_selected_objects()
00867 
00868         CoverArtExport(self.plugin,
00869                        self.shell, self.album_manager).embed_albums(selected_albums)
00870 
00871         print("CoverArtBrowser DEBUG - export_embed_menu_item_callback()")
00872 
00873     def update_request_status_bar(self, coverobject):
00874         '''
00875         Callback called by the album loader starts performing a new cover
00876         request. It prompts the source to change the content of the request
00877         statusbar.
00878         '''
00879         print("CoverArtBrowser DEBUG - update_request_status_bar")
00880 
00881         if coverobject:
00882             # for example "Requesting the picture cover for the music artist Michael Jackson"
00883             tranlation_string = _('Requesting cover for %s...')
00884             self.request_statusbar.set_text(
00885                 rb3compat.unicodedecode(_('Requesting cover for %s...') % (coverobject.name), 'UTF-8'))
00886         else:
00887             self.request_status_box.hide()
00888             self.popup_menu.set_sensitive('cover_search_menu_item', True)
00889             self.request_cancel_button.set_sensitive(True)
00890         print("CoverArtBrowser DEBUG - end update_request_status_bar")
00891 
00892     def cancel_request_callback(self, _):
00893         '''
00894         Callback connected to the cancel button on the request statusbar.
00895         When called, it prompts the album loader to cancel the full cover
00896         search after the current cover.
00897         '''
00898         print("CoverArtBrowser DEBUG - cancel_request_callback")
00899 
00900         self.request_cancel_button.set_sensitive(False)
00901         self._cover_search_manager.cover_man.cancel_cover_request()
00902 
00903         print("CoverArtBrowser DEBUG - end cancel_request_callback")
00904 
00905     def show_hide_pane(self, params):
00906         '''
00907         helper function - if the entry is manually expanded
00908         then if necessary scroll the view to the last selected album
00909         params is "album" or a tuple of "album" and "force_expand" boolean
00910         '''
00911         print ('show_hide_pane')
00912         if isinstance(params, tuple):
00913             album, force = params
00914         else:
00915             album = params
00916             force = PanedCollapsible.Paned.DEFAULT
00917 
00918         if (album and self.click_count == 1 \
00919                     and self.last_selected_album is album) or force != PanedCollapsible.Paned.DEFAULT:
00920             # check if it's a second or third click on the album and expand
00921             # or collapse the entry view accordingly
00922             self.paned.expand(force)
00923 
00924         # update the selected album
00925         selected = self.viewmgr.current_view.get_selected_objects()
00926         self.last_selected_album = selected[0] if len(selected) == 1 else None
00927 
00928         # clear the click count
00929         self.click_count = 0
00930 
00931         print ('show_hide_pane end')
00932 
00933     def update_with_selection(self):
00934         self.last_selected_album, self.click_count = \
00935             self.entryviewpane.update_selection(self.last_selected_album,
00936                                                 self.click_count)
00937 
00938         self.statusbar.emit('display-status', self.viewmgr.current_view)
00939 
00940     def propertiesbutton_callback(self, choice):
00941         print ("properties chosen: %s" % choice)
00942 
00943         if choice == 'download':
00944             self.request_status_box.show_all()
00945             self._cover_search_manager = self.viewmgr.current_view.get_default_manager()
00946             self._cover_search_manager.cover_man.search_covers(
00947                 callback=self.update_request_status_bar)
00948         elif choice == 'random':
00949             self.play_random_album_menu_item_callback()
00950         elif choice == 'random favourite':
00951             self.play_random_album_menu_item_callback(True)
00952         elif choice == 'favourite':
00953             self.favourites = not self.favourites
00954             self.viewmgr.current_view.set_popup_menu(self.popup_menu)
00955         elif choice == 'follow':
00956             self.follow_song = not self.follow_song
00957         elif choice == 'browser prefs':
00958             if not self._browser_preferences:
00959                 self._browser_preferences = Preferences()
00960 
00961             self._browser_preferences.display_preferences_dialog(self.plugin)
00962         elif choice == 'search prefs':
00963             try:
00964                 if not self._search_preferences:
00965                     from gi.repository import Peas
00966 
00967                     peas = Peas.Engine.get_default()
00968                     plugin_info = peas.get_plugin_info('coverart_search_providers')
00969                     module_name = plugin_info.get_module_name()
00970                     mod = __import__(module_name)
00971                     sp = getattr(mod, "SearchPreferences")
00972                     self._search_preferences = sp()
00973                     self._search_preferences.plugin_info = plugin_info
00974 
00975                 self._search_preferences.display_preferences_dialog(self._search_preferences)
00976             except:
00977                 dialog = Gtk.MessageDialog(None,
00978                                            Gtk.DialogFlags.MODAL,
00979                                            Gtk.MessageType.INFO,
00980                                            Gtk.ButtonsType.OK,
00981                                            _(
00982                                                "Please install and activate the latest version of the Coverart Search Providers plugin"))
00983 
00984                 dialog.run()
00985                 dialog.destroy()
00986         else:
00987             assert 1 == 2, ("unknown choice %s", choice)
00988 
00989     @classmethod
00990     def get_instance(cls, **kwargs):
00991         '''
00992         Returns the unique instance of the manager.
00993         '''
00994         if not cls.instance:
00995             cls.instance = CoverArtBrowserSource(**kwargs)
00996 
00997         return cls.instance
00998 
00999 
01000 class Statusbar(GObject.Object):
01001     # signals
01002     __gsignals__ = {
01003         'display-status': (GObject.SIGNAL_RUN_LAST, None, (object,))
01004     }
01005 
01006     custom_statusbar_enabled = GObject.property(type=bool, default=False)
01007 
01008     def __init__(self, source):
01009         super(Statusbar, self).__init__()
01010 
01011         self.status = ''
01012 
01013         self._source_statusbar = SourceStatusBar(source)
01014         self._custom_statusbar = CustomStatusBar(source.status_label)
01015         self.current_statusbar = self._source_statusbar
01016 
01017         self._connect_signals(source)
01018         self._connect_properties()
01019 
01020     def _connect_properties(self):
01021         gs = GSetting()
01022         settings = gs.get_setting(gs.Path.PLUGIN)
01023 
01024         settings.bind(gs.PluginKey.CUSTOM_STATUSBAR, self,
01025                       'custom_statusbar_enabled', Gio.SettingsBindFlags.GET)
01026 
01027     def _connect_signals(self, source):
01028         self.connect('notify::custom-statusbar-enabled',
01029                      self._custom_statusbar_enabled_changed)
01030         self.connect('display-status', self._update)
01031 
01032     def _custom_statusbar_enabled_changed(self, *args):
01033         self.current_statusbar.hide()
01034 
01035         if self.custom_statusbar_enabled:
01036             self.current_statusbar = self._custom_statusbar
01037         else:
01038             self.current_statusbar = self._source_statusbar
01039 
01040         self.current_statusbar.show()
01041         self.current_statusbar.update(self.status)
01042 
01043     def _generate_status(self, albums=None):
01044         self.status = ''
01045 
01046         if albums:
01047             track_count = 0
01048             duration = 0
01049 
01050             for album in albums:
01051                 # Calculate duration and number of tracks from that album
01052                 track_count += album.track_count
01053                 duration += album.duration / 60
01054 
01055             # now lets build up a status label containing some
01056             # 'interesting stuff' about the album
01057             if len(albums) == 1:
01058                 #. TRANSLATORS - for example "abba's greatest hits by ABBA"
01059                 self.status = rb3compat.unicodedecode(_('%s by %s') %
01060                                                       (album.name, album.artist), 'UTF-8')
01061             else:
01062                 #. TRANSLATORS - the number of albums that have been selected/highlighted
01063                 self.status = rb3compat.unicodedecode(_('%d selected albums') %
01064                                                       (len(albums)), 'UTF-8')
01065 
01066             if track_count == 1:
01067                 self.status += rb3compat.unicodedecode(_(' with 1 track'), 'UTF-8')
01068             else:
01069                 self.status += rb3compat.unicodedecode(_(' with %d tracks') %
01070                                                        track_count, 'UTF-8')
01071 
01072             if duration == 1:
01073                 self.status += rb3compat.unicodedecode(_(' and a duration of 1 minute'), 'UTF-8')
01074             else:
01075                 self.status += rb3compat.unicodedecode(_(' and a duration of %d minutes') %
01076                                                        duration, 'UTF-8')
01077 
01078     def _update(self, widget, current_view):
01079         albums = current_view.get_selected_objects()
01080         self._generate_status(albums)
01081         self.current_statusbar.update(self.status)
01082 
01083 
01084 class SourceStatusBar(object):
01085     def __init__(self, source):
01086         self._source = source
01087 
01088     def show(self):
01089         pass
01090 
01091     def hide(self):
01092         self.update('')
01093 
01094     def update(self, status):
01095         self._source.status = status
01096         self._source.notify_status_changed()
01097 
01098 
01099 class CustomStatusBar(object):
01100     def __init__(self, status_label):
01101         self._label = status_label
01102 
01103     def show(self):
01104         self._label.show()
01105 
01106     def hide(self):
01107         self._label.hide()
01108 
01109     def update(self, status):
01110         self._label.set_text(status)
01111 
01112 
01113 class Views:
01114     '''
01115     This class describes the different views available
01116     '''
01117     # storage for the instance reference
01118     __instance = None
01119 
01120     class _impl(GObject.Object):
01121         """ Implementation of the singleton interface """
01122 
01123         # below public variables and methods that can be called for Views
01124         def __init__(self, shell):
01125             '''
01126             Initializes the singleton interface, assigning all the constants
01127             used to access the plugin's settings.
01128             '''
01129             super(Views._impl, self).__init__()
01130 
01131             from coverart_covericonview import CoverIconView
01132             from coverart_coverflowview import CoverFlowView
01133             from coverart_artistview import ArtistView
01134             from coverart_listview import ListView
01135             from coverart_queueview import QueueView
01136             from coverart_playsourceview import PlaySourceView
01137             from coverart_browser_prefs import webkit_support
01138 
01139             library_name = shell.props.library_source.props.name
01140             queue_name = shell.props.queue_source.props.name
01141 
01142             self._values = OrderedDict()
01143 
01144             cl = CoverLocale()
01145             cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
01146 
01147             self._values[CoverIconView.name] = [_('Tiles'),
01148                                                 GLib.Variant.new_string('coverart-browser-tile')]
01149             if webkit_support():
01150                 self._values[CoverFlowView.name] = [_('Flow'),
01151                                                     GLib.Variant.new_string('coverart-browser-coverflow')]
01152             self._values[ArtistView.name] = [_('Artist'),
01153                                              GLib.Variant.new_string('coverart-browser-artist')]
01154             self._values[ListView.name] = [library_name,
01155                                            GLib.Variant.new_string('coverart-browser-list')]
01156             self._values[QueueView.name] = [queue_name,
01157                                             GLib.Variant.new_string('coverart-browser-queue')]
01158             #self._values[PlaySourceView.name] = [_('CoverArt Playlist'),
01159             #                                GLib.Variant.new_string('coverart-browser-playsource')]
01160             cl.switch_locale(cl.Locale.RB)
01161             print(self._values)
01162 
01163         def get_view_names(self):
01164             return list(self._values.keys())
01165 
01166         def get_view_name_for_action(self, action_name):
01167             for view_name in self.get_view_names():
01168                 if self.get_action_name(view_name) == action_name:
01169                     return view_name
01170 
01171             return None
01172 
01173         def get_menu_name(self, view_name):
01174             return self._values[view_name][0]
01175 
01176         def get_action_name(self, view_name):
01177             return self._values[view_name][1]
01178 
01179     def __init__(self, plugin):
01180         """ Create singleton instance """
01181         # Check whether we already have an instance
01182         if Views.__instance is None:
01183             # Create and remember instance
01184             Views.__instance = Views._impl(plugin)
01185 
01186         # Store instance reference as the only member in the handle
01187         self.__dict__['_Views__instance'] = Views.__instance
01188 
01189     def __getattr__(self, attr):
01190         """ Delegate access to implementation """
01191         return getattr(self.__instance, attr)
01192 
01193     def __setattr__(self, attr, value):
01194         """ Delegate access to implementation """
01195         return setattr(self.__instance, attr, value)
01196 
01197 
01198 class ViewManager(GObject.Object):
01199     # signals
01200     __gsignals__ = {
01201         'new-view': (GObject.SIGNAL_RUN_LAST, None, (str,))
01202     }
01203 
01204     # properties
01205     view_name = GObject.property(type=str, default=CoverIconView.name)
01206 
01207     def __init__(self, source, window):
01208         super(ViewManager, self).__init__()
01209 
01210         self.source = source
01211         self.window = window
01212 
01213         # initialize views
01214         self._views = {}
01215         ui = Gtk.Builder()
01216         ui.add_from_file(rb.find_plugin_file(source.plugin,
01217                                              'ui/coverart_iconview.ui'))
01218         self._views[CoverIconView.name] = ui.get_object('covers_view')
01219         self._views[CoverFlowView.name] = CoverFlowView()
01220         self._views[ListView.name] = ListView()
01221         self._views[QueueView.name] = QueueView()
01222         #self._views[PlaySourceView.name] = PlaySourceView()
01223         ui.add_from_file(rb.find_plugin_file(source.plugin,
01224                                              'ui/coverart_artistview.ui'))
01225         self._views[ArtistView.name] = ui.get_object('artist_view')
01226         self._lastview = None
01227 
01228         self.controller = ViewController(source.shell, self)
01229 
01230         # connect signal and properties
01231         self._connect_signals()
01232         self._connect_properties()
01233         self._lastview = self.view_name
01234         if self.current_view.use_plugin_window:
01235             window.add(self.current_view.view)
01236             window.show_all()
01237 
01238     @property
01239     def current_view(self):
01240         return self._views[self.view_name]
01241 
01242     def get_view(self, view_name):
01243         return self._views[view_name]
01244 
01245     def _connect_signals(self):
01246         self.connect('notify::view-name', self.on_notify_view_name)
01247 
01248     def _connect_properties(self):
01249         gs = GSetting()
01250         setting = gs.get_setting(gs.Path.PLUGIN)
01251         setting.bind(gs.PluginKey.VIEW_NAME, self, 'view_name',
01252                      Gio.SettingsBindFlags.DEFAULT)
01253 
01254     def on_notify_view_name(self, *args):
01255         if self._lastview and self.view_name != self._lastview:
01256             selected = self._views[self._lastview].get_selected_objects()
01257             current_album = None
01258             if len(selected) > 0:
01259                 current_album = self._views[self._lastview].get_selected_objects()[0]
01260 
01261             if self._views[self.view_name].use_plugin_window:
01262                 child = self.window.get_child()
01263 
01264                 if child:
01265                     self.window.remove(child)
01266                 self.window.add(self._views[self.view_name].view)
01267                 self.window.show_all()
01268                 self.click_count = 0
01269 
01270                 self._views[self._lastview].panedposition = self.source.paned.get_expansion_status()
01271 
01272             self._views[self.view_name].switch_to_view(self.source, current_album)
01273             self._views[self.view_name].emit('update-toolbar')
01274             self._views[self.view_name].get_default_manager().emit('sort', None)
01275 
01276             if self._views[self.view_name].use_plugin_window:
01277                 self.source.paned.expand(self._views[self.view_name].panedposition)
01278 
01279             self.current_view.set_popup_menu(self.source.popup_menu)
01280             self.source.album_manager.current_view = self.current_view
01281 
01282             if self._views[self.view_name].use_plugin_window:
01283                 # we only ever save plugin views not external views
01284                 saved_view = self.view_name
01285             else:
01286                 saved_view = self._lastview
01287 
01288             self._lastview = self.view_name
01289 
01290             gs = GSetting()
01291             setting = gs.get_setting(gs.Path.PLUGIN)
01292             setting[gs.PluginKey.VIEW_NAME] = saved_view
01293 
01294         self.emit('new-view', self.view_name)
01295 
01296     def get_view_icon_name(self, view_name):
01297         return self._views[view_name].get_view_icon_name()
01298 
01299     def get_selection_colour(self):
01300         try:
01301             colour = self._views[CoverIconView.name].view.get_style_context().get_background_color(
01302                 Gtk.StateFlags.SELECTED)
01303             colour = '#%s%s%s' % (
01304                 str(hex(int(colour.red * 255))).replace('0x', ''),
01305                 str(hex(int(colour.green * 255))).replace('0x', ''),
01306                 str(hex(int(colour.blue * 255))).replace('0x', ''))
01307         except:
01308             colour = '#0000FF'
01309 
01310         return colour
01311 
01312 
01313 GObject.type_register(CoverArtBrowserSource)
 All Classes Functions