def setup_source(self): colour = self.viewmgr.get_selection_colour() self.cover_search_pane = CoverSearchPane(self.plugin, colour) self.stack.add_titled(self.cover_search_pane, "notebook_covers", _("Covers")) # define entry-view toolbar self.stars = ReactiveStar() self.stars.set_rating(0) self.stars.connect('changed', self.rating_changed_callback) self.stars.props.valign = Gtk.Align.CENTER self.entry_view_grid.attach(self.stars, 1, 1, 1, 1) stack_switcher = Gtk.StackSwitcher() stack_switcher.set_stack(self.stack) self.entry_view_grid.attach(stack_switcher, 0, 1, 1, 1) viewtoggle = PixbufButton() viewtoggle.set_image(create_button_image(self.plugin, "entryview.png")) self.viewtoggle_id = None setting = self.gs.get_setting(self.gs.Path.PLUGIN) viewtoggle.set_active(not setting[self.gs.PluginKey.ENTRY_VIEW_MODE]) self.entry_view_toggled(viewtoggle, True) viewtoggle.connect('toggled', self.entry_view_toggled) smallwindowbutton = PixbufButton() smallwindowbutton.set_image( create_button_image(self.plugin, "view-restore.png")) smallwindowbutton.connect('toggled', self.smallwindowbutton_callback) self.smallwindowext = ExternalPlugin() self.smallwindowext.appendattribute('plugin_name', 'smallwindow') self.smallwindowext.appendattribute('action_group_name', 'small window actions') self.smallwindowext.appendattribute('action_name', 'SmallWindow') self.smallwindowext.appendattribute('action_type', 'app') whatsplayingtoggle = PixbufButton() whatsplayingtoggle.set_image( create_button_image(self.plugin, "whatsplaying.png")) whatsplayingtoggle.connect('toggled', self.whatsplayingtoggle_callback) rightgrid = Gtk.Grid() rightgrid.props.halign = Gtk.Align.END # rightgrid.attach(whatsplayingtoggle, 0, 0, 1, 1) rightgrid.attach(viewtoggle, 1, 0, 1, 1) rightgrid.attach(smallwindowbutton, 2, 0, 1, 1) self.entry_view_grid.attach_next_to(rightgrid, self.stars, Gtk.PositionType.RIGHT, 1, 1) self.stack.set_visible_child(self.entry_view_results) self.stack.connect('notify::visible-child-name', self.notebook_switch_page_callback) self.entry_view_grid.show_all() smallwindowbutton.set_visible(self.smallwindowext.is_activated())
def setup_source(self): colour = self.viewmgr.get_selection_colour() self.cover_search_pane = CoverSearchPane(self.plugin, colour) self.stack.add_titled(self.cover_search_pane, "notebook_covers", _("Covers")) # define entry-view toolbar self.stars = ReactiveStar() self.stars.set_rating(0) self.stars.connect('changed', self.rating_changed_callback) self.stars.props.valign = Gtk.Align.CENTER self.entry_view_grid.attach(self.stars, 1, 1, 1, 1) stack_switcher = Gtk.StackSwitcher() stack_switcher.set_stack(self.stack) style_context = self.stack.get_style_context() leftgrid = Gtk.Grid() leftgrid.attach(stack_switcher, 1, 0, 1, 1) self.entry_view_grid.attach(leftgrid, 0, 1, 1, 1) viewtoggle = PixbufButton() viewtoggle.set_image(symbolic='entryview-symbolic') viewtoggle.set_margin_right(6) self.viewtoggle_id = None setting = self.gs.get_setting(self.gs.Path.PLUGIN) viewtoggle.set_active(not setting[self.gs.PluginKey.ENTRY_VIEW_MODE]) self.entry_view_toggled(viewtoggle, True) viewtoggle.connect('toggled', self.entry_view_toggled) smallwindowbutton = PixbufButton() smallwindowbutton.set_image(symbolic='view-restore-symbolic') smallwindowbutton.set_margin_right(6) smallwindowbutton.connect('toggled', self.smallwindowbutton_callback) self.smallwindowext = ExternalPlugin() self.smallwindowext.appendattribute('plugin_name', 'smallwindow') self.smallwindowext.appendattribute('action_group_name', 'small window actions') self.smallwindowext.appendattribute('action_name', 'SmallWindow') self.smallwindowext.appendattribute('action_type', 'app') rightgrid = Gtk.Grid() rightgrid.props.halign = Gtk.Align.END rightgrid.attach(viewtoggle, 1, 0, 1, 1) rightgrid.attach(smallwindowbutton, 2, 0, 1, 1) self.entry_view_grid.attach_next_to(rightgrid, self.stars, Gtk.PositionType.RIGHT, 1, 1) self.stack.set_visible_child(self.entry_view_results) self.stack.connect('notify::visible-child-name', self.notebook_switch_page_callback) self.entry_view_grid.show_all() smallwindowbutton.set_visible(self.smallwindowext.is_activated())
class EntryViewPane(object): ''' encapulates all of the Track Pane objects ''' def __init__(self, shell, plugin, source, entry_view_grid, viewmgr): self.gs = GSetting() self.entry_view_grid = entry_view_grid self.shell = shell self.viewmgr = viewmgr self.plugin = plugin self.source = source # setup entry-view objects and widgets self.stack = Gtk.Stack() self.stack.set_transition_type( Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) self.stack.set_transition_duration(750) # create entry views. Don't allow to reorder until the load is finished self.entry_view_compact = CoverArtCompactEntryView( self.shell, self.source) self.entry_view_full = CoverArtEntryView(self.shell, self.source) self.entry_view = self.entry_view_compact self.shell.props.library_source.get_entry_view().set_columns_clickable( False) self.entry_view_results = ResultsGrid() self.entry_view_results.initialise(self.entry_view_grid, source) self.stack.add_titled(self.entry_view_results, "notebook_tracks", _("Tracks")) self.entry_view_grid.attach(self.stack, 0, 0, 3, 1) def setup_source(self): colour = self.viewmgr.get_selection_colour() self.cover_search_pane = CoverSearchPane(self.plugin, colour) self.stack.add_titled(self.cover_search_pane, "notebook_covers", _("Covers")) # define entry-view toolbar self.stars = ReactiveStar() self.stars.set_rating(0) self.stars.connect('changed', self.rating_changed_callback) self.stars.props.valign = Gtk.Align.CENTER self.entry_view_grid.attach(self.stars, 1, 1, 1, 1) stack_switcher = Gtk.StackSwitcher() stack_switcher.set_stack(self.stack) self.entry_view_grid.attach(stack_switcher, 0, 1, 1, 1) viewtoggle = PixbufButton() viewtoggle.set_image(create_button_image(self.plugin, "entryview.png")) self.viewtoggle_id = None setting = self.gs.get_setting(self.gs.Path.PLUGIN) viewtoggle.set_active(not setting[self.gs.PluginKey.ENTRY_VIEW_MODE]) self.entry_view_toggled(viewtoggle, True) viewtoggle.connect('toggled', self.entry_view_toggled) smallwindowbutton = PixbufButton() smallwindowbutton.set_image( create_button_image(self.plugin, "view-restore.png")) smallwindowbutton.connect('toggled', self.smallwindowbutton_callback) self.smallwindowext = ExternalPlugin() self.smallwindowext.appendattribute('plugin_name', 'smallwindow') self.smallwindowext.appendattribute('action_group_name', 'small window actions') self.smallwindowext.appendattribute('action_name', 'SmallWindow') self.smallwindowext.appendattribute('action_type', 'app') whatsplayingtoggle = PixbufButton() whatsplayingtoggle.set_image( create_button_image(self.plugin, "whatsplaying.png")) whatsplayingtoggle.connect('toggled', self.whatsplayingtoggle_callback) rightgrid = Gtk.Grid() rightgrid.props.halign = Gtk.Align.END # rightgrid.attach(whatsplayingtoggle, 0, 0, 1, 1) rightgrid.attach(viewtoggle, 1, 0, 1, 1) rightgrid.attach(smallwindowbutton, 2, 0, 1, 1) self.entry_view_grid.attach_next_to(rightgrid, self.stars, Gtk.PositionType.RIGHT, 1, 1) self.stack.set_visible_child(self.entry_view_results) self.stack.connect('notify::visible-child-name', self.notebook_switch_page_callback) self.entry_view_grid.show_all() smallwindowbutton.set_visible(self.smallwindowext.is_activated()) def whatsplayingtoggle_callback(self, widget): self.entry_view_results.emit('whats-playing', widget.get_active()) def smallwindowbutton_callback(self, widget): if widget.get_active(): self.smallwindowext.activate(self.shell) widget.emit('clicked') def entry_view_toggled(self, widget, initialised=False): print("DEBUG - entry_view_toggled") if widget.get_active(): next_view = self.entry_view_full show_coverart = False if self.viewtoggle_id: self.shell.props.window.disconnect(self.viewtoggle_id) self.viewtoggle_id = None else: next_view = self.entry_view_compact show_coverart = True self.viewtoggle_id = self.shell.props.window.connect( 'check_resize', self.entry_view_results.window_resize) setting = self.gs.get_setting(self.gs.Path.PLUGIN) setting[self.gs.PluginKey.ENTRY_VIEW_MODE] = not widget.get_active() self.entry_view_results.change_view(next_view, show_coverart) self.entry_view = next_view if not initialised: self.source.update_with_selection() def notebook_switch_page_callback(self, *args): ''' Callback called when the notebook page gets switched. It initiates the cover search when the cover search pane's page is selected. ''' print("CoverArtBrowser DEBUG - notebook_switch_page_callback") if self.stack.get_visible_child_name() == 'notebook_covers': self.viewmgr.current_view.switch_to_coverpane( self.cover_search_pane) else: entries = self.entry_view.get_selected_entries() if entries and len(entries) > 0: self.entry_view_results.emit('update-cover', self.source, entries[0]) else: selected = self.viewmgr.current_view.get_selected_objects() tracks = selected[0].get_tracks() self.entry_view_results.emit('update-cover', self.source, tracks[0].entry) print("CoverArtBrowser DEBUG - end notebook_switch_page_callback") def rating_changed_callback(self, widget): ''' Callback called when the Rating stars is changed ''' print("CoverArtBrowser DEBUG - rating_changed_callback") rating = widget.get_rating() for album in self.viewmgr.current_view.get_selected_objects(): album.rating = rating print("CoverArtBrowser DEBUG - end rating_changed_callback") def get_entry_view(self): return self.entry_view def update_cover(self, album_artist, manager): if not self.stack.get_visible_child_name() == "notebook_covers": return self.cover_search_pane.clear() self.cover_search(album_artist, manager) def cover_search(self, album_artist, manager): self.cover_search_pane.do_search(album_artist, manager.cover_man.update_cover) def update_selection(self, last_selected_album, click_count): ''' Update the source view when an item gets selected. ''' print("DEBUG - update_with_selection") selected = self.viewmgr.current_view.get_selected_objects() # clear the entry view self.entry_view.clear() cover_search_pane_visible = self.stack.get_visible_child_name( ) == "notebook_covers" if not selected: # clean cover tab if selected if cover_search_pane_visible: self.cover_search_pane.clear() self.entry_view_results.emit('update-cover', self.source, None) return last_selected_album, click_count elif len(selected) == 1: self.stars.set_rating(selected[0].rating) if selected[0] is not last_selected_album: # when the selection changes we've to take into account two # things if not click_count: # we may be using the arrows, so if there is no mouse # involved, we should change the last selected last_selected_album = selected[0] else: # we may've doing a fast change after a valid second click, # so it shouldn't be considered a double click click_count -= 1 else: self.stars.set_rating(0) if len(selected) == 1: self.source.artist_info.emit('selected', selected[0].artist, selected[0].name) self.entry_view.set_sorting_order('track-number', Gtk.SortType.ASCENDING) for album in selected: # add the album to the entry_view self.entry_view.add_album(album) if len(selected) > 0: def cover_update(*args): print("emitting") self.entry_view_results.emit('update-cover', self.source, selected[0].get_tracks()[0].entry) # add a short delay to give the entry-pane time to expand etc. Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250, cover_update, None) # update the cover search pane with the first selected album if cover_search_pane_visible: self.cover_search_pane.do_search( selected[0], self.source.album_manager.cover_man.update_cover) return last_selected_album, click_count
class CoverArtBrowserSource(RB.Source): ''' Source utilized by the plugin to show all it's ui. ''' custom_statusbar_enabled = GObject.property(type=bool, default=False) rating_threshold = GObject.property(type=float, default=0) icon_spacing = GObject.property(type=int, default=0) icon_padding = GObject.property(type=int, default=0) # unique instance of the source instance = None def __init__(self, **kargs): ''' Initializes the source. ''' super(CoverArtBrowserSource, self).__init__(**kargs) # create source_source_settings and connect the source's properties self.gs = GSetting() self._connect_properties() self.hasActivated = False self.last_width = 0 self.last_selected_album = None self.click_count = 0 def _connect_properties(self): ''' Connects the source properties to the saved preferences. ''' print "CoverArtBrowser DEBUG - _connect_properties" setting = self.gs.get_setting(self.gs.Path.PLUGIN) setting.bind(self.gs.PluginKey.CUSTOM_STATUSBAR, self, 'custom_statusbar_enabled', Gio.SettingsBindFlags.GET) setting.bind(self.gs.PluginKey.RATING, self, 'rating_threshold', Gio.SettingsBindFlags.GET) setting.bind(self.gs.PluginKey.ICON_SPACING, self, 'icon_spacing', Gio.SettingsBindFlags.GET) setting.bind(self.gs.PluginKey.ICON_PADDING, self, 'icon_padding', Gio.SettingsBindFlags.GET) print "CoverArtBrowser DEBUG - end _connect_properties" def do_get_status(self, *args): ''' Method called by Rhythmbox to figure out what to show on this source statusbar. If the custom statusbar is disabled, the source will show the selected album info. Also, it makes sure to show the progress on the album loading.s ''' try: progress = self.album_manager.progress progress_text = _('Loading...') if progress < 1 else '' except: progress = 1 progress_text = '' return (self.status, progress_text, progress) def do_show_popup(self): ''' Method called by Rhythmbox when an action on our source prompts it to show a popup. ''' print "CoverArtBrowser DEBUG - do_show_popup" self.source_menu.popup(None, None, None, None, 0, Gtk.get_current_event_time()) print "CoverArtBrowser DEBUG - end do_show_popup" return True def do_selected(self): ''' Called by Rhythmbox when the source is selected. It makes sure to create the ui the first time the source is showed. ''' print "CoverArtBrowser DEBUG - do_selected" # first time of activation -> add graphical stuff if not self.hasActivated: self.do_impl_activate() # indicate that the source was activated before self.hasActivated = True print "CoverArtBrowser DEBUG - end do_selected" def do_impl_activate(self): ''' Called by do_selected the first time the source is activated. It creates all the source ui and connects the necesary signals for it correct behavior. ''' print "CoverArtBrowser DEBUG - do_impl_activate" # initialise some variables self.plugin = self.props.plugin self.shell = self.props.shell self.status = '' self.search_text = '' self.actiongroup = Gtk.ActionGroup('coverplaylist_submenu') self.favourite_actiongroup = Gtk.ActionGroup( 'favourite_coverplaylist_submenu') uim = self.shell.props.ui_manager uim.insert_action_group(self.actiongroup) uim.insert_action_group(self.favourite_actiongroup) # connect properties signals self.connect('notify::custom-statusbar-enabled', self.on_notify_custom_statusbar_enabled) self.connect('notify::rating-threshold', self.on_notify_rating_threshold) self.connect('notify::icon-spacing', self.on_notify_icon_spacing) self.connect('notify::icon-padding', self.on_notify_icon_padding) # indicate that the source was activated before self.hasActivated = True self._create_ui() self._setup_source() self._apply_settings() print "CoverArtBrowser DEBUG - end do_impl_activate" def _create_ui(self): ''' Creates the ui for the source and saves the important widgets onto properties. ''' print "CoverArtBrowser DEBUG - _create_ui" # dialog has not been created so lets do so. cl = CoverLocale() ui = Gtk.Builder() ui.set_translation_domain(cl.Locale.LOCALE_DOMAIN) ui.add_from_file(rb.find_plugin_file(self.plugin, 'ui/coverart_browser.ui')) ui.connect_signals(self) # load the page and put it in the source self.page = ui.get_object('main_box') self.pack_start(self.page, True, True, 0) # get widgets for main icon-view self.status_label = ui.get_object('status_label') self.covers_view = ui.get_object('covers_view') self.popup_menu = ui.get_object('popup_menu') self.cover_search_menu_item = ui.get_object('cover_search_menu_item') self.status_label = ui.get_object('status_label') self.request_status_box = ui.get_object('request_status_box') self.request_spinner = ui.get_object('request_spinner') self.request_statusbar = ui.get_object('request_statusbar') self.request_cancel_button = ui.get_object('request_cancel_button') self.paned = ui.get_object('paned') self.notebook = ui.get_object('bottom_notebook') # get widgets for source popup self.source_menu = ui.get_object('source_menu') self.source_menu_search_all_item = ui.get_object( 'source_search_menu_item') self.play_favourites_album_menu_item = ui.get_object( 'play_favourites_album_menu_item') self.queue_favourites_album_menu_item = ui.get_object( 'queue_favourites_album_menu_item') self.favourite_playlist_menu_item = ui.get_object( 'favourite_playlist_menu_item') self.playlist_sub_menu_item = ui.get_object('playlist_sub_menu_item') self.favourite_playlist_sub_menu_item = ui.get_object( 'favourite_playlist_sub_menu_item') self.export_embed_menu_item = ui.get_object( 'export_embed_menu_item') # quick search self.quick_search = ui.get_object('quick_search_entry') print "CoverArtBrowser DEBUG - end _create_ui" def _setup_source(self): ''' Setups the differents parts of the source so they are ready to be used by the user. It also creates and configure some custom widgets. ''' print "CoverArtBrowser DEBUG - _setup_source" # setup iconview popup self.covers_view.popup = self.popup_menu self.covers_view.view_name = "covers_view" self.covers_view.shell = self.shell self.covers_view.ext_menu_pos = 10 # setup entry-view objects and widgets setting = self.gs.get_setting(self.gs.Path.PLUGIN) setting.bind(self.gs.PluginKey.PANED_POSITION, self.paned, 'collapsible-y', Gio.SettingsBindFlags.DEFAULT) setting.bind(self.gs.PluginKey.DISPLAY_BOTTOM, self.paned.get_child2(), 'visible', Gio.SettingsBindFlags.DEFAULT) # create entry view. Don't allow to reorder until the load is finished self.entry_view = EV(self.shell, self) self.entry_view.set_columns_clickable(False) self.shell.props.library_source.get_entry_view().set_columns_clickable( False) self.stars = ReactiveStar() self.stars.set_rating(0) a = Gtk.Alignment.new(0.5, 0.5, 0, 0) a.add(self.stars) self.stars.connect('changed', self.rating_changed_callback) vbox = Gtk.Box() vbox.set_orientation(Gtk.Orientation.VERTICAL) vbox.pack_start(self.entry_view, True, True, 0) vbox.pack_start(a, False, False, 1) vbox.show_all() self.notebook.append_page(vbox, Gtk.Label(_("Tracks"))) # setup iconview drag&drop support # first drag and drop on the coverart view to receive coverart self.covers_view.enable_model_drag_dest([], Gdk.DragAction.COPY) self.covers_view.drag_dest_add_image_targets() self.covers_view.drag_dest_add_text_targets() self.covers_view.connect('drag-drop', self.on_drag_drop) self.covers_view.connect('drag-data-received', self.on_drag_data_received) self.covers_view.connect('drag-begin', self.on_drag_begin) # lastly support drag-drop from coverart to devices/nautilus etc self.covers_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY) targets = Gtk.TargetList.new([Gtk.TargetEntry.new("application/x-rhythmbox-entry", 0,0), Gtk.TargetEntry.new("text/uri-list", 0,1) ]) # N.B. values taken from rhythmbox v2.97 widgets/rb_entry_view.c targets.add_uri_targets(2) self.covers_view.drag_source_set_target_list(targets) self.covers_view.connect("drag-data-get", self.on_drag_data_get) # create an album manager self.album_manager = AlbumManager(self.plugin, self.covers_view) # setup cover search pane try: color = self.covers_view.get_style_context().get_background_color( Gtk.StateFlags.SELECTED) color = '#%s%s%s' % ( str(hex(int(color.red * 255))).replace('0x', ''), str(hex(int(color.green * 255))).replace('0x', ''), str(hex(int(color.blue * 255))).replace('0x', '')) except: color = '#0000FF' self.cover_search_pane = CoverSearchPane(self.plugin, self.album_manager, color) self.notebook.append_page(self.cover_search_pane, Gtk.Label( _("Covers"))) # connect a signal to when the info of albums is ready self.load_fin_id = self.album_manager.loader.connect( 'model-load-finished', self.load_finished_callback) # prompt the loader to load the albums self.album_manager.loader.load_albums(self.props.base_query_model) # set the model to the view self.covers_view.set_model(self.album_manager.model.store) # initialise the toolbar manager self._toolbar_manager = ToolbarManager(self.plugin, self.page, self.album_manager.model) # initialise the variables of the quick search self.quick_search_controller = AlbumQuickSearchController( self.album_manager) self.quick_search_controller.connect_quick_search(self.quick_search) # set sensitivity of export menu item for iconview self.export_embed_menu_item.set_sensitive( CoverArtExport(self.plugin, self.shell, self.album_manager).is_search_plugin_enabled()) print "CoverArtBrowser DEBUG - end _setup_source" def _apply_settings(self): ''' Applies all the settings related to the source and connects those that must be updated when the preferences dialog changes it's values. Also enables differents parts of the ui if the settings says so. ''' print "CoverArtBrowser DEBUG - _apply_settings" # connect some signals to the loader to keep the source informed self.album_mod_id = self.album_manager.model.connect('album-updated', self.on_album_updated) self.notify_prog_id = self.album_manager.connect( 'notify::progress', lambda *args: self.notify_status_changed()) # enable some ui if necesary self.on_notify_rating_threshold(_) print "CoverArtBrowser DEBUG - end _apply_settings" def load_finished_callback(self, _): ''' Callback called when the loader finishes loading albums into the covers view model. ''' print "CoverArtBrowser DEBUG - load_finished_callback" if not self.request_status_box.get_visible(): # it should only be enabled if no cover request is going on self.source_menu_search_all_item.set_sensitive(True) # enable sorting on the entryview self.entry_view.set_columns_clickable(True) self.shell.props.library_source.get_entry_view().set_columns_clickable( True) print "CoverArtBrowser DEBUG - end load_finished_callback" def get_entry_view(self): return self.entry_view def item_clicked_callback(self, iconview, event, path): ''' Callback called when the user clicks somewhere on the cover_view. Along with _timeout_expand, takes care of showing/hiding the bottom pane after a second click on a selected album. ''' # to expand the entry view ctrl = event.state & Gdk.ModifierType.CONTROL_MASK shift = event.state & Gdk.ModifierType.SHIFT_MASK self.click_count += 1 if not ctrl and not shift else 0 if self.click_count == 1: album = self.album_manager.model.get_from_path(path)\ if path else None Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250, self._timeout_expand, album) def _timeout_expand(self, album): ''' helper function - if the entry is manually expanded then if necessary scroll the view to the last selected album ''' if album and self.click_count == 1 \ and self.last_selected_album is album: # check if it's a second or third click on the album and expand # or collapse the entry view accordingly self.paned.expand() # update the selected album selected = self.covers_view.get_selected_objects() self.last_selected_album = selected[0] if len(selected) == 1 else None # clear the click count self.click_count = 0 def on_notify_custom_statusbar_enabled(self, *args): ''' Callback for when the option to show the custom statusbar is enabled or disabled from the plugin's preferences dialog. ''' print "CoverArtBrowser DEBUG - on_notify_custom_statusbar_enabled" if self.custom_statusbar_enabled: self.status = '' self.notify_status_changed() else: self.status_label.hide() self.selectionchanged_callback(self.covers_view) print "CoverArtBrowser DEBUG - end on_notify_custom_statusbar_enabled" def on_notify_rating_threshold(self, *args): ''' Callback called when the option rating threshold is changed on the plugin's preferences dialog If the threshold is zero then the rating menu options in the coverview should not be enabled ''' print "CoverArtBrowser DEBUG - on_notify_rating_threshold" if self.rating_threshold > 0: enable_menus = True else: enable_menus = False self.play_favourites_album_menu_item.set_sensitive(enable_menus) self.queue_favourites_album_menu_item.set_sensitive(enable_menus) self.favourite_playlist_menu_item.set_sensitive(enable_menus) print "CoverArtBrowser DEBUG - end on_notify_rating_threshold" def on_album_updated(self, model, path, tree_iter): ''' Callback called by the album loader when one of the albums managed by him gets modified in some way. ''' album = model.get_from_path(path) selected = self.covers_view.get_selected_objects() if album in selected: # update the selection since it may have changed self.selectionchanged_callback(self.covers_view) if album is selected[0] and \ self.notebook.get_current_page() == \ self.notebook.page_num(self.cover_search_pane): # also, if it's the first, update the cover search pane self.cover_search_pane.clear() self.cover_search_pane.do_search(album) def show_properties_menu_item_callback(self, menu_item): ''' Callback called when the show album properties option is selected from the cover view popup. It shows a SongInfo dialog showing the selected albums' entries info, which can be modified. ''' print "CoverArtBrowser DEBUG - show_properties_menu_item_callback" self.entry_view.select_all() info_dialog = RB.SongInfo(source=self, entry_view=self.entry_view) info_dialog.show_all() print "CoverArtBrowser DEBUG - end show_properties_menu_item_callback" def item_activated_callback(self, iconview, path): ''' Callback called when the cover view is double clicked or space-bar is pressed. It plays the selected album ''' self.play_selected_album() return True def play_selected_album(self, favourites=False): ''' Utilitary method that plays all entries from an album into the play queue. ''' # callback when play an album print "CoverArtBrowser DEBUG - play_selected_album" query_model = RB.RhythmDBQueryModel.new_empty(self.shell.props.db) self.queue_selected_album(query_model) self.props.query_model = query_model # Start the music player = self.shell.props.shell_player player.play_entry(query_model[0][0], self) print "CoverArtBrowser DEBUG - end play_selected_album" def queue_selected_album(self, source, favourites=False): ''' Utilitary method that queues all entries from an album into the play queue. ''' print "CoverArtBrowser DEBUG - queue_selected_album" selected_albums = self.covers_view.get_selected_objects() threshold = self.rating_threshold if favourites else 0 for album in selected_albums: # Retrieve and sort the entries of the album tracks = album.get_tracks(threshold) # Add the songs to the play queue for track in tracks: source.add_entry(track.entry, -1) print "CoverArtBrowser DEBUG - end queue_select_album" def play_album_menu_item_callback(self, _): ''' Callback called when the play album item from the cover view popup is selected. It cleans the play queue and queues the selected album. ''' print "CoverArtBrowser DEBUG - play_album_menu_item_callback" self.play_selected_album() print "CoverArtBrowser DEBUG - end play_album_menu_item_callback" def queue_album_menu_item_callback(self, _): ''' Callback called when the queue album item from the cover view popup is selected. It queues the selected album at the end of the play queue. ''' print "CoverArtBrowser DEBUG - queue_album_menu_item_callback()" self.queue_selected_album(self.shell.props.queue_source) print "CoverArtBrowser DEBUG - end queue_album_menu_item_callback()" def queue_favourites_album_menu_item_callback(self, _): ''' Callback called when the queue-favourites album item from the cover view popup is selected. It queues the selected album at the end of the play queue. ''' print '''CoverArtBrowser DEBUG - queue_favourites_album_menu_item_callback()''' self.queue_selected_album(self.shell.props.queue_source, True) print '''CoverArtBrowser DEBUG - end queue_favourites_album_menu_item_callback()''' def play_favourites_album_menu_item_callback(self, _): ''' Callback called when the play favourites album item from the cover view popup is selected. It queues the selected album at the end of the play queue. ''' print '''CoverArtBrowser DEBUG - play_favourites_album_menu_item_callback()''' self.play_selected_album(True) print '''CoverArtBrowser DEBUG - end play_favourites_album_menu_item_callback()''' def playlist_menu_item_callback(self, menu_item): print "CoverArtBrowser DEBUG - playlist_menu_item_callback" self.playlist_fillmenu(self.playlist_sub_menu_item, self.actiongroup, self.add_to_static_playlist_menu_item_callback) def favourite_playlist_menu_item_callback(self, menu_item): print "CoverArtBrowser DEBUG - favourite_playlist_menu_item_callback" self.playlist_fillmenu(self.favourite_playlist_sub_menu_item, self.favourite_actiongroup, self.add_to_static_playlist_menu_item_callback, True) def playlist_fillmenu(self, menubar, actiongroup, func, favourite=False): print "CoverArtBrowser DEBUG - playlist_fillmenu" uim = self.shell.props.ui_manager playlist_manager = self.shell.props.playlist_manager playlists_entries = playlist_manager.get_playlists() # tidy up old playlists menu items before recreating the list for action in actiongroup.list_actions(): actiongroup.remove_action(action) count = 0 for menu_item in menubar: if count > 1: # ignore the first two menu items menubar.remove(menu_item) count += 1 menubar.show_all() uim.ensure_update() if playlists_entries: for playlist in playlists_entries: if playlist.props.is_local and \ isinstance(playlist, RB.StaticPlaylistSource): new_menu_item = Gtk.MenuItem(label=playlist.props.name) action = Gtk.Action(label=playlist.props.name, name=playlist.props.name, tooltip='', stock_id=Gtk.STOCK_CLEAR) action.connect('activate', func, playlist, favourite) new_menu_item.set_related_action(action) menubar.append(new_menu_item) actiongroup.add_action(action) menubar.show_all() uim.ensure_update() def add_to_static_playlist_menu_item_callback(self, action, playlist, favourite): print '''CoverArtBrowser DEBUG - add_to_static_playlist_menu_item_callback''' self.queue_selected_album(playlist, favourite) def add_playlist_menu_item_callback(self, menu_item): print '''CoverArtBrowser DEBUG - add_playlist_menu_item_callback''' playlist_manager = self.shell.props.playlist_manager playlist = playlist_manager.new_playlist('', False) self.queue_selected_album(playlist, False) def favourite_add_playlist_menu_item_callback(self, menu_item): print '''CoverArtBrowser DEBUG - favourite_add_playlist_menu_item_callback''' playlist_manager = self.shell.props.playlist_manager playlist = playlist_manager.new_playlist('', False) self.queue_selected_album(playlist, True) def cover_search_menu_item_callback(self, menu_item): ''' Callback called when the search cover option is selected from the cover view popup. It prompts the album loader to retrieve the selected album cover ''' print "CoverArtBrowser DEBUG - cover_search_menu_item_callback()" selected_albums = self.covers_view.get_selected_objects() self.request_status_box.show_all() self.album_manager.cover_man.search_covers(selected_albums, self.update_request_status_bar) print "CoverArtBrowser DEBUG - end cover_search_menu_item_callback()" def export_embed_menu_item_callback(self, menu_item): ''' Callback called when the export and embed coverart option is selected from the cover view popup. It prompts the exporter to copy and embed art for the albums chosen ''' print "CoverArtBrowser DEBUG - export_embed_menu_item_callback()" selected_albums = self.covers_view.get_selected_objects() CoverArtExport(self.plugin, self.shell, self.album_manager).embed_albums(selected_albums) print "CoverArtBrowser DEBUG - export_embed_menu_item_callback()" def search_all_covers_callback(self, _): ''' Callback called when the search all covers option is selected from the source's popup. It prompts the album loader to request ALL album's covers ''' print "CoverArtBrowser DEBUG - search_all_covers_callback()" self.request_status_box.show_all() self.album_manager.cover_man.search_covers( callback=self.update_request_status_bar) print "CoverArtBrowser DEBUG - end search_all_covers_callback()" def update_request_status_bar(self, album): ''' Callback called by the album loader starts performing a new cover request. It prompts the source to change the content of the request statusbar. ''' print "CoverArtBrowser DEBUG - update_request_status_bar" if album: self.request_statusbar.set_text( (_('Requesting cover for %s - %s...') % (album.name, album.artist)).decode('UTF-8')) else: self.request_status_box.hide() self.source_menu_search_all_item.set_sensitive(True) self.cover_search_menu_item.set_sensitive(True) self.request_cancel_button.set_sensitive(True) print "CoverArtBrowser DEBUG - end update_request_status_bar" def cancel_request_callback(self, _): ''' Callback connected to the cancel button on the request statusbar. When called, it prompts the album loader to cancel the full cover search after the current cover. ''' print "CoverArtBrowser DEBUG - cancel_request_callback" self.request_cancel_button.set_sensitive(False) self.album_manager.cover_man.cancel_cover_request() print "CoverArtBrowser DEBUG - end cancel_request_callback" def selectionchanged_callback(self, widget): ''' Callback called when an item from the cover view gets selected. It changes the content of the statusbar (which statusbar is dependant on the custom_statusbar_enabled property) to show info about the current selected album. ''' print "CoverArtBrowser DEBUG - selectionchanged_callback" selected = self.covers_view.get_selected_objects() # clear the entry view self.entry_view.clear() cover_search_pane_visible = self.notebook.get_current_page() == \ self.notebook.page_num(self.cover_search_pane) if not selected: # if no album selected, clean the status and the cover tab if # if selected self.update_statusbar() if cover_search_pane_visible: self.cover_search_pane.clear() return elif len(selected) == 1: self.stars.set_rating(selected[0].rating) if selected[0] is not self.last_selected_album: # when the selection changes we've to take into account two # things if not self.click_count: # we may be using the arrows, so if there is no mouse # involved, we should change the last selected self.last_selected_album = selected[0] else: # we may've doing a fast change after a valid second click, # so it shouldn't be considered a double click self.click_count -= 1 else: self.stars.set_rating(0) track_count = 0 duration = 0 for album in selected: # Calculate duration and number of tracks from that album track_count += album.track_count duration += album.duration / 60 # add the album to the entry_view self.entry_view.add_album(album) # now lets build up a status label containing some 'interesting stuff' # about the album if len(selected) == 1: status = (_('%s by %s') % (album.name, album.artist)).decode( 'UTF-8') else: status = (_('%d selected albums ') % (len(selected))).decode( 'UTF-8') if track_count == 1: status += (_(' with 1 track')).decode('UTF-8') else: status += (_(' with %d tracks') % track_count).decode('UTF-8') if duration == 1: status += (_(' and a duration of 1 minute')).decode('UTF-8') else: status += (_(' and a duration of %d minutes') % duration).decode( 'UTF-8') self.update_statusbar(status) # update the cover search pane with the first selected album if cover_search_pane_visible: self.cover_search_pane.do_search(selected[0]) print "CoverArtBrowser DEBUG - end selectionchanged_callback" def update_statusbar(self, status=''): ''' Utility method that updates the status bar. ''' print "CoverArtBrowser DEBUG - update_statusbar" if self.custom_statusbar_enabled: # if the custom statusbar is enabled... use it. self.status_label.set_text(status) self.status_label.show() else: # use the global statusbar from Rhythmbox self.status = status self.notify_status_changed() print "CoverArtBrowser DEBUG - end update_statusbar" def bottom_expander_expanded_callback(self, paned, expand): ''' Callback connected to expanded signal of the paned GtkExpander ''' if expand: # acomodate the viewport if there's an album selected if self.last_selected_album: def scroll_to_album(*args): # acomodate the viewport if there's an album selected path = self.album_manager.model.get_path( self.last_selected_album) self.covers_view.scroll_to_path(path, False, 0, 0) return False Gdk.threads_add_idle(GObject.PRIORITY_DEFAULT_IDLE, scroll_to_album, None) def on_drag_drop(self, widget, context, x, y, time): ''' Callback called when a drag operation finishes over the cover view of the source. It decides if the dropped item can be processed as an image to use as a cover. ''' print "CoverArtBrowser DEBUG - on_drag_drop" # stop the propagation of the signal (deactivates superclass callback) widget.stop_emission('drag-drop') # obtain the path of the icon over which the drag operation finished path, pos = widget.get_dest_item_at_pos(x, y) result = path is not None if result: target = self.covers_view.drag_dest_find_target(context, None) widget.drag_get_data(context, target, time) print "CoverArtBrowser DEBUG - end on_drag_drop" return result def on_drag_data_received(self, widget, drag_context, x, y, data, info, time): ''' Callback called when the drag source has prepared the data (pixbuf) for us to use. ''' print "CoverArtBrowser DEBUG - on_drag_data_received" # stop the propagation of the signal (deactivates superclass callback) widget.stop_emission('drag-data-received') # get the album and the info and ask the loader to update the cover path, pos = widget.get_dest_item_at_pos(x, y) album = widget.get_model()[path][2] pixbuf = data.get_pixbuf() if pixbuf: self.album_manager.cover_man.update_cover(album, pixbuf) else: uri = data.get_text() self.album_manager.cover_man.update_cover(album, uri=uri) # call the context drag_finished to inform the source about it drag_context.finish(True, False, time) print "CoverArtBrowser DEBUG - end on_drag_data_received" def on_drag_data_get(self, widget, drag_context, data, info, time): ''' Callback called when the drag destination (playlist) has requested what album (icon) has been dragged ''' print "CoverArtBrowser DEBUG - on_drag_data_get" uris = [] for album in widget.get_selected_objects(): for track in album.get_tracks(): uris.append(track.location) data.set_uris(uris) # stop the propagation of the signal (deactivates superclass callback) widget.stop_emission('drag-data-get') print "CoverArtBrowser DEBUG - end on_drag_data_get" def on_drag_begin(self, widget, context): ''' Callback called when the drag-drop from coverview has started Changes the drag icon as appropriate ''' album_number = len(widget.get_selected_objects()) if album_number == 1: item = Gtk.STOCK_DND else: item = Gtk.STOCK_DND_MULTIPLE widget.drag_source_set_icon_stock(item) widget.stop_emission('drag-begin') def notebook_switch_page_callback(self, notebook, page, page_num): ''' Callback called when the notebook page gets switched. It initiates the cover search when the cover search pane's page is selected. ''' print "CoverArtBrowser DEBUG - notebook_switch_page_callback" if page_num == 1: selected_albums = self.covers_view.get_selected_objects() if selected_albums: self.cover_search_pane.do_search(selected_albums[0]) print "CoverArtBrowser DEBUG - end notebook_switch_page_callback" def rating_changed_callback(self, widget): ''' Callback called when the Rating stars is changed ''' print "CoverArtBrowser DEBUG - rating_changed_callback" rating = widget.get_rating() for album in self.covers_view.get_selected_objects(): album.rating = rating print "CoverArtBrowser DEBUG - end rating_changed_callback" def on_notify_icon_padding(self, *args): ''' Callback called when the icon-padding gsetting value is changed ''' print self.covers_view.set_item_padding(self.icon_padding) pass def on_notify_icon_spacing(self, *args): ''' Callback called when the icon-spacing gsetting value is changed ''' print self.covers_view.set_row_spacing(self.icon_spacing) print self.covers_view.set_column_spacing(self.icon_spacing) pass @classmethod def get_instance(cls, **kwargs): ''' Returns the unique instance of the manager. ''' if not cls.instance: cls.instance = CoverArtBrowserSource(**kwargs) return cls.instance
def _setup_source(self): ''' Setups the differents parts of the source so they are ready to be used by the user. It also creates and configure some custom widgets. ''' print "CoverArtBrowser DEBUG - _setup_source" # setup iconview popup self.covers_view.popup = self.popup_menu self.covers_view.view_name = "covers_view" self.covers_view.shell = self.shell self.covers_view.ext_menu_pos = 10 # setup entry-view objects and widgets setting = self.gs.get_setting(self.gs.Path.PLUGIN) setting.bind(self.gs.PluginKey.PANED_POSITION, self.paned, 'collapsible-y', Gio.SettingsBindFlags.DEFAULT) setting.bind(self.gs.PluginKey.DISPLAY_BOTTOM, self.paned.get_child2(), 'visible', Gio.SettingsBindFlags.DEFAULT) # create entry view. Don't allow to reorder until the load is finished self.entry_view = EV(self.shell, self) self.entry_view.set_columns_clickable(False) self.shell.props.library_source.get_entry_view().set_columns_clickable( False) self.stars = ReactiveStar() self.stars.set_rating(0) a = Gtk.Alignment.new(0.5, 0.5, 0, 0) a.add(self.stars) self.stars.connect('changed', self.rating_changed_callback) vbox = Gtk.Box() vbox.set_orientation(Gtk.Orientation.VERTICAL) vbox.pack_start(self.entry_view, True, True, 0) vbox.pack_start(a, False, False, 1) vbox.show_all() self.notebook.append_page(vbox, Gtk.Label(_("Tracks"))) # setup iconview drag&drop support # first drag and drop on the coverart view to receive coverart self.covers_view.enable_model_drag_dest([], Gdk.DragAction.COPY) self.covers_view.drag_dest_add_image_targets() self.covers_view.drag_dest_add_text_targets() self.covers_view.connect('drag-drop', self.on_drag_drop) self.covers_view.connect('drag-data-received', self.on_drag_data_received) self.covers_view.connect('drag-begin', self.on_drag_begin) # lastly support drag-drop from coverart to devices/nautilus etc self.covers_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY) targets = Gtk.TargetList.new([Gtk.TargetEntry.new("application/x-rhythmbox-entry", 0,0), Gtk.TargetEntry.new("text/uri-list", 0,1) ]) # N.B. values taken from rhythmbox v2.97 widgets/rb_entry_view.c targets.add_uri_targets(2) self.covers_view.drag_source_set_target_list(targets) self.covers_view.connect("drag-data-get", self.on_drag_data_get) # create an album manager self.album_manager = AlbumManager(self.plugin, self.covers_view) # setup cover search pane try: color = self.covers_view.get_style_context().get_background_color( Gtk.StateFlags.SELECTED) color = '#%s%s%s' % ( str(hex(int(color.red * 255))).replace('0x', ''), str(hex(int(color.green * 255))).replace('0x', ''), str(hex(int(color.blue * 255))).replace('0x', '')) except: color = '#0000FF' self.cover_search_pane = CoverSearchPane(self.plugin, self.album_manager, color) self.notebook.append_page(self.cover_search_pane, Gtk.Label( _("Covers"))) # connect a signal to when the info of albums is ready self.load_fin_id = self.album_manager.loader.connect( 'model-load-finished', self.load_finished_callback) # prompt the loader to load the albums self.album_manager.loader.load_albums(self.props.base_query_model) # set the model to the view self.covers_view.set_model(self.album_manager.model.store) # initialise the toolbar manager self._toolbar_manager = ToolbarManager(self.plugin, self.page, self.album_manager.model) # initialise the variables of the quick search self.quick_search_controller = AlbumQuickSearchController( self.album_manager) self.quick_search_controller.connect_quick_search(self.quick_search) # set sensitivity of export menu item for iconview self.export_embed_menu_item.set_sensitive( CoverArtExport(self.plugin, self.shell, self.album_manager).is_search_plugin_enabled()) print "CoverArtBrowser DEBUG - end _setup_source"
class CoverArtBrowserSource(RB.Source): ''' Source utilized by the plugin to show all it's ui. ''' custom_statusbar_enabled = GObject.property(type=bool, default=False) display_bottom_enabled = GObject.property(type=bool, default=False) rating_threshold = GObject.property(type=float, default=0) toolbar_pos = GObject.property(type=str, default='main') sort_order = GObject.property(type=bool, default=False) # unique instance of the source instance = None def __init__(self, **kargs): ''' Initializes the source. ''' super(CoverArtBrowserSource, self).__init__( **kargs) # create source_source_settings and connect the source's properties self.gs = GSetting() self._connect_properties() self.filter_type = 'all' self.search_text = '' self.hasActivated = False self.last_toolbar_pos = None self.last_width = 0 self.quick_search_idle = 0 self.last_selected_album = None self.click_count = 0 def _connect_properties(self): ''' Connects the source properties to the saved preferences. ''' print "CoverArtBrowser DEBUG - _connect_properties" setting = self.gs.get_setting(self.gs.Path.PLUGIN) setting.bind(self.gs.PluginKey.CUSTOM_STATUSBAR, self, 'custom_statusbar_enabled', Gio.SettingsBindFlags.GET) setting.bind(self.gs.PluginKey.DISPLAY_BOTTOM, self, 'display_bottom_enabled', Gio.SettingsBindFlags.GET) setting.bind(self.gs.PluginKey.RATING, self, 'rating_threshold', Gio.SettingsBindFlags.GET) setting.bind(self.gs.PluginKey.TOOLBAR_POS, self, 'toolbar_pos', Gio.SettingsBindFlags.GET) setting.bind(self.gs.PluginKey.SORT_ORDER, self, 'sort_order', Gio.SettingsBindFlags.DEFAULT) print "CoverArtBrowser DEBUG - end _connect_properties" def do_get_status(self, *args): ''' Method called by Rhythmbox to figure out what to show on this source statusbar. If the custom statusbar is disabled, the source will show the selected album info. Also, it makes sure to show the progress on the album loading.s ''' try: progress = self.album_manager.progress progress_text = _('Loading...') if progress < 1 else '' except: progress = 1 progress_text = '' return (self.status, progress_text, progress) def do_show_popup(self): ''' Method called by Rhythmbox when an action on our source prompts it to show a popup. ''' print "CoverArtBrowser DEBUG - do_show_popup" self.source_menu.popup(None, None, None, None, 0, Gtk.get_current_event_time()) print "CoverArtBrowser DEBUG - end do_show_popup" return True def do_selected(self): ''' Called by Rhythmbox when the source is selected. It makes sure to create the ui the first time the source is showed. ''' print "CoverArtBrowser DEBUG - do_selected" # first time of activation -> add graphical stuff if not self.hasActivated: self.do_impl_activate() #indicate that the source was activated before self.hasActivated = True print "CoverArtBrowser DEBUG - end do_selected" def do_impl_activate(self): ''' Called by do_selected the first time the source is activated. It creates all the source ui and connects the necesary signals for it correct behavior. ''' print "CoverArtBrowser DEBUG - do_impl_activate" # initialise some variables self.plugin = self.props.plugin self.shell = self.props.shell self.status = '' self.search_text = '' self.actiongroup = Gtk.ActionGroup('coverplaylist_submenu') self.favourite_actiongroup = Gtk.ActionGroup( 'favourite_coverplaylist_submenu') uim = self.shell.props.ui_manager uim.insert_action_group(self.actiongroup) uim.insert_action_group(self.favourite_actiongroup) self.coversearchtimer = ttimer(30, -1, self.update_request_status_bar, None) # connect properties signals self.connect('notify::custom-statusbar-enabled', self.on_notify_custom_statusbar_enabled) self.connect('notify::display-bottom-enabled', self.on_notify_display_bottom_enabled) self.connect('notify::rating-threshold', self.on_notify_rating_threshold) #indicate that the source was activated before self.hasActivated = True self._create_ui() self._setup_source() self._apply_settings() print "CoverArtBrowser DEBUG - end do_impl_activate" def _create_ui(self): ''' Creates the ui for the source and saves the important widgets onto properties. ''' print "CoverArtBrowser DEBUG - _create_ui" # dialog has not been created so lets do so. cl = CoverLocale() ui = Gtk.Builder() ui.set_translation_domain(cl.Locale.LOCALE_DOMAIN) ui.add_from_file(rb.find_plugin_file(self.plugin, 'ui/coverart_browser.ui')) self.toolbar_box = ui.get_object('toolbar_box') si = Gtk.Builder() si.set_translation_domain(cl.Locale.LOCALE_DOMAIN) si.add_from_file(rb.find_plugin_file(self.plugin, 'ui/coverart_sidebar.ui')) # load the page and put it in the source self.sidebar = si.get_object('main_box') # load the page and put it in the source self.page = ui.get_object('main_box') self.pack_start(self.page, True, True, 0) # get widgets for main icon-view self.status_label = ui.get_object('status_label') self.covers_view = ui.get_object('covers_view') self.popup_menu = ui.get_object('popup_menu') self.cover_search_menu_item = ui.get_object('cover_search_menu_item') self.status_label = ui.get_object('status_label') self.request_status_box = ui.get_object('request_status_box') self.request_spinner = ui.get_object('request_spinner') self.request_statusbar = ui.get_object('request_statusbar') self.request_cancel_button = ui.get_object('request_cancel_button') self.paned = ui.get_object('paned') self.bottom_box = ui.get_object('bottom_box') self.bottom_expander = ui.get_object('bottom_expander') self.notebook = ui.get_object('bottom_notebook') # get widgets for source popup self.source_menu = ui.get_object('source_menu') self.source_menu_search_all_item = ui.get_object( 'source_search_menu_item') self.play_favourites_album_menu_item = ui.get_object( 'play_favourites_album_menu_item') self.queue_favourites_album_menu_item = ui.get_object( 'queue_favourites_album_menu_item') self.favourite_playlist_menu_item = ui.get_object( 'favourite_playlist_menu_item') self.playlist_sub_menu_item = ui.get_object('playlist_sub_menu_item') self.favourite_playlist_sub_menu_item = ui.get_object( 'favourite_playlist_sub_menu_item') # get widgets for filter popup self.filter_menu = ui.get_object('filter_menu') self.filter_menu_all_item = ui.get_object('filter_all_menu_item') self.filter_menu_artist_item = ui.get_object('filter_artist_menu_item') self.filter_menu_album_artist_item = ui.get_object( 'filter_album_artist_menu_item') self.filter_menu_album_item = ui.get_object('filter_album_menu_item') self.filter_menu_track_title_item = ui.get_object( 'filter_track_title_menu_item') # quick search entry self.quick_search = ui.get_object('quick_search_entry') self.quick_search_box = ui.get_object('quick_search_box') self.ui = ui self.si = si print "CoverArtBrowser DEBUG - end _create_ui" def _toolbar(self, ui): ''' setup toolbar ui - called for sidebar and main-view ''' print "CoverArtBrowser DEBUG - _toolbar" # dialog has not been created so lets do so. cl = CoverLocale() # get widgets for main icon-view # the first part is to first remove the current search-entry # before recreating it again - we have to do this to ensure # the locale is set correctly i.e. the overall ui is coverart # locale but the search-entry uses rhythmbox translation align = ui.get_object('entry_search_alignment') align.remove(align.get_child()) cl.switch_locale(cl.Locale.RB) self.search_entry = RB.SearchEntry(has_popup=True) align.add(self.search_entry) align.show_all() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) self.search_entry.connect('search', self.searchchanged_callback) self.search_entry.connect('show-popup', self.search_show_popup_callback) self.sort_by = ui.get_object('sort_by') self.sort_by.initialise(self.plugin, self.shell, self.sorting_criteria_changed) self.sort_order_button = ui.get_object('sort_order') self.sort_order_button.initialise(self.plugin, self.sorting_direction_changed, self.sort_order) # get widget for search and apply some workarounds search_entry = ui.get_object('search_entry') search_entry.set_placeholder(_('Search album')) search_entry.show_all() self.search_entry.set_placeholder(ui.get_object( 'filter_all_menu_item').get_label()) # genre genre_button = ui.get_object('genre_button') genre_button.initialise(self.plugin, self.shell, self.genre_filter_callback) # get playlist popup playlist_button = ui.get_object('playlist_button') playlist_button.initialise(self.plugin, self.shell, self.filter_by_model) # decade decade_button = ui.get_object('decade_button') decade_button.initialise(self.plugin, self.shell, self.decade_filter_callback) print "CoverArtBrowser DEBUG - end _toolbar" def _setup_source(self): ''' Setups the differents parts of the source so they are ready to be used by the user. It also creates and configure some custom widgets. ''' print "CoverArtBrowser DEBUG - _setup_source" # setup iconview drag&drop support self.covers_view.enable_model_drag_dest([], Gdk.DragAction.COPY) self.covers_view.drag_dest_add_image_targets() self.covers_view.drag_dest_add_text_targets() self.covers_view.connect('drag-drop', self.on_drag_drop) self.covers_view.connect('drag-data-received', self.on_drag_data_received) # setup entry-view objects and widgets y = self.gs.get_value(self.gs.Path.PLUGIN, self.gs.PluginKey.PANED_POSITION) self.paned.set_position(y) # create entry view. Don't allow to reorder until the load is finished self.entry_view = EV(self.shell, self) self.entry_view.set_columns_clickable(False) self.shell.props.library_source.get_entry_view().set_columns_clickable( False) self.stars = ReactiveStar() self.stars.set_rating(0) a = Gtk.Alignment.new(0.5, 0.5, 0, 0) a.add(self.stars) self.stars.connect('changed', self.rating_changed_callback) vbox = Gtk.Box() vbox.set_orientation(Gtk.Orientation.VERTICAL) vbox.pack_start(self.entry_view, True, True, 0) vbox.pack_start(a, False, False, 1) vbox.show_all() self.notebook.append_page(vbox, Gtk.Label(_("Tracks"))) # create an album manager self.album_manager = AlbumManager(self.plugin, self.covers_view) # setup cover search pane try: color = self.covers_view.get_style_context().get_background_color( Gtk.StateFlags.SELECTED) color = '#%s%s%s' % ( str(hex(int(color.red * 255))).replace('0x', ''), str(hex(int(color.green * 255))).replace('0x', ''), str(hex(int(color.blue * 255))).replace('0x', '')) except: color = '#0000FF' self.cover_search_pane = CoverSearchPane(self.plugin, self.album_manager, color) self.notebook.append_page(self.cover_search_pane, Gtk.Label( _("Covers"))) # connect a signal to when the info of albums is ready self.load_fin_id = self.album_manager.loader.connect( 'model-load-finished', self.load_finished_callback) # prompt the loader to load the albums self.album_manager.loader.load_albums(self.props.query_model) # set the model to the view self.covers_view.set_model(self.album_manager.model.store) print "CoverArtBrowser DEBUG - end _setup_source" def _apply_settings(self): ''' Applies all the settings related to the source and connects those that must be updated when the preferences dialog changes it's values. Also enables differents parts of the ui if the settings says so. ''' print "CoverArtBrowser DEBUG - _apply_settings" # connect some signals to the loader to keep the source informed self.si.connect_signals(self) self.ui.connect_signals(self) self.album_mod_id = self.album_manager.model.connect('album-updated', self.on_album_updated) self.notify_prog_id = self.album_manager.connect( 'notify::progress', lambda *args: self.notify_status_changed()) self.toolbar_pos_id = self.connect('notify::toolbar-pos', self.on_notify_toolbar_pos) # enable some ui if necesary self.on_notify_rating_threshold(_) self.on_notify_display_bottom_enabled(_) self.on_notify_toolbar_pos() #self.sorting_direction_changed(_, True) print "CoverArtBrowser DEBUG - end _apply_settings" def load_finished_callback(self, _): ''' Callback called when the loader finishes loading albums into the covers view model. ''' print "CoverArtBrowser DEBUG - load_finished_callback" if not self.request_status_box.get_visible(): # it should only be enabled if no cover request is going on self.source_menu_search_all_item.set_sensitive(True) # enable sorting on the entryview self.entry_view.set_columns_clickable(True) self.shell.props.library_source.get_entry_view().set_columns_clickable( True) print "CoverArtBrowser DEBUG - end load_finished_callback" def on_notify_custom_statusbar_enabled(self, *args): ''' Callback for when the option to show the custom statusbar is enabled or disabled from the plugin's preferences dialog. ''' print "CoverArtBrowser DEBUG - on_notify_custom_statusbar_enabled" if self.custom_statusbar_enabled: self.status = '' self.notify_status_changed() else: self.status_label.hide() self.selectionchanged_callback(self.covers_view) print "CoverArtBrowser DEBUG - end on_notify_custom_statusbar_enabled" def on_notify_rating_threshold(self, *args): ''' Callback called when the option rating threshold is changed on the plugin's preferences dialog If the threshold is zero then the rating menu options in the coverview should not be enabled ''' print "CoverArtBrowser DEBUG - on_notify_rating_threshold" if self.rating_threshold > 0: enable_menus = True else: enable_menus = False self.play_favourites_album_menu_item.set_sensitive(enable_menus) self.queue_favourites_album_menu_item.set_sensitive(enable_menus) self.favourite_playlist_menu_item.set_sensitive(enable_menus) print "CoverArtBrowser DEBUG - end on_notify_rating_threshold" def on_notify_toolbar_pos(self, *args): ''' Callback called when the toolbar position is changed in preferences ''' print "CoverArtBrowser DEBUG - on_notify_toolbar_pos" if self.last_toolbar_pos == 'left': self.shell.remove_widget(self.sidebar, RB.ShellUILocation.SIDEBAR) if self.last_toolbar_pos == 'right': self.shell.remove_widget(self.sidebar, RB.ShellUILocation.RIGHT_SIDEBAR) if self.toolbar_pos == 'main': self._toolbar(self.ui) self.toolbar_box.set_visible(True) if self.toolbar_pos == 'left': self.toolbar_box.set_visible(False) self._toolbar(self.si) self.shell.add_widget(self.sidebar, RB.ShellUILocation.SIDEBAR, expand=False, fill=False) if self.toolbar_pos == 'right': self.toolbar_box.set_visible(False) self._toolbar(self.si) self.shell.add_widget(self.sidebar, RB.ShellUILocation.RIGHT_SIDEBAR, expand=False, fill=False) self.last_toolbar_pos = self.toolbar_pos print "CoverArtBrowser DEBUG - end on_notify_toolbar_pos" def on_notify_display_bottom_enabled(self, *args): ''' Callback called when the option 'display tracks' is enabled or disabled on the plugin's preferences dialog ''' print "CoverArtBrowser DEBUG - on_notify_display_bottom_enabled" if self.display_bottom_enabled: # make the entry view visible self.bottom_box.set_visible(True) self.bottom_expander_expanded_callback( self.bottom_expander, None) # update it with the current selected album self.selectionchanged_callback(self.covers_view) else: if self.bottom_expander.get_expanded(): y = self.paned.get_position() self.gs.set_value(self.gs.Path.PLUGIN, self.gs.PluginKey.PANED_POSITION, y) self.bottom_box.set_visible(False) print "CoverArtBrowser DEBUG - end on_notify_display_bottom_enabled" def paned_button_press_callback(self, *args): ''' This callback allows or denies the paned handle to move depending on the expanded state of the entry_view ''' print "CoverArtBrowser DEBUG - paned_button_press_callback" return not self.bottom_expander.get_expanded() def on_paned_button_release_event(self, *args): ''' Callback when the paned handle is released from its mouse click. ''' print "CoverArtBrowser DEBUG - on_paned_button_release_event" if self.bottom_expander.get_expanded(): # save the new position new_y = self.paned.get_position() self.gs.set_value(self.gs.Path.PLUGIN, self.gs.PluginKey.PANED_POSITION, new_y) print "CoverArtBrowser DEBUG - end on_paned_button_release_event" def on_album_updated(self, model, path, tree_iter): ''' Callback called by the album loader when one of the albums managed by him gets modified in some way. ''' album = model.get_from_path(path) selected = self.get_selected_albums() if album in selected: # update the selection since it may have changed self.selectionchanged_callback(self.covers_view) if album is selected[0] and \ self.notebook.get_current_page() == \ self.notebook.page_num(self.cover_search_pane): # also, if it's the first, update the cover search pane self.cover_search_pane.clear() self.cover_search_pane.do_search(album) def on_overlay_key_press(self, overlay, event, *args): if not self.quick_search_box.get_visible() and \ event.keyval not in [Gdk.KEY_Shift_L, Gdk.KEY_Shift_R, Gdk.KEY_Control_L, Gdk.KEY_Control_R, Gdk.KEY_Escape]: # grab focus, redirect the pressed key and make the quick search # entry visible self.quick_search.grab_focus() self.quick_search.im_context_filter_keypress(event) self.quick_search_box.show_all() elif event.keyval == Gdk.KEY_Escape: self.hide_quick_search() return False def on_quick_search_focus_lost(self, quick_search, event, *args): self.hide_quick_search() return False def show_properties_menu_item_callback(self, menu_item): ''' Callback called when the show album properties option is selected from the cover view popup. It shows a SongInfo dialog showing the selected albums' entries info, which can be modified. ''' print "CoverArtBrowser DEBUG - show_properties_menu_item_callback" self.entry_view.select_all() info_dialog = RB.SongInfo(source=self, entry_view=self.entry_view) info_dialog.show_all() print "CoverArtBrowser DEBUG - end show_properties_menu_item_callback" def search_show_popup_callback(self, entry): ''' Callback called by the search entry when the magnifier is clicked. It prompts the user through a popup to select a filter type. ''' print "CoverArtBrowser DEBUG - search_show_popup_callback" self.filter_menu.popup(None, None, None, None, 0, Gtk.get_current_event_time()) print "CoverArtBrowser DEBUG - end search_show_popup_callback" def searchchanged_callback(self, entry, text): ''' Callback called by the search entry when a new search must be performed. ''' print "CoverArtBrowser DEBUG - searchchanged_callback" self.search_text = text self.album_manager.model.replace_filter(self.filter_type, text) print "CoverArtBrowser DEBUG - end searchchanged_callback" def filter_menu_callback(self, radiomenu): ''' Callback called when an item from the filters popup menu is clicked. It changes the current filter type for the search to the one selected on the popup. ''' print "CoverArtBrowser DEBUG - filter_menu_callback" # remove old filter self.album_manager.model.remove_filter(self.filter_type, False) # radiomenu is of type GtkRadioMenuItem if radiomenu == self.filter_menu_all_item: self.filter_type = 'all' elif radiomenu == self.filter_menu_album_item: self.filter_type = 'album_name' elif radiomenu == self.filter_menu_artist_item: self.filter_type = 'artist' elif radiomenu == self.filter_menu_album_artist_item: self.filter_type = 'album_artist' elif radiomenu == self.filter_menu_track_title_item: self.filter_type = 'track' else: assert "unknown radiomenu" if self.search_text == '': self.search_entry.set_placeholder(radiomenu.get_label()) self.searchchanged_callback(_, self.search_text) print "CoverArtBrowser DEBUG - end filter_menu_callback" def filter_by_model(self, model=None): ''' resets what is displayed in the coverview with contents from the new query_model ''' print "CoverArtBrowser DEBUG - reset_coverview" if not model: self.album_manager.model.remove_filter('model') else: self.album_manager.model.replace_filter('model', model) print "CoverArtBrowser DEBUG - end reset_coverview" def update_iconview_callback(self, scrolled, *args): ''' Callback called by the cover view when its view port gets resized. It forces the cover_view to redraw it's contents to fill the available space. ''' width = scrolled.get_allocated_width() if width != self.last_width: # don't need to reacommodate if the bottom pane is being resized print "CoverArtBrowser DEBUG - update_iconview_callback" self.covers_view.set_columns(0) self.covers_view.set_columns(-1) # update width self.last_width = width def mouseclick_callback(self, iconview, event): ''' Callback called when the user clicks somewhere on the cover_view. If it's a right click, it shows a popup showing different actions to perform with the selected album. ''' print "CoverArtBrowser DEBUG - mouseclick_callback()" x = int(event.x) y = int(event.y) pthinfo = iconview.get_path_at_pos(x, y) if event.type is Gdk.EventType.BUTTON_PRESS and pthinfo: if event.triggers_context_menu(): # to show the context menu # if the item being clicked isn't selected, we should clear # the current selection if len(iconview.get_selected_items()) > 0 and \ not iconview.path_is_selected(pthinfo): iconview.unselect_all() iconview.select_path(pthinfo) iconview.set_cursor(pthinfo, None, False) self.popup_menu.popup(None, None, None, None, event.button, event.time) else: # to expand the entry view ctrl = event.state & Gdk.ModifierType.CONTROL_MASK shift = event.state & Gdk.ModifierType.SHIFT_MASK self.click_count += 1 if not ctrl and not shift and self.click_count == 1: album = self.album_manager.model.get_from_path(pthinfo)\ if pthinfo else None Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250, self._timeout_expand, album) print "CoverArtBrowser DEBUG - end mouseclick_callback()" def _timeout_expand(self, album): ''' helper function - if the entry is manually expanded then if necessary scroll the view to the last selected album ''' print "CoverArtBrowser DEBUG - _timeout_expand" if album and self.click_count == 1 \ and self.last_selected_album is album: # check if it's a second or third click on the album and expand # or collapse the entry view accordingly self.bottom_expander.set_expanded( not self.bottom_expander.get_expanded()) # update the selected album selected = self.get_selected_albums() self.last_selected_album = selected[0] if len(selected) == 1 else None # clear the click count self.click_count = 0 def item_activated_callback(self, iconview, path): ''' Callback called when the cover view is double clicked or space-bar is pressed. It plays the selected album ''' print "CoverArtBrowser DEBUG - item_activated_callback" self.play_selected_album() return True def get_selected_albums(self): ''' Retrieves the currently selected albums on the cover_view. ''' selected_albums = [] for selected in self.covers_view.get_selected_items(): selected_albums.append(self.album_manager.model.get_from_path( selected)) return selected_albums def play_selected_album(self, favourites=False): ''' Utilitary method that plays all entries from an album into the play queue. ''' # callback when play an album print "CoverArtBrowser DEBUG - play_selected_album" # clear the queue play_queue = self.shell.props.queue_source for row in play_queue.props.query_model: play_queue.remove_entry(row[0]) self.queue_selected_album(play_queue, favourites) # Start the music player = self.shell.props.shell_player player.stop() player.set_playing_source(self.shell.props.queue_source) player.playpause(True) print "CoverArtBrowser DEBUG - end play_selected_album" def queue_selected_album(self, source, favourites=False): ''' Utilitary method that queues all entries from an album into the play queue. ''' print "CoverArtBrowser DEBUG - queue_selected_album" selected_albums = self.get_selected_albums() threshold = self.rating_threshold if favourites else 0 for album in selected_albums: # Retrieve and sort the entries of the album tracks = album.get_tracks(threshold) # Add the songs to the play queue for track in tracks: source.add_entry(track.entry, -1) print "CoverArtBrowser DEBUG - end queue_select_album" def play_album_menu_item_callback(self, _): ''' Callback called when the play album item from the cover view popup is selected. It cleans the play queue and queues the selected album. ''' print "CoverArtBrowser DEBUG - play_album_menu_item_callback" self.play_selected_album() print "CoverArtBrowser DEBUG - end play_album_menu_item_callback" def queue_album_menu_item_callback(self, _): ''' Callback called when the queue album item from the cover view popup is selected. It queues the selected album at the end of the play queue. ''' print "CoverArtBrowser DEBUG - queue_album_menu_item_callback()" self.queue_selected_album(self.shell.props.queue_source) print "CoverArtBrowser DEBUG - end queue_album_menu_item_callback()" def queue_favourites_album_menu_item_callback(self, _): ''' Callback called when the queue-favourites album item from the cover view popup is selected. It queues the selected album at the end of the play queue. ''' print '''CoverArtBrowser DEBUG - queue_favourites_album_menu_item_callback()''' self.queue_selected_album(self.shell.props.queue_source, True) print '''CoverArtBrowser DEBUG - end queue_favourites_album_menu_item_callback()''' def play_favourites_album_menu_item_callback(self, _): ''' Callback called when the play favourites album item from the cover view popup is selected. It queues the selected album at the end of the play queue. ''' print '''CoverArtBrowser DEBUG - play_favourites_album_menu_item_callback()''' self.play_selected_album(True) print '''CoverArtBrowser DEBUG - end play_favourites_album_menu_item_callback()''' def playlist_menu_item_callback(self, menu_item): print "CoverArtBrowser DEBUG - playlist_menu_item_callback" self.playlist_fillmenu(self.playlist_sub_menu_item, self.actiongroup, self.add_to_static_playlist_menu_item_callback) def favourite_playlist_menu_item_callback(self, menu_item): print "CoverArtBrowser DEBUG - favourite_playlist_menu_item_callback" self.playlist_fillmenu(self.favourite_playlist_sub_menu_item, self.favourite_actiongroup, self.add_to_static_playlist_menu_item_callback, True) def playlist_fillmenu(self, menubar, actiongroup, func, favourite=False): print "CoverArtBrowser DEBUG - playlist_fillmenu" uim = self.shell.props.ui_manager playlist_manager = self.shell.props.playlist_manager playlists_entries = playlist_manager.get_playlists() #tidy up old playlists menu items before recreating the list for action in actiongroup.list_actions(): actiongroup.remove_action(action) count = 0 for menu_item in menubar: if count > 1: # ignore the first two menu items menubar.remove(menu_item) count += 1 menubar.show_all() uim.ensure_update() if playlists_entries: for playlist in playlists_entries: if playlist.props.is_local and \ isinstance(playlist, RB.StaticPlaylistSource): new_menu_item = Gtk.MenuItem(label=playlist.props.name) action = Gtk.Action(label=playlist.props.name, name=playlist.props.name, tooltip='', stock_id=Gtk.STOCK_CLEAR) action.connect('activate', func, playlist, favourite) new_menu_item.set_related_action(action) menubar.append(new_menu_item) actiongroup.add_action(action) menubar.show_all() uim.ensure_update() def add_to_static_playlist_menu_item_callback(self, action, playlist, favourite): print '''CoverArtBrowser DEBUG - add_to_static_playlist_menu_item_callback''' self.queue_selected_album(playlist, favourite) def add_playlist_menu_item_callback(self, menu_item): print '''CoverArtBrowser DEBUG - add_playlist_menu_item_callback''' playlist_manager = self.shell.props.playlist_manager playlist = playlist_manager.new_playlist('', False) self.queue_selected_album(playlist, False) def favourite_add_playlist_menu_item_callback(self, menu_item): print '''CoverArtBrowser DEBUG - favourite_add_playlist_menu_item_callback''' playlist_manager = self.shell.props.playlist_manager playlist = playlist_manager.new_playlist('', False) self.queue_selected_album(playlist, True) def cover_search_menu_item_callback(self, menu_item): ''' Callback called when the search cover option is selected from the cover view popup. It prompts the album loader to retrieve the selected album cover ''' print "CoverArtBrowser DEBUG - cover_search_menu_item_callback()" selected_albums = self.get_selected_albums() self.request_status_box.show_all() self.source_menu_search_all_item.set_sensitive(False) self.cover_search_menu_item.set_sensitive(False) self.coversearchtimer.Start() self.album_manager.cover_man.search_covers(selected_albums, self.update_request_status_bar) print "CoverArtBrowser DEBUG - end cover_search_menu_item_callback()" def search_all_covers_callback(self, _): ''' Callback called when the search all covers option is selected from the source's popup. It prompts the album loader to request ALL album's covers ''' print "CoverArtBrowser DEBUG - search_all_covers_callback()" self.request_status_box.show_all() self.source_menu_search_all_item.set_sensitive(False) self.cover_search_menu_item.set_sensitive(False) self.coversearchtimer.Start() self.album_manager.cover_man.search_covers( callback=self.update_request_status_bar) print "CoverArtBrowser DEBUG - end search_all_covers_callback()" def update_request_status_bar(self, album): ''' Callback called by the album loader starts performing a new cover request. It prompts the source to change the content of the request statusbar. ''' print "CoverArtBrowser DEBUG - update_request_status_bar" if album: Gdk.threads_enter() self.request_statusbar.set_text( (_('Requesting cover for %s - %s...') % (album.name, album.artist)).decode('UTF-8')) Gdk.threads_leave() if self.coversearchtimer: self.coversearchtimer.Stop() self.coversearchtimer.Start() else: Gdk.threads_enter() self.request_status_box.hide() self.source_menu_search_all_item.set_sensitive(True) self.cover_search_menu_item.set_sensitive(True) self.request_cancel_button.set_sensitive(True) self.coversearchtimer.Stop() Gdk.threads_leave() print "CoverArtBrowser DEBUG - end update_request_status_bar" def cancel_request_callback(self, _): ''' Callback connected to the cancel button on the request statusbar. When called, it prompts the album loader to cancel the full cover search after the current cover. ''' print "CoverArtBrowser DEBUG - cancel_request_callback" self.request_cancel_button.set_sensitive(False) self.album_manager.cover_man.cancel_cover_request() print "CoverArtBrowser DEBUG - end cancel_request_callback" def selectionchanged_callback(self, widget): ''' Callback called when an item from the cover view gets selected. It changes the content of the statusbar (which statusbar is dependant on the custom_statusbar_enabled property) to show info about the current selected album. ''' print "CoverArtBrowser DEBUG - selectionchanged_callback" # clear the entry view self.entry_view.clear() selected = self.get_selected_albums() cover_search_pane_visible = self.notebook.get_current_page() == \ self.notebook.page_num(self.cover_search_pane) if not selected: # if no album selected, clean the status and the cover tab if # if selected self.update_statusbar() if cover_search_pane_visible: self.cover_search_pane.clear() return elif len(selected) == 1: self.stars.set_rating(selected[0].rating) if selected[0] is not self.last_selected_album: # when the selection changes we've to take into account two # things if not self.click_count: # we may be using the arrows, so if there is no mouse # involved, we should change the last selected self.last_selected_album = selected[0] else: # we may've doing a fast change after a valid second click, # so it shouldn't be considered a double click self.click_count -= 1 else: self.stars.set_rating(0) track_count = 0 duration = 0 for album in selected: # Calculate duration and number of tracks from that album track_count += album.track_count duration += album.duration / 60 # add the album to the entry_view self.entry_view.add_album(album) # now lets build up a status label containing some 'interesting stuff' # about the album if len(selected) == 1: status = (_('%s by %s') % (album.name, album.artist)).decode( 'UTF-8') else: status = (_('%d selected albums ') % (len(selected))).decode( 'UTF-8') if track_count == 1: status += (_(' with 1 track')).decode('UTF-8') else: status += (_(' with %d tracks') % track_count).decode('UTF-8') if duration == 1: status += (_(' and a duration of 1 minute')).decode('UTF-8') else: status += (_(' and a duration of %d minutes') % duration).decode( 'UTF-8') self.update_statusbar(status) # update the cover search pane with the first selected album if cover_search_pane_visible: self.cover_search_pane.do_search(selected[0]) print "CoverArtBrowser DEBUG - end selectionchanged_callback" def update_statusbar(self, status=''): ''' Utility method that updates the status bar. ''' print "CoverArtBrowser DEBUG - update_statusbar" if self.custom_statusbar_enabled: # if the custom statusbar is enabled... use it. self.status_label.set_text(status) self.status_label.show() else: # use the global statusbar from Rhythmbox self.status = status self.notify_status_changed() print "CoverArtBrowser DEBUG - end update_statusbar" def bottom_expander_expanded_callback(self, action, param): ''' Callback connected to expanded signal of the paned GtkExpander ''' print "CoverArtBrowser DEBUG - bottom_expander_expanded_callback" expand = action.get_expanded() if not expand: # move the lower pane to the bottom since it's collapsed (x, y) = Gtk.Widget.get_toplevel(self.status_label).get_size() new_y = self.paned.get_position() self.gs.set_value(self.gs.Path.PLUGIN, self.gs.PluginKey.PANED_POSITION, new_y) self.paned.set_position(y - 10) else: # restitute the lower pane to it's expanded size new_y = self.gs.get_value(self.gs.Path.PLUGIN, self.gs.PluginKey.PANED_POSITION) if new_y == 0: # if there isn't a saved size, use half of the space (x, y) = Gtk.Widget.get_toplevel(self.status_label).get_size() new_y = (y / 2) self.gs.set_value(self.gs.Path.PLUGIN, self.gs.PluginKey.PANED_POSITION, new_y) self.paned.set_position(new_y) # acomodate the viewport if there's an album selected if self.last_selected_album: def scroll_to_album(*args): # acomodate the viewport if there's an album selected path = self.album_manager.model.get_path( self.last_selected_album) self.covers_view.scroll_to_path(path, False, 0, 0) return False Gdk.threads_add_idle(GObject.PRIORITY_DEFAULT_IDLE, scroll_to_album, None) print "CoverArtBrowser DEBUG - end bottom_expander_expanded_callback" def sorting_criteria_changed(self, sort_by): ''' Callback called when a radio corresponding to a sorting order is toggled. It changes the sorting function and reorders the cover model. ''' print "CoverArtBrowser DEBUG - sorting_criteria_changed" #if not radio.get_active(): # return print "sorting by %s" % sort_by self.sort_prop = sort_by self.album_manager.model.sort(self.sort_prop, self.sort_order) print "CoverArtBrowser DEBUG - end sorting_criteria_changed" def sorting_direction_changed(self, sort_by): ''' Callback called when the sort toggle button is toggled. It changes the sorting direction and reorders the cover model ''' print "CoverArtBrowser DEBUG - sorting_direction_changed" self.album_manager.model.sort(getattr(self, 'sort_prop', 'name'), sort_by) print "CoverArtBrowser DEBUG - end sorting_direction_changed" def on_drag_drop(self, widget, context, x, y, time): ''' Callback called when a drag operation finishes over the cover view of the source. It decides if the dropped item can be processed as an image to use as a cover. ''' print "CoverArtBrowser DEBUG - on_drag_drop" # stop the propagation of the signal (deactivates superclass callback) widget.stop_emission('drag-drop') # obtain the path of the icon over which the drag operation finished path, pos = widget.get_dest_item_at_pos(x, y) result = path is not None if result: target = self.covers_view.drag_dest_find_target(context, None) widget.drag_get_data(context, target, time) print "CoverArtBrowser DEBUG - end on_drag_drop" return result def on_drag_data_received(self, widget, drag_context, x, y, data, info, time): ''' Callback called when the drag source has prepared the data (pixbuf) for us to use. ''' print "CoverArtBrowser DEBUG - on_drag_data_received" # stop the propagation of the signal (deactivates superclass callback) widget.stop_emission('drag-data-received') # get the album and the info and ask the loader to update the cover path, pos = widget.get_dest_item_at_pos(x, y) album = widget.get_model()[path][2] pixbuf = data.get_pixbuf() if pixbuf: self.album_manager.cover_man.update_cover(album, pixbuf) else: uri = data.get_text() self.album_manager.cover_man.update_cover(album, uri=uri) # call the context drag_finished to inform the source about it drag_context.finish(True, False, time) print "CoverArtBrowser DEBUG - end on_drag_data_received" def notebook_switch_page_callback(self, notebook, page, page_num): ''' Callback called when the notebook page gets switched. It initiates the cover search when the cover search pane's page is selected. ''' print "CoverArtBrowser DEBUG - notebook_switch_page_callback" if page_num == 1: selected_albums = self.get_selected_albums() if selected_albums: self.cover_search_pane.do_search(selected_albums[0]) print "CoverArtBrowser DEBUG - end notebook_switch_page_callback" def rating_changed_callback(self, widget): ''' Callback called when the Rating stars is changed ''' print "CoverArtBrowser DEBUG - rating_changed_callback" rating = widget.get_rating() for album in self.get_selected_albums(): album.rating = rating print "CoverArtBrowser DEBUG - end rating_changed_callback" def genre_filter_callback(self, genre): if not genre: self.album_manager.model.remove_filter('genre') else: self.album_manager.model.replace_filter('genre', genre) def decade_filter_callback(self, decade): if not decade: self.album_manager.model.remove_filter('decade') else: self.album_manager.model.replace_filter('decade', decade) def select_album(self, album): path = self.album_manager.model.get_path(album) self.covers_view.unselect_all() self.covers_view.select_path(path) self.covers_view.set_cursor(path, None, False) self.covers_view.scroll_to_path(path, True, 0.5, 0.5) def hide_quick_search(self): self.quick_search_box.hide() self.covers_view.grab_focus() self.quick_search.props.text = '' def add_hide_on_timeout(self): self.quick_search_idle += 1 def hide_on_timeout(*args): self.quick_search_idle -= 1 if not self.quick_search_idle: self.hide_quick_search() return False Gdk.threads_add_timeout_seconds(GLib.PRIORITY_DEFAULT_IDLE, 4, hide_on_timeout, None) def on_quick_search(self, quick_search, *args): if self.quick_search_box.get_visible(): # quick search on album names search_text = quick_search.props.text album = self.album_manager.model.find_first_visible('album_name', search_text) if album: self.select_album(album) # add a timeout to hide the search entry self.add_hide_on_timeout() def on_quick_search_up_down(self, quick_search, event, *args): arrow = False try: current = self.get_selected_albums()[0] search_text = quick_search.props.text album = None if event.keyval == Gdk.KEY_Up: arrow = True album = self.album_manager.model.find_first_visible( 'album_name', search_text, current, True) elif event.keyval == Gdk.KEY_Down: arrow = True album = self.album_manager.model.find_first_visible( 'album_name', search_text, current) if album: self.select_album(album) except: pass if arrow: self.add_hide_on_timeout() return arrow @classmethod def get_instance(cls, **kwargs): ''' Returns the unique instance of the manager. ''' if not cls.instance: cls.instance = CoverArtBrowserSource(**kwargs) return cls.instance
class Preferences(GObject.Object, PeasGtk.Configurable): ''' Preferences for the CoverArt Browser Plugins. It holds the settings for the plugin and also is the responsible of creating the preferences dialog. ''' __gtype_name__ = 'CoverArtBrowserPreferences' object = GObject.property(type=GObject.Object) GENRE_POPUP = 1 GENRE_LIST = 2 def __init__(self): ''' Initialises the preferences, getting an instance of the settings saved by Gio. ''' GObject.Object.__init__(self) gs = GSetting() self.settings = gs.get_setting(gs.Path.PLUGIN) self._first_run = True def do_create_configure_widget(self): ''' Creates the plugin's preferences dialog ''' return self._create_display_contents(self) def display_preferences_dialog(self, plugin): print ("DEBUG - display_preferences_dialog") if self._first_run: self._first_run = False cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) self._dialog = Gtk.Dialog(modal=True, destroy_with_parent=True) self._dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) self._dialog.set_title(_('Browser Preferences')) content_area = self._dialog.get_content_area() content_area.pack_start(self._create_display_contents(plugin), True, True, 0) helpbutton = self._dialog.add_button(Gtk.STOCK_HELP, Gtk.ResponseType.HELP) helpbutton.connect('clicked', self._display_help) self._dialog.show_all() print ("shown") while True: response = self._dialog.run() print ("and run") if response != Gtk.ResponseType.HELP: break self._dialog.hide() print ("DEBUG - display_preferences_dialog end") def _display_help(self, *args): peas = Peas.Engine.get_default() uri = peas.get_plugin_info('coverart_browser').get_help_uri() webbrowser.open(uri) def _create_display_contents(self, plugin): print ("DEBUG - create_display_contents") # create the ui self._first_run = True cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) builder = Gtk.Builder() builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN) builder.add_from_file(rb.find_plugin_file(plugin, 'ui/coverart_browser_prefs.ui')) self.launchpad_button = builder.get_object('show_launchpad') self.launchpad_label = builder.get_object('launchpad_label') builder.connect_signals(self) #. TRANSLATORS: Do not translate this string. translators = _('translator-credits') if translators != "translator-credits": self.launchpad_label.set_text(translators) else: self.launchpad_button.set_visible(False) gs = GSetting() # bind the toggles to the settings toggle_statusbar = builder.get_object('custom_statusbar_checkbox') self.settings.bind(gs.PluginKey.CUSTOM_STATUSBAR, toggle_statusbar, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_text = builder.get_object('display_text_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, toggle_text, 'active', Gio.SettingsBindFlags.DEFAULT) box_text = builder.get_object('display_text_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, box_text, 'sensitive', Gio.SettingsBindFlags.GET) self.display_text_pos = self.settings[gs.PluginKey.DISPLAY_TEXT_POS] self.display_text_under_radiobutton = builder.get_object('display_text_under_radiobutton') self.display_text_within_radiobutton = builder.get_object('display_text_within_radiobutton') if self.display_text_pos: self.display_text_under_radiobutton.set_active(True) else: self.display_text_within_radiobutton.set_active(True) random_scale = builder.get_object('random_adjustment') self.settings.bind(gs.PluginKey.RANDOM, random_scale, 'value', Gio.SettingsBindFlags.DEFAULT) toggle_text_ellipsize = builder.get_object( 'display_text_ellipsize_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, toggle_text_ellipsize, 'active', Gio.SettingsBindFlags.DEFAULT) box_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, box_text_ellipsize_length, 'sensitive', Gio.SettingsBindFlags.GET) spinner_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_spin') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE_LENGTH, spinner_text_ellipsize_length, 'value', Gio.SettingsBindFlags.DEFAULT) spinner_font_size = builder.get_object( 'display_font_spin') self.settings.bind(gs.PluginKey.DISPLAY_FONT_SIZE, spinner_font_size, 'value', Gio.SettingsBindFlags.DEFAULT) cover_size_scale = builder.get_object('cover_size_adjustment') self.settings.bind(gs.PluginKey.COVER_SIZE, cover_size_scale, 'value', Gio.SettingsBindFlags.DEFAULT) add_shadow = builder.get_object('add_shadow_checkbox') self.settings.bind(gs.PluginKey.ADD_SHADOW, add_shadow, 'active', Gio.SettingsBindFlags.DEFAULT) rated_box = builder.get_object('rated_box') self.stars = ReactiveStar(size=StarSize.BIG) self.stars.connect('changed', self.rating_changed_callback) align = Gtk.Alignment.new(0.5, 0, 0, 0.1) align.add(self.stars) rated_box.add(align) self.stars.set_rating(self.settings[gs.PluginKey.RATING]) autostart = builder.get_object('autostart_checkbox') self.settings.bind(gs.PluginKey.AUTOSTART, autostart, 'active', Gio.SettingsBindFlags.DEFAULT) toolbar_pos_combo = builder.get_object('show_in_combobox') renderer = Gtk.CellRendererText() toolbar_pos_combo.pack_start(renderer, True) toolbar_pos_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.TOOLBAR_POS, toolbar_pos_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) light_source_combo = builder.get_object('light_source_combobox') renderer = Gtk.CellRendererText() light_source_combo.pack_start(renderer, True) light_source_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.SHADOW_IMAGE, light_source_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) combo_liststore = builder.get_object('combo_liststore') from coverart_utils import Theme for theme in Theme(self).themes: combo_liststore.append([theme, theme]) theme_combo = builder.get_object('theme_combobox') renderer = Gtk.CellRendererText() theme_combo.pack_start(renderer, True) theme_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.THEME, theme_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) button_relief = builder.get_object('button_relief_checkbox') self.settings.bind(gs.PluginKey.BUTTON_RELIEF, button_relief, 'active', Gio.SettingsBindFlags.DEFAULT) # create user data files cachedir = RB.user_cache_dir() + "/coverart_browser/usericons" if not os.path.exists(cachedir): os.makedirs(cachedir) popup = cachedir + "/popups.xml" temp = RB.find_user_data_file('plugins/coverart_browser/img/usericons/popups.xml') # lets see if there is a legacy file - if necessary copy it to the cache dir if os.path.isfile(temp) and not os.path.isfile(popup): shutil.copyfile(temp, popup) if not os.path.isfile(popup): template = rb.find_plugin_file(plugin, 'template/popups.xml') folder = os.path.split(popup)[0] if not os.path.exists(folder): os.makedirs(folder) shutil.copyfile(template, popup) # now prepare the genre tab from coverart_utils import GenreConfiguredSpriteSheet from coverart_utils import get_stock_size self._sheet = GenreConfiguredSpriteSheet(plugin, "genre", get_stock_size()) self.alt_liststore = builder.get_object('alt_liststore') self.alt_user_liststore = builder.get_object('alt_user_liststore') self._iters = {} for key in list(self._sheet.keys()): store_iter = self.alt_liststore.append([key, self._sheet[key]]) self._iters[(key, self.GENRE_POPUP)] = store_iter for key, value in self._sheet.genre_alternate.items(): if key.genre_type == GenreConfiguredSpriteSheet.GENRE_USER: store_iter = self.alt_user_liststore.append([key.name, self._sheet[self._sheet.genre_alternate[key]], self._sheet.genre_alternate[key]]) self._iters[(key.name, self.GENRE_LIST)] = store_iter self.amend_mode = False self.blank_iter = None self.genre_combobox = builder.get_object('genre_combobox') self.genre_entry = builder.get_object('genre_entry') self.genre_view = builder.get_object('genre_view') self.save_button = builder.get_object('save_button') self.filechooserdialog = builder.get_object('filechooserdialog') last_genre_folder = self.settings[gs.PluginKey.LAST_GENRE_FOLDER] if last_genre_folder != "": self.filechooserdialog.set_current_folder(last_genre_folder) padding_scale = builder.get_object('padding_adjustment') self.settings.bind(gs.PluginKey.ICON_PADDING, padding_scale, 'value', Gio.SettingsBindFlags.DEFAULT) spacing_scale = builder.get_object('spacing_adjustment') self.settings.bind(gs.PluginKey.ICON_SPACING, spacing_scale, 'value', Gio.SettingsBindFlags.DEFAULT) icon_automatic = builder.get_object('icon_automatic_checkbox') self.settings.bind(gs.PluginKey.ICON_AUTOMATIC, icon_automatic, 'active', Gio.SettingsBindFlags.DEFAULT) #flow tab flow_combo = builder.get_object('flow_combobox') renderer = Gtk.CellRendererText() flow_combo.pack_start(renderer, True) flow_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.FLOW_APPEARANCE, flow_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) flow_hide = builder.get_object('hide_caption_checkbox') self.settings.bind(gs.PluginKey.FLOW_HIDE_CAPTION, flow_hide, 'active', Gio.SettingsBindFlags.DEFAULT) flow_scale = builder.get_object('cover_scale_adjustment') self.settings.bind(gs.PluginKey.FLOW_SCALE, flow_scale, 'value', Gio.SettingsBindFlags.DEFAULT) flow_width = builder.get_object('cover_width_adjustment') self.settings.bind(gs.PluginKey.FLOW_WIDTH, flow_width, 'value', Gio.SettingsBindFlags.DEFAULT) flow_max = builder.get_object('flow_max_adjustment') self.settings.bind(gs.PluginKey.FLOW_MAX, flow_max, 'value', Gio.SettingsBindFlags.DEFAULT) flow_automatic = builder.get_object('automatic_checkbox') self.settings.bind(gs.PluginKey.FLOW_AUTOMATIC, flow_automatic, 'active', Gio.SettingsBindFlags.DEFAULT) self.background_colour = self.settings[gs.PluginKey.FLOW_BACKGROUND_COLOUR] self.white_radiobutton = builder.get_object('white_radiobutton') self.black_radiobutton = builder.get_object('black_radiobutton') if self.background_colour == 'W': self.white_radiobutton.set_active(True) else: self.black_radiobutton.set_active(True) # return the dialog self._first_run = False print ("end create dialog contents") return builder.get_object('main_notebook') def on_flow_combobox_changed(self, combobox): current_val = combobox.get_model()[combobox.get_active()][0] gs = GSetting() if self.settings[gs.PluginKey.FLOW_APPEARANCE] != current_val: if current_val == 'flow-vert': default_size = 150 else: default_size = 600 self.settings[gs.PluginKey.FLOW_WIDTH] = default_size if current_val == 'carousel': self.settings[gs.PluginKey.FLOW_HIDE_CAPTION] = True def on_background_radio_toggled(self, button): if button.get_active(): gs = GSetting() if button == self.white_radiobutton: self.settings[gs.PluginKey.FLOW_BACKGROUND_COLOUR] = 'W' else: self.settings[gs.PluginKey.FLOW_BACKGROUND_COLOUR] = 'B' def on_display_text_pos_radio_toggled(self, button): if self._first_run: return if button.get_active(): gs = GSetting() if button == self.display_text_under_radiobutton: self.settings[gs.PluginKey.DISPLAY_TEXT_POS] = True else: self.settings[gs.PluginKey.DISPLAY_TEXT_POS] = False self.settings[gs.PluginKey.ADD_SHADOW] = False def on_add_shadow_checkbox_toggled(self, button): if button.get_active(): #gs = GSetting() #self.settings[gs.PluginKey.DISPLAY_TEXT_POS] = True self.display_text_under_radiobutton.set_active(True) def rating_changed_callback(self, stars): print("rating_changed_callback") gs = GSetting() self.settings[gs.PluginKey.RATING] = self.stars.get_rating() def on_save_button_clicked(self, button): ''' action when genre edit area is saved ''' entry_value = self.genre_entry.get_text() treeiter = self.genre_combobox.get_active_iter() icon_value = self.alt_liststore[treeiter][0] # model 0 is the icon name, model 1 is the pixbuf if self.amend_mode: key = self._sheet.amend_genre_info(self.current_genre, entry_value, icon_value) self.alt_user_liststore[self._iters[(self.current_genre, self.GENRE_LIST)]][1] = self._sheet[self._sheet.genre_alternate[key]] self.alt_user_liststore[self._iters[(self.current_genre, self.GENRE_LIST)]][0] = key.name store_iter = self._iters[(self.current_genre, self.GENRE_LIST)] del self._iters[(self.current_genre, self.GENRE_LIST)] self._iters[(key.name, self.GENRE_LIST)] = store_iter else: self.amend_mode = True key = self._sheet.amend_genre_info('', entry_value, icon_value) self.current_genre = key.name store_iter = self.alt_user_liststore.append([key.name, self._sheet[self._sheet.genre_alternate[key]], self._sheet.genre_alternate[key]]) self._iters[(key.name, self.GENRE_LIST)] = store_iter selection = self.genre_view.get_selection() selection.select_iter(store_iter) self.save_button.set_sensitive(False) self._toggle_new_genre_state() def on_genre_filechooserbutton_file_set(self, filechooser): ''' action when genre new icon button is pressed ''' key = self._sheet.add_genre_icon(self.filechooserdialog.get_filename()) store_iter = self.alt_liststore.append([key.name, self._sheet[key.name]]) self._iters[(key.name, self.GENRE_POPUP)] = store_iter gs = GSetting() last_genre_folder = self.filechooserdialog.get_current_folder() print(last_genre_folder) print(self.filechooserdialog.get_filename()) if last_genre_folder: self.settings[gs.PluginKey.LAST_GENRE_FOLDER] = last_genre_folder def on_genre_view_selection_changed(self, view): ''' action when user selects a row in the list of genres ''' model, genre_iter = view.get_selected() if genre_iter: self.genre_entry.set_text(model[genre_iter][0]) index = model[genre_iter][2] if index != '': self.genre_combobox.set_active_iter(self._iters[(index, self.GENRE_POPUP)]) self.amend_mode = True self.current_genre = rb3compat.unicodestr(model[genre_iter][0], 'utf-8') else: self.genre_entry.set_text('') self.genre_combobox.set_active_iter(None) self.amend_mode = False if self.blank_iter and self.amend_mode: try: index = model[self.blank_iter][0] if index == '': model.remove(self.blank_iter) self.blank_iter = None except: self.blank_iter = None def on_add_button_clicked(self, button): ''' action when a new genre is added to the table ''' self.genre_entry.set_text('') self.genre_combobox.set_active(-1) self.amend_mode = False self.blank_iter = self.alt_user_liststore.append(['', None, '']) selection = self.genre_view.get_selection() selection.select_iter(self.blank_iter) def on_delete_button_clicked(self, button): ''' action when a genre is to be deleted ''' selection = self.genre_view.get_selection() model, genre_iter = selection.get_selected() if genre_iter: index = rb3compat.unicodestr(model[genre_iter][0], 'utf-8') model.remove(genre_iter) if index: del self._iters[(index, self.GENRE_LIST)] self._sheet.delete_genre(index) self._toggle_new_genre_state() def set_save_sensitivity(self, _): ''' action to toggle the state of the save button depending upon the values entered in the genre edit fields ''' entry_value = self.genre_entry.get_text() treeiter = self.genre_combobox.get_active_iter() entry_value = rb3compat.unicodestr(entry_value, 'utf-8') enable = False try: test = self._iters[(entry_value, self.GENRE_LIST)] if RB.search_fold(self.current_genre) == RB.search_fold(entry_value): #if the current entry is the same then could save enable = True except: # reach here if this is a brand new entry enable = True if treeiter == None or entry_value == None or entry_value == "": # no icon chosen, or no entry value then nothing to save enable = False self.save_button.set_sensitive(enable) def _toggle_new_genre_state(self): ''' fire an event - uses gsettings and an object such as a controller connects to receive the signal that a new or amended genre has been made ''' gs = GSetting() test = self.settings[gs.PluginKey.NEW_GENRE_ICON] if test: test = False else: test = True self.settings[gs.PluginKey.NEW_GENRE_ICON] = test def on_show_launchpad_toggled(self, button): self.launchpad_label.set_visible(button.get_active())
def _setup_source(self): ''' Setups the differents parts of the source so they are ready to be used by the user. It also creates and configure some custom widgets. ''' print("CoverArtBrowser DEBUG - _setup_source") cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) # setup iconview popup self.viewmgr.current_view.set_popup_menu(self.popup_menu) # setup entry-view objects and widgets setting = self.gs.get_setting(self.gs.Path.PLUGIN) setting.bind(self.gs.PluginKey.PANED_POSITION, self.paned, 'collapsible-y', Gio.SettingsBindFlags.DEFAULT) setting.bind(self.gs.PluginKey.DISPLAY_BOTTOM, self.paned.get_child2(), 'visible', Gio.SettingsBindFlags.DEFAULT) # create entry view. Don't allow to reorder until the load is finished self.entry_view = EV(self.shell, self) self.entry_view.set_columns_clickable(False) self.shell.props.library_source.get_entry_view().set_columns_clickable( False) self.stars = ReactiveStar() self.stars.set_rating(0) a = Gtk.Alignment.new(0.5, 0.5, 0, 0) a.add(self.stars) self.stars.connect('changed', self.rating_changed_callback) vbox = Gtk.Box() vbox.set_orientation(Gtk.Orientation.VERTICAL) vbox.pack_start(self.entry_view, True, True, 0) vbox.pack_start(a, False, False, 1) vbox.show_all() self.notebook.append_page(vbox, Gtk.Label.new_with_mnemonic(_("Tracks"))) # create an album manager self.album_manager = AlbumManager(self.plugin, self.viewmgr.current_view) self.viewmgr.current_view.initialise(self) # setup cover search pane colour = self.viewmgr.get_selection_colour() self.cover_search_pane = CoverSearchPane(self.plugin, colour) self.notebook.append_page(self.cover_search_pane, Gtk.Label.new_with_mnemonic( _("Covers"))) # connect a signal to when the info of albums is ready self.load_fin_id = self.album_manager.loader.connect( 'model-load-finished', self.load_finished_callback) # prompt the loader to load the albums self.album_manager.loader.load_albums(self.props.base_query_model) # initialise the variables of the quick search self.quick_search_controller = AlbumQuickSearchController( self.album_manager) self.quick_search_controller.connect_quick_search(self.quick_search) # set sensitivity of export menu item for iconview self.popup_menu.set_sensitive('export_embed_menu_item', CoverArtExport(self.plugin, self.shell, self.album_manager).is_search_plugin_enabled()) # setup the statusbar component self.statusbar = Statusbar(self) # initialise the toolbar manager self.toolbar_manager = ToolbarManager(self.plugin, self.page, self.viewmgr) self.viewmgr.current_view.emit('update-toolbar') cl.switch_locale(cl.Locale.RB) # setup the artist paned artist_pview = None for view in self.shell.props.library_source.get_property_views(): print (view.props.title) print (_("Artist")) if view.props.title == _("Artist"): artist_pview = view break assert artist_pview, "cannot find artist property view" self.artist_treeview.set_model(artist_pview.get_model()) setting.bind(self.gs.PluginKey.ARTIST_PANED_POSITION, self, 'artist-paned-pos', Gio.SettingsBindFlags.DEFAULT) self.artist_paned.connect('button-release-event', self.artist_paned_button_release_callback) # intercept JumpToPlaying Song action so that we can scroll to the playing album appshell = rb3compat.ApplicationShell(self.shell) action = appshell.lookup_action("", "jump-to-playing", "win") action.action.connect("activate", self.jump_to_playing, None) self.echonest_similar_playlist = None print("CoverArtBrowser DEBUG - end _setup_source")
class EntryViewPane(object): ''' encapulates all of the Track Pane objects ''' def __init__(self, shell, plugin, source, entry_view_grid, viewmgr): self.gs = GSetting() self.entry_view_grid = entry_view_grid self.shell = shell self.viewmgr = viewmgr self.plugin = plugin self.source = source # setup entry-view objects and widgets self.stack = Gtk.Stack() self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) self.stack.set_transition_duration(750) # create entry views. Don't allow to reorder until the load is finished self.entry_view_compact = CoverArtCompactEntryView(self.shell, self.source) self.entry_view_full = CoverArtEntryView(self.shell, self.source) self.entry_view = self.entry_view_compact self.shell.props.library_source.get_entry_view().set_columns_clickable( False) self.entry_view_results = ResultsGrid() self.entry_view_results.initialise(self.entry_view_grid, source) self.stack.add_titled(self.entry_view_results, "notebook_tracks", _("Tracks")) self.entry_view_grid.attach(self.stack, 0, 0, 3, 1) def setup_source(self): colour = self.viewmgr.get_selection_colour() self.cover_search_pane = CoverSearchPane(self.plugin, colour) self.stack.add_titled(self.cover_search_pane, "notebook_covers", _("Covers")) # define entry-view toolbar self.stars = ReactiveStar() self.stars.set_rating(0) self.stars.connect('changed', self.rating_changed_callback) self.stars.props.valign = Gtk.Align.CENTER self.entry_view_grid.attach(self.stars, 1, 1, 1, 1) stack_switcher = Gtk.StackSwitcher() stack_switcher.set_stack(self.stack) self.entry_view_grid.attach(stack_switcher, 0, 1, 1, 1) viewtoggle = PixbufButton() viewtoggle.set_image(create_button_image(self.plugin, "entryview.png")) self.viewtoggle_id = None setting = self.gs.get_setting(self.gs.Path.PLUGIN) viewtoggle.set_active(not setting[self.gs.PluginKey.ENTRY_VIEW_MODE]) self.entry_view_toggled(viewtoggle, True) viewtoggle.connect('toggled', self.entry_view_toggled) smallwindowbutton = PixbufButton() smallwindowbutton.set_image(create_button_image(self.plugin, "view-restore.png")) smallwindowbutton.connect('toggled', self.smallwindowbutton_callback) self.smallwindowext = ExternalPlugin() self.smallwindowext.appendattribute('plugin_name', 'smallwindow') self.smallwindowext.appendattribute('action_group_name', 'small window actions') self.smallwindowext.appendattribute('action_name', 'SmallWindow') self.smallwindowext.appendattribute('action_type', 'app') whatsplayingtoggle = PixbufButton() whatsplayingtoggle.set_image(create_button_image(self.plugin, "whatsplaying.png")) whatsplayingtoggle.connect('toggled', self.whatsplayingtoggle_callback) rightgrid = Gtk.Grid() rightgrid.props.halign = Gtk.Align.END # rightgrid.attach(whatsplayingtoggle, 0, 0, 1, 1) rightgrid.attach(viewtoggle, 1, 0, 1, 1) rightgrid.attach(smallwindowbutton, 2, 0, 1, 1) self.entry_view_grid.attach_next_to(rightgrid, self.stars, Gtk.PositionType.RIGHT, 1, 1) self.stack.set_visible_child(self.entry_view_results) self.stack.connect('notify::visible-child-name', self.notebook_switch_page_callback) self.entry_view_grid.show_all() smallwindowbutton.set_visible(self.smallwindowext.is_activated()) def whatsplayingtoggle_callback(self, widget): self.entry_view_results.emit('whats-playing', widget.get_active()) def smallwindowbutton_callback(self, widget): if widget.get_active(): self.smallwindowext.activate(self.shell) widget.emit('clicked') def entry_view_toggled(self, widget, initialised=False): print("DEBUG - entry_view_toggled") if widget.get_active(): next_view = self.entry_view_full show_coverart = False if self.viewtoggle_id: self.shell.props.window.disconnect(self.viewtoggle_id) self.viewtoggle_id = None else: next_view = self.entry_view_compact show_coverart = True self.viewtoggle_id = self.shell.props.window.connect('check_resize', self.entry_view_results.window_resize) setting = self.gs.get_setting(self.gs.Path.PLUGIN) setting[self.gs.PluginKey.ENTRY_VIEW_MODE] = not widget.get_active() self.entry_view_results.change_view(next_view, show_coverart) self.entry_view = next_view if not initialised: self.source.update_with_selection() def notebook_switch_page_callback(self, *args): ''' Callback called when the notebook page gets switched. It initiates the cover search when the cover search pane's page is selected. ''' print("CoverArtBrowser DEBUG - notebook_switch_page_callback") if self.stack.get_visible_child_name() == 'notebook_covers': self.viewmgr.current_view.switch_to_coverpane(self.cover_search_pane) else: entries = self.entry_view.get_selected_entries() if entries and len(entries) > 0: self.entry_view_results.emit('update-cover', self.source, entries[0]) else: selected = self.viewmgr.current_view.get_selected_objects() tracks = selected[0].get_tracks() self.entry_view_results.emit('update-cover', self.source, tracks[0].entry) print("CoverArtBrowser DEBUG - end notebook_switch_page_callback") def rating_changed_callback(self, widget): ''' Callback called when the Rating stars is changed ''' print("CoverArtBrowser DEBUG - rating_changed_callback") rating = widget.get_rating() for album in self.viewmgr.current_view.get_selected_objects(): album.rating = rating print("CoverArtBrowser DEBUG - end rating_changed_callback") def get_entry_view(self): return self.entry_view def update_cover(self, album_artist, manager): if not self.stack.get_visible_child_name() == "notebook_covers": return self.cover_search_pane.clear() self.cover_search(album_artist, manager) def cover_search(self, album_artist, manager): self.cover_search_pane.do_search(album_artist, manager.cover_man.update_cover) def update_selection(self, last_selected_album, click_count): ''' Update the source view when an item gets selected. ''' print("DEBUG - update_with_selection") selected = self.viewmgr.current_view.get_selected_objects() # clear the entry view self.entry_view.clear() cover_search_pane_visible = self.stack.get_visible_child_name() == "notebook_covers" if not selected: # clean cover tab if selected if cover_search_pane_visible: self.cover_search_pane.clear() self.entry_view_results.emit('update-cover', self.source, None) return last_selected_album, click_count elif len(selected) == 1: self.stars.set_rating(selected[0].rating) if selected[0] is not last_selected_album: # when the selection changes we've to take into account two # things if not click_count: # we may be using the arrows, so if there is no mouse # involved, we should change the last selected last_selected_album = selected[0] else: # we may've doing a fast change after a valid second click, # so it shouldn't be considered a double click click_count -= 1 else: self.stars.set_rating(0) if len(selected) == 1: self.source.artist_info.emit('selected', selected[0].artist, selected[0].name) self.entry_view.set_sorting_order('track-number', Gtk.SortType.ASCENDING) for album in selected: # add the album to the entry_view self.entry_view.add_album(album) if len(selected) > 0: def cover_update(*args): print("emitting") self.entry_view_results.emit('update-cover', self.source, selected[0].get_tracks()[0].entry) # add a short delay to give the entry-pane time to expand etc. Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250, cover_update, None) # update the cover search pane with the first selected album if cover_search_pane_visible: self.cover_search_pane.do_search(selected[0], self.source.album_manager.cover_man.update_cover) return last_selected_album, click_count
def do_create_configure_widget(self): ''' Creates the plugin's preferences dialog ''' # create the ui cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) builder = Gtk.Builder() builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN) builder.add_from_file(rb.find_plugin_file(self, 'ui/coverart_browser_prefs.ui')) self.launchpad_button = builder.get_object('show_launchpad') self.launchpad_label = builder.get_object('launchpad_label') builder.connect_signals(self) #. TRANSLATORS: Do not translate this string. translators = _('translator-credits') if translators != "translator-credits": self.launchpad_label.set_text(translators) else: self.launchpad_button.set_visible(False) gs = GSetting() # bind the toggles to the settings toggle_statusbar = builder.get_object('custom_statusbar_checkbox') self.settings.bind(gs.PluginKey.CUSTOM_STATUSBAR, toggle_statusbar, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_bottom = builder.get_object('display_bottom_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_BOTTOM, toggle_bottom, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_text = builder.get_object('display_text_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, toggle_text, 'active', Gio.SettingsBindFlags.DEFAULT) box_text = builder.get_object('display_text_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, box_text, 'sensitive', Gio.SettingsBindFlags.GET) toggle_text_ellipsize = builder.get_object( 'display_text_ellipsize_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, toggle_text_ellipsize, 'active', Gio.SettingsBindFlags.DEFAULT) box_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, box_text_ellipsize_length, 'sensitive', Gio.SettingsBindFlags.GET) spinner_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_spin') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE_LENGTH, spinner_text_ellipsize_length, 'value', Gio.SettingsBindFlags.DEFAULT) spinner_font_size = builder.get_object( 'display_font_spin') self.settings.bind(gs.PluginKey.DISPLAY_FONT_SIZE, spinner_font_size, 'value', Gio.SettingsBindFlags.DEFAULT) cover_size_scale = builder.get_object('cover_size_adjustment') self.settings.bind(gs.PluginKey.COVER_SIZE, cover_size_scale, 'value', Gio.SettingsBindFlags.DEFAULT) add_shadow = builder.get_object('add_shadow_checkbox') self.settings.bind(gs.PluginKey.ADD_SHADOW, add_shadow, 'active', Gio.SettingsBindFlags.DEFAULT) rated_box = builder.get_object('rated_box') self.stars = ReactiveStar(size=StarSize.BIG) self.stars.connect('changed', self.rating_changed_callback) align = Gtk.Alignment.new(0.5, 0, 0, 0.1) align.add(self.stars) rated_box.add(align) self.stars.set_rating(self.settings[gs.PluginKey.RATING]) autostart = builder.get_object('autostart_checkbox') self.settings.bind(gs.PluginKey.AUTOSTART, autostart, 'active', Gio.SettingsBindFlags.DEFAULT) toolbar_pos_combo = builder.get_object('show_in_combobox') renderer = Gtk.CellRendererText() toolbar_pos_combo.pack_start(renderer, True) toolbar_pos_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.TOOLBAR_POS, toolbar_pos_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) light_source_combo = builder.get_object('light_source_combobox') renderer = Gtk.CellRendererText() light_source_combo.pack_start(renderer, True) light_source_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.SHADOW_IMAGE, light_source_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) combo_liststore = builder.get_object('combo_liststore') from coverart_utils import Theme for theme in Theme(self).themes: combo_liststore.append([theme, theme]) theme_combo = builder.get_object('theme_combobox') renderer = Gtk.CellRendererText() theme_combo.pack_start(renderer, True) theme_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.THEME, theme_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) button_relief = builder.get_object('button_relief_checkbox') self.settings.bind(gs.PluginKey.BUTTON_RELIEF, button_relief, 'active', Gio.SettingsBindFlags.DEFAULT) # create user data files popup = RB.find_user_data_file('plugins/coverart_browser/img/usericons/popups.xml') if not os.path.isfile(popup): template = rb.find_plugin_file(self, 'template/popups.xml') folder = os.path.split(popup)[0] if not os.path.exists(folder): os.makedirs(folder) shutil.copyfile(template, popup) # now prepare the genre tab from coverart_utils import GenreConfiguredSpriteSheet from coverart_utils import get_stock_size from coverart_utils import GenreType self._sheet = GenreConfiguredSpriteSheet(self, "genre", get_stock_size()) self.alt_liststore = builder.get_object('alt_liststore') self.alt_user_liststore = builder.get_object('alt_user_liststore') self._iters = {} for key in self._sheet.keys(): store_iter = self.alt_liststore.append([key, self._sheet[key]]) self._iters[(key,self.GENRE_POPUP)] = store_iter for key, value in self._sheet.genre_alternate.iteritems(): if key.genre_type == GenreConfiguredSpriteSheet.GENRE_USER: store_iter = self.alt_user_liststore.append([key.name, self._sheet[self._sheet.genre_alternate[key]], self._sheet.genre_alternate[key]]) self._iters[(key.name, self.GENRE_LIST)] = store_iter self.amend_mode = False self.blank_iter = None self.genre_combobox = builder.get_object('genre_combobox') self.genre_entry = builder.get_object('genre_entry') self.genre_view = builder.get_object('genre_view') self.save_button = builder.get_object('save_button') self.filechooserdialog = builder.get_object('filechooserdialog') # return the dialog return builder.get_object('main_notebook')
class Preferences(GObject.Object, PeasGtk.Configurable): ''' Preferences for the CoverArt Browser Plugins. It holds the settings for the plugin and also is the responsible of creating the preferences dialog. ''' __gtype_name__ = 'CoverArtBrowserPreferences' object = GObject.property(type=GObject.Object) GENRE_POPUP = 1 GENRE_LIST = 2 def __init__(self): ''' Initialises the preferences, getting an instance of the settings saved by Gio. ''' GObject.Object.__init__(self) gs = GSetting() self.settings = gs.get_setting(gs.Path.PLUGIN) def do_create_configure_widget(self): ''' Creates the plugin's preferences dialog ''' # create the ui cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) builder = Gtk.Builder() builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN) builder.add_from_file(rb.find_plugin_file(self, 'ui/coverart_browser_prefs.ui')) self.launchpad_button = builder.get_object('show_launchpad') self.launchpad_label = builder.get_object('launchpad_label') builder.connect_signals(self) #. TRANSLATORS: Do not translate this string. translators = _('translator-credits') if translators != "translator-credits": self.launchpad_label.set_text(translators) else: self.launchpad_button.set_visible(False) gs = GSetting() # bind the toggles to the settings toggle_statusbar = builder.get_object('custom_statusbar_checkbox') self.settings.bind(gs.PluginKey.CUSTOM_STATUSBAR, toggle_statusbar, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_bottom = builder.get_object('display_bottom_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_BOTTOM, toggle_bottom, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_text = builder.get_object('display_text_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, toggle_text, 'active', Gio.SettingsBindFlags.DEFAULT) box_text = builder.get_object('display_text_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, box_text, 'sensitive', Gio.SettingsBindFlags.GET) toggle_text_ellipsize = builder.get_object( 'display_text_ellipsize_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, toggle_text_ellipsize, 'active', Gio.SettingsBindFlags.DEFAULT) box_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, box_text_ellipsize_length, 'sensitive', Gio.SettingsBindFlags.GET) spinner_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_spin') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE_LENGTH, spinner_text_ellipsize_length, 'value', Gio.SettingsBindFlags.DEFAULT) spinner_font_size = builder.get_object( 'display_font_spin') self.settings.bind(gs.PluginKey.DISPLAY_FONT_SIZE, spinner_font_size, 'value', Gio.SettingsBindFlags.DEFAULT) cover_size_scale = builder.get_object('cover_size_adjustment') self.settings.bind(gs.PluginKey.COVER_SIZE, cover_size_scale, 'value', Gio.SettingsBindFlags.DEFAULT) add_shadow = builder.get_object('add_shadow_checkbox') self.settings.bind(gs.PluginKey.ADD_SHADOW, add_shadow, 'active', Gio.SettingsBindFlags.DEFAULT) rated_box = builder.get_object('rated_box') self.stars = ReactiveStar(size=StarSize.BIG) self.stars.connect('changed', self.rating_changed_callback) align = Gtk.Alignment.new(0.5, 0, 0, 0.1) align.add(self.stars) rated_box.add(align) self.stars.set_rating(self.settings[gs.PluginKey.RATING]) autostart = builder.get_object('autostart_checkbox') self.settings.bind(gs.PluginKey.AUTOSTART, autostart, 'active', Gio.SettingsBindFlags.DEFAULT) toolbar_pos_combo = builder.get_object('show_in_combobox') renderer = Gtk.CellRendererText() toolbar_pos_combo.pack_start(renderer, True) toolbar_pos_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.TOOLBAR_POS, toolbar_pos_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) light_source_combo = builder.get_object('light_source_combobox') renderer = Gtk.CellRendererText() light_source_combo.pack_start(renderer, True) light_source_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.SHADOW_IMAGE, light_source_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) combo_liststore = builder.get_object('combo_liststore') from coverart_utils import Theme for theme in Theme(self).themes: combo_liststore.append([theme, theme]) theme_combo = builder.get_object('theme_combobox') renderer = Gtk.CellRendererText() theme_combo.pack_start(renderer, True) theme_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.THEME, theme_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) button_relief = builder.get_object('button_relief_checkbox') self.settings.bind(gs.PluginKey.BUTTON_RELIEF, button_relief, 'active', Gio.SettingsBindFlags.DEFAULT) # create user data files popup = RB.find_user_data_file('plugins/coverart_browser/img/usericons/popups.xml') if not os.path.isfile(popup): template = rb.find_plugin_file(self, 'template/popups.xml') folder = os.path.split(popup)[0] if not os.path.exists(folder): os.makedirs(folder) shutil.copyfile(template, popup) # now prepare the genre tab from coverart_utils import GenreConfiguredSpriteSheet from coverart_utils import get_stock_size from coverart_utils import GenreType self._sheet = GenreConfiguredSpriteSheet(self, "genre", get_stock_size()) self.alt_liststore = builder.get_object('alt_liststore') self.alt_user_liststore = builder.get_object('alt_user_liststore') self._iters = {} for key in self._sheet.keys(): store_iter = self.alt_liststore.append([key, self._sheet[key]]) self._iters[(key,self.GENRE_POPUP)] = store_iter for key, value in self._sheet.genre_alternate.iteritems(): if key.genre_type == GenreConfiguredSpriteSheet.GENRE_USER: store_iter = self.alt_user_liststore.append([key.name, self._sheet[self._sheet.genre_alternate[key]], self._sheet.genre_alternate[key]]) self._iters[(key.name, self.GENRE_LIST)] = store_iter self.amend_mode = False self.blank_iter = None self.genre_combobox = builder.get_object('genre_combobox') self.genre_entry = builder.get_object('genre_entry') self.genre_view = builder.get_object('genre_view') self.save_button = builder.get_object('save_button') self.filechooserdialog = builder.get_object('filechooserdialog') # return the dialog return builder.get_object('main_notebook') def rating_changed_callback(self, stars): print "rating_changed_callback" gs = GSetting() self.settings[gs.PluginKey.RATING] = self.stars.get_rating() def on_save_button_clicked(self, button): ''' action when genre edit area is saved ''' entry_value = self.genre_entry.get_text() treeiter = self.genre_combobox.get_active_iter() icon_value = self.alt_liststore[treeiter][0] # model 0 is the icon name, model 1 is the pixbuf if self.amend_mode: key = self._sheet.amend_genre_info(self.current_genre, entry_value, icon_value) self.alt_user_liststore[self._iters[(self.current_genre, self.GENRE_LIST)]][1]=self._sheet[self._sheet.genre_alternate[key]] self.alt_user_liststore[self._iters[(self.current_genre, self.GENRE_LIST)]][0]=key.name store_iter = self._iters[(self.current_genre, self.GENRE_LIST)] del self._iters[(self.current_genre, self.GENRE_LIST)] self._iters[(key.name, self.GENRE_LIST)] = store_iter else: self.amend_mode = True key = self._sheet.amend_genre_info('', entry_value, icon_value) self.current_genre = key.name store_iter = self.alt_user_liststore.append([key.name, self._sheet[self._sheet.genre_alternate[key]], self._sheet.genre_alternate[key]]) self._iters[(key.name, self.GENRE_LIST)] = store_iter selection = self.genre_view.get_selection() selection.select_iter(store_iter) self.save_button.set_sensitive(False) self._toggle_new_genre_state() def on_genre_filechooserbutton_file_set(self, filechooser): ''' action when genre new icon button is pressed ''' key = self._sheet.add_genre_icon( self.filechooserdialog.get_filename() ) store_iter = self.alt_liststore.append([key.name, self._sheet[key.name]]) self._iters[(key.name,self.GENRE_POPUP)] = store_iter def on_genre_view_selection_changed(self, view): ''' action when user selects a row in the list of genres ''' model, genre_iter = view.get_selected() if genre_iter: self.genre_entry.set_text(model[genre_iter][0]) index = model[genre_iter][2] if index != '': self.genre_combobox.set_active_iter(self._iters[(index, self.GENRE_POPUP)]) self.amend_mode = True self.current_genre=unicode(model[genre_iter][0], 'utf-8') else: self.genre_entry.set_text('') self.genre_combobox.set_active_iter(None) self.amend_mode = False if self.blank_iter and self.amend_mode: try: index = model[self.blank_iter][0] if index == '': model.remove(self.blank_iter) self.blank_iter = None except: self.blank_iter = None def on_add_button_clicked(self, button): ''' action when a new genre is added to the table ''' self.genre_entry.set_text('') self.genre_combobox.set_active(-1) self.amend_mode = False self.blank_iter = self.alt_user_liststore.append(['', None, '']) selection = self.genre_view.get_selection() selection.select_iter(self.blank_iter) def on_delete_button_clicked(self, button): ''' action when a genre is to be deleted ''' selection = self.genre_view.get_selection() model, genre_iter = selection.get_selected() if genre_iter: index = unicode(model[genre_iter][0],'utf-8') model.remove(genre_iter) if index: del self._iters[(index, self.GENRE_LIST)] self._sheet.delete_genre(index) self._toggle_new_genre_state() def set_save_sensitivity(self, _): ''' action to toggle the state of the save button depending upon the values entered in the genre edit fields ''' entry_value = self.genre_entry.get_text() treeiter = self.genre_combobox.get_active_iter() entry_value = unicode(entry_value, 'utf-8') enable = False try: test = self._iters[(entry_value, self.GENRE_LIST)] if RB.search_fold(self.current_genre) == RB.search_fold(entry_value): #if the current entry is the same then could save enable = True except: # reach here if this is a brand new entry enable = True if treeiter == None or entry_value == None or entry_value == "": # no icon chosen, or no entry value then nothing to save enable = False self.save_button.set_sensitive(enable) def _toggle_new_genre_state(self): ''' fire an event - uses gsettings and an object such as a controller connects to receive the signal that a new or amended genre has been made ''' gs = GSetting() test = self.settings[gs.PluginKey.NEW_GENRE_ICON] if test: test = False else: test = True self.settings[gs.PluginKey.NEW_GENRE_ICON]=test def on_show_launchpad_toggled(self, button): self.launchpad_label.set_visible(button.get_active())
class CoverArtBrowserSource(RB.Source): ''' Source utilized by the plugin to show all it's ui. ''' rating_threshold = GObject.property(type=float, default=0) artist_paned_pos = GObject.property(type=str) min_paned_pos = 40 # unique instance of the source instance = None def __init__(self, **kargs): ''' Initializes the source. ''' super(CoverArtBrowserSource, self).__init__(**kargs) # create source_source_settings and connect the source's properties self.gs = GSetting() self._connect_properties() self.hasActivated = False self.last_width = 0 self.last_selected_album = None self.click_count = 0 self.favourites = False self.task_progress = None def _connect_properties(self): ''' Connects the source properties to the saved preferences. ''' print("CoverArtBrowser DEBUG - _connect_properties") setting = self.gs.get_setting(self.gs.Path.PLUGIN) setting.bind(self.gs.PluginKey.RATING, self, 'rating_threshold', Gio.SettingsBindFlags.GET) print("CoverArtBrowser DEBUG - end _connect_properties") def do_get_status(self, *args): ''' Method called by Rhythmbox to figure out what to show on this source statusbar. If the custom statusbar is disabled, the source will show the selected album info. Also, it makes sure to show the progress on the album loading ''' try: # this will only work for RB3.0 and later if not self.task_progress: self.task_progress = RB.TaskProgressSimple.new() except: pass try: progress = self.album_manager.progress progress_text = _('Loading...') if progress < 1 else '' try: # this will only work for RB3.0 and later if progress < 1: if self.props.shell.props.task_list.get_model().n_items( ) == 0: self.props.shell.props.task_list.add_task( self.task_progress) self.task_progress.props.task_progress = progress self.task_progress.props.task_label = progress_text else: self.task_progress.props.task_outcome = RB.TaskOutcome.COMPLETE except: pass except: progress = 1 progress_text = '' try: # this will only work for RB3.0 and later self.task_progress.props.task_outcome = RB.TaskOutcome.COMPLETE except: pass return (self.status, progress_text, progress) def do_selected(self): ''' Called by Rhythmbox when the source is selected. It makes sure to create the ui the first time the source is showed. ''' print("CoverArtBrowser DEBUG - do_selected") # first time of activation -> add graphical stuff if not self.hasActivated: self.do_impl_activate() # indicate that the source was activated before self.hasActivated = True print("CoverArtBrowser DEBUG - end do_selected") def do_impl_activate(self): ''' Called by do_selected the first time the source is activated. It creates all the source ui and connects the necesary signals for it correct behavior. ''' print("CoverArtBrowser DEBUG - do_impl_activate") # initialise some variables self.plugin = self.props.plugin self.shell = self.props.shell self.status = '' self.search_text = '' self.actiongroup = ActionGroup(self.shell, 'coverplaylist_submenu') self._browser_preferences = None self._search_preferences = None # indicate that the source was activated before self.hasActivated = True self._create_ui() self._setup_source() self._apply_settings() print("CoverArtBrowser DEBUG - end do_impl_activate") def _create_ui(self): ''' Creates the ui for the source and saves the important widgets onto properties. ''' print("CoverArtBrowser DEBUG - _create_ui") # dialog has not been created so lets do so. cl = CoverLocale() ui = Gtk.Builder() ui.set_translation_domain(cl.Locale.LOCALE_DOMAIN) ui.add_from_file( rb.find_plugin_file(self.plugin, 'ui/coverart_browser.ui')) ui.connect_signals(self) # load the page and put it in the source self.page = ui.get_object('main_box') self.pack_start(self.page, True, True, 0) # get widgets for main icon-view self.status_label = ui.get_object('status_label') window = ui.get_object('scrolled_window') self.viewmgr = ViewManager(self, window) # get widgets for the artist paned self.artist_paned = ui.get_object('vertical_paned') Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 50, self._change_artist_paned_pos, self.viewmgr.view_name) self.viewmgr.connect('new-view', self.on_view_changed) self.artist_treeview = ui.get_object('artist_treeview') self.artist_scrolledwindow = ui.get_object('artist_scrolledwindow') # define menu's self.popup_menu = Menu(self.plugin, self.shell) self.popup_menu.load_from_file('ui/coverart_browser_pop_rb2.ui', 'ui/coverart_browser_pop_rb3.ui') self._external_plugins = None signals = \ { 'play_album_menu_item': self.play_album_menu_item_callback, 'queue_album_menu_item': self.queue_album_menu_item_callback, 'new_playlist': self.add_playlist_menu_item_callback, 'cover_search_menu_item': self.cover_search_menu_item_callback, 'export_embed_menu_item': self.export_embed_menu_item_callback, 'show_properties_menu_item': self.show_properties_menu_item_callback, 'play_similar_artist_menu_item': self.play_similar_artist_menu_item_callback} self.popup_menu.connect_signals(signals) self.popup_menu.connect('pre-popup', self.add_external_menu) self.status_label = ui.get_object('status_label') self.request_status_box = ui.get_object('request_status_box') self.request_spinner = ui.get_object('request_spinner') self.request_statusbar = ui.get_object('request_statusbar') self.request_cancel_button = ui.get_object('request_cancel_button') self.paned = ui.get_object('paned') self.notebook = ui.get_object('bottom_notebook') #---- set up info pane -----# info_scrolled_window = ui.get_object('info_scrolled_window') info_button_box = ui.get_object('info_button_box') artist_info_paned = ui.get_object('vertical_info_paned') self.artist_info = ArtistInfoPane(info_button_box, info_scrolled_window, artist_info_paned, self) # quick search self.quick_search = ui.get_object('quick_search_entry') print("CoverArtBrowser DEBUG - end _create_ui") def _setup_source(self): ''' Setups the differents parts of the source so they are ready to be used by the user. It also creates and configure some custom widgets. ''' print("CoverArtBrowser DEBUG - _setup_source") cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) # setup iconview popup self.viewmgr.current_view.set_popup_menu(self.popup_menu) # setup entry-view objects and widgets setting = self.gs.get_setting(self.gs.Path.PLUGIN) setting.bind(self.gs.PluginKey.PANED_POSITION, self.paned, 'collapsible-y', Gio.SettingsBindFlags.DEFAULT) setting.bind(self.gs.PluginKey.DISPLAY_BOTTOM, self.paned.get_child2(), 'visible', Gio.SettingsBindFlags.DEFAULT) # create entry view. Don't allow to reorder until the load is finished self.entry_view = EV(self.shell, self) self.entry_view.set_columns_clickable(False) self.shell.props.library_source.get_entry_view().set_columns_clickable( False) self.stars = ReactiveStar() self.stars.set_rating(0) a = Gtk.Alignment.new(0.5, 0.5, 0, 0) a.add(self.stars) self.stars.connect('changed', self.rating_changed_callback) vbox = Gtk.Box() vbox.set_orientation(Gtk.Orientation.VERTICAL) vbox.pack_start(self.entry_view, True, True, 0) vbox.pack_start(a, False, False, 1) vbox.show_all() self.notebook.append_page(vbox, Gtk.Label.new_with_mnemonic(_("Tracks"))) # create an album manager self.album_manager = AlbumManager(self.plugin, self.viewmgr.current_view) self.viewmgr.current_view.initialise(self) # setup cover search pane colour = self.viewmgr.get_selection_colour() self.cover_search_pane = CoverSearchPane(self.plugin, colour) self.notebook.append_page(self.cover_search_pane, Gtk.Label.new_with_mnemonic(_("Covers"))) # connect a signal to when the info of albums is ready self.load_fin_id = self.album_manager.loader.connect( 'model-load-finished', self.load_finished_callback) # prompt the loader to load the albums self.album_manager.loader.load_albums(self.props.base_query_model) # initialise the variables of the quick search self.quick_search_controller = AlbumQuickSearchController( self.album_manager) self.quick_search_controller.connect_quick_search(self.quick_search) # set sensitivity of export menu item for iconview self.popup_menu.set_sensitive( 'export_embed_menu_item', CoverArtExport(self.plugin, self.shell, self.album_manager).is_search_plugin_enabled()) # setup the statusbar component self.statusbar = Statusbar(self) # initialise the toolbar manager self.toolbar_manager = ToolbarManager(self.plugin, self.page, self.viewmgr) self.viewmgr.current_view.emit('update-toolbar') cl.switch_locale(cl.Locale.RB) # setup the artist paned artist_pview = None for view in self.shell.props.library_source.get_property_views(): print(view.props.title) print(_("Artist")) if view.props.title == _("Artist"): artist_pview = view break assert artist_pview, "cannot find artist property view" self.artist_treeview.set_model(artist_pview.get_model()) setting.bind(self.gs.PluginKey.ARTIST_PANED_POSITION, self, 'artist-paned-pos', Gio.SettingsBindFlags.DEFAULT) self.artist_paned.connect('button-release-event', self.artist_paned_button_release_callback) # intercept JumpToPlaying Song action so that we can scroll to the playing album appshell = rb3compat.ApplicationShell(self.shell) action = appshell.lookup_action("", "jump-to-playing", "win") action.action.connect("activate", self.jump_to_playing, None) self.echonest_similar_playlist = None print("CoverArtBrowser DEBUG - end _setup_source") def add_external_menu(self, *args): ''' Callback when the popup menu is about to be displayed ''' if not self._external_plugins: # initialise external plugin menu support self._external_plugins = \ CreateExternalPluginMenu("ca_covers_view", 7, self.popup_menu) self._external_plugins.create_menu('popup_menu', True) self.playlist_menu_item_callback() def jump_to_playing(self, *args): ''' Callback when the JumpToPlaying action is invoked This will scroll the view to the playing song ''' if not self.shell.props.selected_page.props.name == self.props.name: # if the source page that was played from is not the plugin then # nothing to do return album = None entry = self.shell.props.shell_player.get_playing_entry() if entry: album = self.album_manager.model.get_from_dbentry(entry) self.viewmgr.current_view.scroll_to_album(album) def artist_paned_button_release_callback(self, *args): ''' Callback when the artist paned handle is released from its mouse click. ''' child_width = self._get_child_width() paned_positions = eval(self.artist_paned_pos) found = None for viewpos in paned_positions: if self.viewmgr.view_name in viewpos: found = viewpos break if not found: return paned_positions.remove(found) if child_width <= self.min_paned_pos: child_width = 0 self.artist_paned.set_position(child_width) paned_positions.append(self.viewmgr.view_name + ":" + str(child_width)) self.artist_paned_pos = repr(paned_positions) def on_view_changed(self, widget, view_name): self._change_artist_paned_pos(view_name) def _change_artist_paned_pos(self, view_name): paned_positions = eval(self.artist_paned_pos) print(paned_positions) found = None for viewpos in paned_positions: if view_name in viewpos: found = viewpos break print(found) if not found: return child_width = int(found.split(":")[1]) print(child_width) # odd case - if the pane is not visible but the position is zero # then the paned position on visible=true is some large arbitary value # hence - set it to be 1 px larger than the real value, then set it back # to its expected value self.artist_paned.set_position(child_width + 1) self.artist_paned.set_visible(True) self.artist_paned.set_position(child_width) def _get_child_width(self): child = self.artist_paned.get_child1() return child.get_allocated_width() def on_artist_treeview_selection_changed(self, view): model, artist_iter = view.get_selected() if artist_iter: artist = model[artist_iter][0] cl = CoverLocale() cl.switch_locale(cl.Locale.RB) #. TRANSLATORS - "All" is used in the context of "All artist names" if artist == _('All'): self.album_manager.model.remove_filter('quick_artist') else: self.album_manager.model.replace_filter('quick_artist', artist) cl.switch_locale(cl.Locale.LOCALE_DOMAIN) def _apply_settings(self): ''' Applies all the settings related to the source and connects those that must be updated when the preferences dialog changes it's values. Also enables differents parts of the ui if the settings says so. ''' print("CoverArtBrowser DEBUG - _apply_settings") # connect some signals to the loader to keep the source informed self.album_mod_id = self.album_manager.model.connect( 'album-updated', self.on_album_updated) self.notify_prog_id = self.album_manager.connect( 'notify::progress', lambda *args: self.notify_status_changed()) print("CoverArtBrowser DEBUG - end _apply_settings") def load_finished_callback(self, _): ''' Callback called when the loader finishes loading albums into the covers view model. ''' print("CoverArtBrowser DEBUG - load_finished_callback") #if not self.request_status_box.get_visible(): # it should only be enabled if no cover request is going on #self.source_menu_search_all_item.set_sensitive(True) # enable sorting on the entryview self.entry_view.set_columns_clickable(True) self.shell.props.library_source.get_entry_view().set_columns_clickable( True) print("CoverArtBrowser DEBUG - end load_finished_callback") def get_entry_view(self): return self.entry_view def on_album_updated(self, model, path, tree_iter): ''' Callback called by the album loader when one of the albums managed by him gets modified in some way. ''' album = model.get_from_path(path) selected = self.viewmgr.current_view.get_selected_objects() if album in selected: # update the selection since it may have changed self.viewmgr.current_view.selectionchanged_callback() if album is selected[0] and \ self.notebook.get_current_page() == \ self.notebook.page_num(self.cover_search_pane): # also, if it's the first, update the cover search pane self.cover_search_pane.clear() self.cover_search_pane.do_search( album, self.album_manager.cover_man.update_cover) def play_similar_artist_menu_item_callback(self, *args): ''' Callback called when the play similar artist option is selected from the cover view popup. It plays similar artists music. ''' def play_similar_artist_menu_item_callback(self, *args): if not self.echonest_similar_playlist: self.echonest_similar_playlist = \ EchoNestPlaylist( self.shell, self.shell.props.queue_source) selected_albums = self.viewmgr.current_view.get_selected_objects() album = selected_albums[0] tracks = album.get_tracks() entry = tracks[0].entry self.echonest_similar_playlist.start(entry, reinitialise=True) def show_properties_menu_item_callback(self, *args): ''' Callback called when the show album properties option is selected from the cover view popup. It shows a SongInfo dialog showing the selected albums' entries info, which can be modified. ''' print("CoverArtBrowser DEBUG - show_properties_menu_item_callback") self.entry_view.select_all() info_dialog = RB.SongInfo(source=self, entry_view=self.entry_view) info_dialog.show_all() print("CoverArtBrowser DEBUG - end show_properties_menu_item_callback") def play_selected_album(self, favourites=False): ''' Utilitary method that plays all entries from an album into the play queue. ''' # callback when play an album print("CoverArtBrowser DEBUG - play_selected_album") query_model = RB.RhythmDBQueryModel.new_empty(self.shell.props.db) self.queue_selected_album(query_model, favourites) if len(query_model) > 0: self.props.query_model = query_model # Start the music player = self.shell.props.shell_player player.play_entry(query_model[0][0], self) print("CoverArtBrowser DEBUG - end play_selected_album") def queue_selected_album(self, source, favourites=False): ''' Utilitary method that queues all entries from an album into the play queue. ''' print("CoverArtBrowser DEBUG - queue_selected_album") selected_albums = self.viewmgr.current_view.get_selected_objects() threshold = self.rating_threshold if favourites else 0 total = 0 for album in selected_albums: # Retrieve and sort the entries of the album tracks = album.get_tracks(threshold) total = total + len(tracks) # Add the songs to the play queue for track in tracks: source.add_entry(track.entry, -1) if total == 0 and threshold: dialog = Gtk.MessageDialog( None, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("No tracks have been added because no tracks meet the favourite rating threshold" )) dialog.run() dialog.destroy() print("CoverArtBrowser DEBUG - end queue_select_album") def play_album_menu_item_callback(self, *args): ''' Callback called when the play album item from the cover view popup is selected. It cleans the play queue and queues the selected album. ''' print("CoverArtBrowser DEBUG - play_album_menu_item_callback") self.play_selected_album(self.favourites) print("CoverArtBrowser DEBUG - end play_album_menu_item_callback") def queue_album_menu_item_callback(self, *args): ''' Callback called when the queue album item from the cover view popup is selected. It queues the selected album at the end of the play queue. ''' print("CoverArtBrowser DEBUG - queue_album_menu_item_callback()") self.queue_selected_album(self.shell.props.queue_source, self.favourites) print("CoverArtBrowser DEBUG - end queue_album_menu_item_callback()") def playlist_menu_item_callback(self, *args): print("CoverArtBrowser DEBUG - playlist_menu_item_callback") self.playlist_fillmenu(self.popup_menu, 'playlist_submenu', 'playlist_section', self.actiongroup, self.add_to_static_playlist_menu_item_callback, self.favourites) def playlist_fillmenu(self, popup_menu, menubar, section_name, actiongroup, func, favourite=False): print("CoverArtBrowser DEBUG - playlist_fillmenu") playlist_manager = self.shell.props.playlist_manager playlists_entries = playlist_manager.get_playlists() # tidy up old playlists menu items before recreating the list actiongroup.remove_actions() popup_menu.remove_menu_items(menubar, section_name) if playlists_entries: for playlist in playlists_entries: if playlist.props.is_local and \ isinstance(playlist, RB.StaticPlaylistSource): args = (playlist, favourite) # take the name of the playlist, strip out non-english characters and reduce the string # to just a-to-z characters i.e. this will make the action_name valid in RB3 ascii_name = unicodedata.normalize('NFKD', \ rb3compat.unicodestr(playlist.props.name, 'utf-8')).encode('ascii','ignore') ascii_name = ascii_name.decode(encoding='UTF-8') ascii_name = re.sub(r'[^a-zA-Z]', '', ascii_name) action = actiongroup.add_action(func=func, action_name=ascii_name, playlist=playlist, favourite=favourite, label=playlist.props.name) popup_menu.add_menu_item(menubar, section_name, action) def add_to_static_playlist_menu_item_callback(self, action, param, args): print('''CoverArtBrowser DEBUG - add_to_static_playlist_menu_item_callback''') playlist = args['playlist'] favourite = args['favourite'] self.queue_selected_album(playlist, favourite) def add_playlist_menu_item_callback(self, *args): print('''CoverArtBrowser DEBUG - add_playlist_menu_item_callback''') playlist_manager = self.shell.props.playlist_manager playlist = playlist_manager.new_playlist(_('New Playlist'), False) self.queue_selected_album(playlist, self.favourites) def play_random_album_menu_item_callback(self, favourites=False): print( '''CoverArtBrowser DEBUG - play_random_album_menu_item_callback''') query_model = RB.RhythmDBQueryModel.new_empty(self.shell.props.db) num_albums = len(self.album_manager.model.store) #random_list = [] selected_albums = [] gs = GSetting() settings = gs.get_setting(gs.Path.PLUGIN) to_queue = settings[gs.PluginKey.RANDOM] if num_albums <= to_queue: dialog = Gtk.MessageDialog( None, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("The number of albums to randomly play is less than that displayed." )) dialog.run() dialog.destroy() return album_col = self.album_manager.model.columns['album'] chosen = {} # now loop through finding unique random albums # i.e. ensure we dont queue the same album twice for loop in range(0, to_queue): while True: pos = random.randint(0, num_albums - 1) if pos not in chosen: chosen[pos] = None selected_albums.append( self.album_manager.model.store[pos][album_col]) break threshold = self.rating_threshold if favourites else 0 total = 0 for album in selected_albums: # Retrieve and sort the entries of the album tracks = album.get_tracks(threshold) total = total + len(tracks) # Add the songs to the play queue for track in tracks: query_model.add_entry(track.entry, -1) if total == 0 and threshold: dialog = Gtk.MessageDialog( None, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("No tracks have been added because no tracks meet the favourite rating threshold" )) dialog.run() dialog.destroy() self.props.query_model = query_model # Start the music player = self.shell.props.shell_player player.play_entry(query_model[0][0], self) print("CoverArtBrowser DEBUG - end play_selected_album") def cover_search_menu_item_callback(self, *args): ''' Callback called when the search cover option is selected from the cover view popup. It prompts the album loader to retrieve the selected album cover ''' print("CoverArtBrowser DEBUG - cover_search_menu_item_callback()") selected_albums = self.viewmgr.current_view.get_selected_objects() self.request_status_box.show_all() self.album_manager.cover_man.search_covers( selected_albums, self.update_request_status_bar) print("CoverArtBrowser DEBUG - end cover_search_menu_item_callback()") def export_embed_menu_item_callback(self, *args): ''' Callback called when the export and embed coverart option is selected from the cover view popup. It prompts the exporter to copy and embed art for the albums chosen ''' print("CoverArtBrowser DEBUG - export_embed_menu_item_callback()") selected_albums = self.viewmgr.current_view.get_selected_objects() CoverArtExport(self.plugin, self.shell, self.album_manager).embed_albums(selected_albums) print("CoverArtBrowser DEBUG - export_embed_menu_item_callback()") def update_request_status_bar(self, coverobject): ''' Callback called by the album loader starts performing a new cover request. It prompts the source to change the content of the request statusbar. ''' print("CoverArtBrowser DEBUG - update_request_status_bar") if coverobject: # for example "Requesting the picture cover for the music artist Michael Jackson" tranlation_string = _('Requesting cover for %s...') self.request_statusbar.set_text( rb3compat.unicodedecode( _('Requesting cover for %s...') % (coverobject.name), 'UTF-8')) else: self.request_status_box.hide() self.popup_menu.set_sensitive('cover_search_menu_item', True) self.request_cancel_button.set_sensitive(True) print("CoverArtBrowser DEBUG - end update_request_status_bar") def cancel_request_callback(self, _): ''' Callback connected to the cancel button on the request statusbar. When called, it prompts the album loader to cancel the full cover search after the current cover. ''' print("CoverArtBrowser DEBUG - cancel_request_callback") self.request_cancel_button.set_sensitive(False) self._cover_search_manager.cover_man.cancel_cover_request() print("CoverArtBrowser DEBUG - end cancel_request_callback") def notebook_switch_page_callback(self, notebook, page, page_num): ''' Callback called when the notebook page gets switched. It initiates the cover search when the cover search pane's page is selected. ''' print("CoverArtBrowser DEBUG - notebook_switch_page_callback") if page_num == 1: self.viewmgr.current_view.switch_to_coverpane( self.cover_search_pane) print("CoverArtBrowser DEBUG - end notebook_switch_page_callback") def rating_changed_callback(self, widget): ''' Callback called when the Rating stars is changed ''' print("CoverArtBrowser DEBUG - rating_changed_callback") rating = widget.get_rating() for album in self.viewmgr.current_view.get_selected_objects(): album.rating = rating print("CoverArtBrowser DEBUG - end rating_changed_callback") def show_hide_pane(self, params): ''' helper function - if the entry is manually expanded then if necessary scroll the view to the last selected album params is "album" or a tuple of "album" and "force_expand" boolean ''' if isinstance(params, tuple): album, force = params else: album = params force = PanedCollapsible.Paned.DEFAULT if (album and self.click_count == 1 \ and self.last_selected_album is album) or force != PanedCollapsible.Paned.DEFAULT: # check if it's a second or third click on the album and expand # or collapse the entry view accordingly self.paned.expand(force) # update the selected album selected = self.viewmgr.current_view.get_selected_objects() self.last_selected_album = selected[0] if len(selected) == 1 else None # clear the click count self.click_count = 0 def update_with_selection(self): ''' Update the source view when an item gets selected. ''' selected = self.viewmgr.current_view.get_selected_objects() # clear the entry view self.entry_view.clear() cover_search_pane_visible = self.notebook.get_current_page() == \ self.notebook.page_num(self.cover_search_pane) if not selected: # clean cover tab if selected if cover_search_pane_visible: self.cover_search_pane.clear() return elif len(selected) == 1: self.stars.set_rating(selected[0].rating) if selected[0] is not self.last_selected_album: # when the selection changes we've to take into account two # things if not self.click_count: # we may be using the arrows, so if there is no mouse # involved, we should change the last selected self.last_selected_album = selected[0] else: # we may've doing a fast change after a valid second click, # so it shouldn't be considered a double click self.click_count -= 1 else: self.stars.set_rating(0) if len(selected) == 1: self.artist_info.emit('selected', selected[0].artist, selected[0].name) for album in selected: # add the album to the entry_view self.entry_view.add_album(album) # update the cover search pane with the first selected album if cover_search_pane_visible: self.cover_search_pane.do_search( selected[0], self.album_manager.cover_man.update_cover) self.statusbar.emit('display-status', self.viewmgr.current_view) def propertiesbutton_callback(self, choice): if choice == 'download': self.request_status_box.show_all() self._cover_search_manager = self.viewmgr.current_view.get_default_manager( ) self._cover_search_manager.cover_man.search_covers( callback=self.update_request_status_bar) elif choice == 'random': self.play_random_album_menu_item_callback() elif choice == 'random favourite': self.play_random_album_menu_item_callback(True) elif choice == 'favourite': self.favourites = not self.favourites self.viewmgr.current_view.set_popup_menu(self.popup_menu) elif choice == 'browser prefs': if not self._browser_preferences: self._browser_preferences = Preferences() self._browser_preferences.display_preferences_dialog(self.plugin) elif choice == 'search prefs': try: if not self._search_preferences: from gi.repository import Peas peas = Peas.Engine.get_default() plugin_info = peas.get_plugin_info( 'coverart_search_providers') module_name = plugin_info.get_module_name() mod = __import__(module_name) sp = getattr(mod, "SearchPreferences") self._search_preferences = sp() self._search_preferences.plugin_info = plugin_info self._search_preferences.display_preferences_dialog( self._search_preferences) except: dialog = Gtk.MessageDialog( None, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("Please install and activate the latest version of the Coverart Search Providers plugin" )) dialog.run() dialog.destroy() else: assert 1 == 2, ("unknown choice %s", choice) @classmethod def get_instance(cls, **kwargs): ''' Returns the unique instance of the manager. ''' if not cls.instance: cls.instance = CoverArtBrowserSource(**kwargs) return cls.instance
def _setup_source(self): ''' Setups the differents parts of the source so they are ready to be used by the user. It also creates and configure some custom widgets. ''' print("CoverArtBrowser DEBUG - _setup_source") cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) # setup iconview popup self.viewmgr.current_view.set_popup_menu(self.popup_menu) # setup entry-view objects and widgets setting = self.gs.get_setting(self.gs.Path.PLUGIN) setting.bind(self.gs.PluginKey.PANED_POSITION, self.paned, 'collapsible-y', Gio.SettingsBindFlags.DEFAULT) setting.bind(self.gs.PluginKey.DISPLAY_BOTTOM, self.paned.get_child2(), 'visible', Gio.SettingsBindFlags.DEFAULT) # create entry view. Don't allow to reorder until the load is finished self.entry_view = EV(self.shell, self) self.entry_view.set_columns_clickable(False) self.shell.props.library_source.get_entry_view().set_columns_clickable( False) self.stars = ReactiveStar() self.stars.set_rating(0) a = Gtk.Alignment.new(0.5, 0.5, 0, 0) a.add(self.stars) self.stars.connect('changed', self.rating_changed_callback) vbox = Gtk.Box() vbox.set_orientation(Gtk.Orientation.VERTICAL) vbox.pack_start(self.entry_view, True, True, 0) vbox.pack_start(a, False, False, 1) vbox.show_all() self.notebook.append_page(vbox, Gtk.Label.new_with_mnemonic(_("Tracks"))) # create an album manager self.album_manager = AlbumManager(self.plugin, self.viewmgr.current_view) self.viewmgr.current_view.initialise(self) # setup cover search pane colour = self.viewmgr.get_selection_colour() self.cover_search_pane = CoverSearchPane(self.plugin, colour) self.notebook.append_page(self.cover_search_pane, Gtk.Label.new_with_mnemonic(_("Covers"))) # connect a signal to when the info of albums is ready self.load_fin_id = self.album_manager.loader.connect( 'model-load-finished', self.load_finished_callback) # prompt the loader to load the albums self.album_manager.loader.load_albums(self.props.base_query_model) # initialise the variables of the quick search self.quick_search_controller = AlbumQuickSearchController( self.album_manager) self.quick_search_controller.connect_quick_search(self.quick_search) # set sensitivity of export menu item for iconview self.popup_menu.set_sensitive( 'export_embed_menu_item', CoverArtExport(self.plugin, self.shell, self.album_manager).is_search_plugin_enabled()) # setup the statusbar component self.statusbar = Statusbar(self) # initialise the toolbar manager self.toolbar_manager = ToolbarManager(self.plugin, self.page, self.viewmgr) self.viewmgr.current_view.emit('update-toolbar') cl.switch_locale(cl.Locale.RB) # setup the artist paned artist_pview = None for view in self.shell.props.library_source.get_property_views(): print(view.props.title) print(_("Artist")) if view.props.title == _("Artist"): artist_pview = view break assert artist_pview, "cannot find artist property view" self.artist_treeview.set_model(artist_pview.get_model()) setting.bind(self.gs.PluginKey.ARTIST_PANED_POSITION, self, 'artist-paned-pos', Gio.SettingsBindFlags.DEFAULT) self.artist_paned.connect('button-release-event', self.artist_paned_button_release_callback) # intercept JumpToPlaying Song action so that we can scroll to the playing album appshell = rb3compat.ApplicationShell(self.shell) action = appshell.lookup_action("", "jump-to-playing", "win") action.action.connect("activate", self.jump_to_playing, None) self.echonest_similar_playlist = None print("CoverArtBrowser DEBUG - end _setup_source")
class Preferences(GObject.Object, PeasGtk.Configurable): ''' Preferences for the CoverArt Browser Plugins. It holds the settings for the plugin and also is the responsible of creating the preferences dialog. ''' __gtype_name__ = 'CoverArtBrowserPreferences' object = GObject.property(type=GObject.Object) GENRE_POPUP = 1 GENRE_LIST = 2 def __init__(self): ''' Initialises the preferences, getting an instance of the settings saved by Gio. ''' GObject.Object.__init__(self) gs = GSetting() self.settings = gs.get_setting(gs.Path.PLUGIN) def do_create_configure_widget(self): ''' Creates the plugin's preferences dialog ''' # create the ui cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) builder = Gtk.Builder() builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN) builder.add_from_file(rb.find_plugin_file(self, 'ui/coverart_browser_prefs.ui')) self.launchpad_button = builder.get_object('show_launchpad') self.launchpad_label = builder.get_object('launchpad_label') builder.connect_signals(self) #. TRANSLATORS: Do not translate this string. translators = _('translator-credits') if translators != "translator-credits": self.launchpad_label.set_text(translators) else: self.launchpad_button.set_visible(False) gs = GSetting() # bind the toggles to the settings toggle_statusbar = builder.get_object('custom_statusbar_checkbox') self.settings.bind(gs.PluginKey.CUSTOM_STATUSBAR, toggle_statusbar, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_bottom = builder.get_object('display_bottom_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_BOTTOM, toggle_bottom, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_text = builder.get_object('display_text_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, toggle_text, 'active', Gio.SettingsBindFlags.DEFAULT) box_text = builder.get_object('display_text_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, box_text, 'sensitive', Gio.SettingsBindFlags.GET) toggle_text_ellipsize = builder.get_object( 'display_text_ellipsize_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, toggle_text_ellipsize, 'active', Gio.SettingsBindFlags.DEFAULT) box_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, box_text_ellipsize_length, 'sensitive', Gio.SettingsBindFlags.GET) spinner_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_spin') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE_LENGTH, spinner_text_ellipsize_length, 'value', Gio.SettingsBindFlags.DEFAULT) spinner_font_size = builder.get_object( 'display_font_spin') self.settings.bind(gs.PluginKey.DISPLAY_FONT_SIZE, spinner_font_size, 'value', Gio.SettingsBindFlags.DEFAULT) cover_size_scale = builder.get_object('cover_size_adjustment') self.settings.bind(gs.PluginKey.COVER_SIZE, cover_size_scale, 'value', Gio.SettingsBindFlags.DEFAULT) add_shadow = builder.get_object('add_shadow_checkbox') self.settings.bind(gs.PluginKey.ADD_SHADOW, add_shadow, 'active', Gio.SettingsBindFlags.DEFAULT) rated_box = builder.get_object('rated_box') self.stars = ReactiveStar(size=StarSize.BIG) self.stars.connect('changed', self.rating_changed_callback) align = Gtk.Alignment.new(0.5, 0, 0, 0.1) align.add(self.stars) rated_box.add(align) self.stars.set_rating(self.settings[gs.PluginKey.RATING]) autostart = builder.get_object('autostart_checkbox') self.settings.bind(gs.PluginKey.AUTOSTART, autostart, 'active', Gio.SettingsBindFlags.DEFAULT) toolbar_pos_combo = builder.get_object('show_in_combobox') renderer = Gtk.CellRendererText() toolbar_pos_combo.pack_start(renderer, True) toolbar_pos_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.TOOLBAR_POS, toolbar_pos_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) light_source_combo = builder.get_object('light_source_combobox') renderer = Gtk.CellRendererText() light_source_combo.pack_start(renderer, True) light_source_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.SHADOW_IMAGE, light_source_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) combo_liststore = builder.get_object('combo_liststore') from coverart_utils import Theme for theme in Theme(self).themes: combo_liststore.append([theme, theme]) theme_combo = builder.get_object('theme_combobox') renderer = Gtk.CellRendererText() theme_combo.pack_start(renderer, True) theme_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.THEME, theme_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) button_relief = builder.get_object('button_relief_checkbox') self.settings.bind(gs.PluginKey.BUTTON_RELIEF, button_relief, 'active', Gio.SettingsBindFlags.DEFAULT) # create user data files popup = RB.find_user_data_file('plugins/coverart_browser/img/usericons/popups.xml') if not os.path.isfile(popup): template = rb.find_plugin_file(self, 'template/popups.xml') folder = os.path.split(popup)[0] if not os.path.exists(folder): os.makedirs(folder) shutil.copyfile(template, popup) # now prepare the genre tab from coverart_utils import GenreConfiguredSpriteSheet from coverart_utils import get_stock_size from coverart_utils import GenreType self._sheet = GenreConfiguredSpriteSheet(self, "genre", get_stock_size()) self.alt_liststore = builder.get_object('alt_liststore') self.alt_user_liststore = builder.get_object('alt_user_liststore') self._iters = {} for key in list(self._sheet.keys()): store_iter = self.alt_liststore.append([key, self._sheet[key]]) self._iters[(key,self.GENRE_POPUP)] = store_iter for key, value in self._sheet.genre_alternate.items(): if key.genre_type == GenreConfiguredSpriteSheet.GENRE_USER: store_iter = self.alt_user_liststore.append([key.name, self._sheet[self._sheet.genre_alternate[key]], self._sheet.genre_alternate[key]]) self._iters[(key.name, self.GENRE_LIST)] = store_iter self.amend_mode = False self.blank_iter = None self.genre_combobox = builder.get_object('genre_combobox') self.genre_entry = builder.get_object('genre_entry') self.genre_view = builder.get_object('genre_view') self.save_button = builder.get_object('save_button') self.filechooserdialog = builder.get_object('filechooserdialog') padding_scale = builder.get_object('padding_adjustment') self.settings.bind(gs.PluginKey.ICON_PADDING, padding_scale, 'value', Gio.SettingsBindFlags.DEFAULT) spacing_scale = builder.get_object('spacing_adjustment') self.settings.bind(gs.PluginKey.ICON_SPACING, spacing_scale, 'value', Gio.SettingsBindFlags.DEFAULT) # return the dialog return builder.get_object('main_notebook') def rating_changed_callback(self, stars): print("rating_changed_callback") gs = GSetting() self.settings[gs.PluginKey.RATING] = self.stars.get_rating() def on_save_button_clicked(self, button): ''' action when genre edit area is saved ''' entry_value = self.genre_entry.get_text() treeiter = self.genre_combobox.get_active_iter() icon_value = self.alt_liststore[treeiter][0] # model 0 is the icon name, model 1 is the pixbuf if self.amend_mode: key = self._sheet.amend_genre_info(self.current_genre, entry_value, icon_value) self.alt_user_liststore[self._iters[(self.current_genre, self.GENRE_LIST)]][1]=self._sheet[self._sheet.genre_alternate[key]] self.alt_user_liststore[self._iters[(self.current_genre, self.GENRE_LIST)]][0]=key.name store_iter = self._iters[(self.current_genre, self.GENRE_LIST)] del self._iters[(self.current_genre, self.GENRE_LIST)] self._iters[(key.name, self.GENRE_LIST)] = store_iter else: self.amend_mode = True key = self._sheet.amend_genre_info('', entry_value, icon_value) self.current_genre = key.name store_iter = self.alt_user_liststore.append([key.name, self._sheet[self._sheet.genre_alternate[key]], self._sheet.genre_alternate[key]]) self._iters[(key.name, self.GENRE_LIST)] = store_iter selection = self.genre_view.get_selection() selection.select_iter(store_iter) self.save_button.set_sensitive(False) self._toggle_new_genre_state() def on_genre_filechooserbutton_file_set(self, filechooser): ''' action when genre new icon button is pressed ''' key = self._sheet.add_genre_icon( self.filechooserdialog.get_filename() ) store_iter = self.alt_liststore.append([key.name, self._sheet[key.name]]) self._iters[(key.name,self.GENRE_POPUP)] = store_iter def on_genre_view_selection_changed(self, view): ''' action when user selects a row in the list of genres ''' model, genre_iter = view.get_selected() if genre_iter: self.genre_entry.set_text(model[genre_iter][0]) index = model[genre_iter][2] if index != '': self.genre_combobox.set_active_iter(self._iters[(index, self.GENRE_POPUP)]) self.amend_mode = True self.current_genre=rb3compat.unicodestr(model[genre_iter][0], 'utf-8') else: self.genre_entry.set_text('') self.genre_combobox.set_active_iter(None) self.amend_mode = False if self.blank_iter and self.amend_mode: try: index = model[self.blank_iter][0] if index == '': model.remove(self.blank_iter) self.blank_iter = None except: self.blank_iter = None def on_add_button_clicked(self, button): ''' action when a new genre is added to the table ''' self.genre_entry.set_text('') self.genre_combobox.set_active(-1) self.amend_mode = False self.blank_iter = self.alt_user_liststore.append(['', None, '']) selection = self.genre_view.get_selection() selection.select_iter(self.blank_iter) def on_delete_button_clicked(self, button): ''' action when a genre is to be deleted ''' selection = self.genre_view.get_selection() model, genre_iter = selection.get_selected() if genre_iter: index = rb3compat.unicodestr(model[genre_iter][0],'utf-8') model.remove(genre_iter) if index: del self._iters[(index, self.GENRE_LIST)] self._sheet.delete_genre(index) self._toggle_new_genre_state() def set_save_sensitivity(self, _): ''' action to toggle the state of the save button depending upon the values entered in the genre edit fields ''' entry_value = self.genre_entry.get_text() treeiter = self.genre_combobox.get_active_iter() entry_value = rb3compat.unicodestr(entry_value, 'utf-8') enable = False try: test = self._iters[(entry_value, self.GENRE_LIST)] if RB.search_fold(self.current_genre) == RB.search_fold(entry_value): #if the current entry is the same then could save enable = True except: # reach here if this is a brand new entry enable = True if treeiter == None or entry_value == None or entry_value == "": # no icon chosen, or no entry value then nothing to save enable = False self.save_button.set_sensitive(enable) def _toggle_new_genre_state(self): ''' fire an event - uses gsettings and an object such as a controller connects to receive the signal that a new or amended genre has been made ''' gs = GSetting() test = self.settings[gs.PluginKey.NEW_GENRE_ICON] if test: test = False else: test = True self.settings[gs.PluginKey.NEW_GENRE_ICON]=test def on_show_launchpad_toggled(self, button): self.launchpad_label.set_visible(button.get_active())
class Preferences(GObject.Object, PeasGtk.Configurable): ''' Preferences for the CoverArt Browser Plugins. It holds the settings for the plugin and also is the responsible of creating the preferences dialog. ''' __gtype_name__ = 'CoverArtBrowserPreferences' object = GObject.property(type=GObject.Object) GENRE_POPUP = 1 GENRE_LIST = 2 def __init__(self): ''' Initialises the preferences, getting an instance of the settings saved by Gio. ''' GObject.Object.__init__(self) gs = GSetting() self.settings = gs.get_setting(gs.Path.PLUGIN) self._first_run = True self._cover_size = 0 self._cover_size_delay = 0 def do_create_configure_widget(self): ''' Creates the plugin's preferences dialog ''' return self._create_display_contents(self) def display_preferences_dialog(self, plugin): print("DEBUG - display_preferences_dialog") if self._first_run: self._first_run = False cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) self._dialog = Gtk.Dialog(modal=True, destroy_with_parent=True) self._dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) self._dialog.set_title(_('Browser Preferences')) content_area = self._dialog.get_content_area() content_area.pack_start(self._create_display_contents(plugin), True, True, 0) helpbutton = self._dialog.add_button(Gtk.STOCK_HELP, Gtk.ResponseType.HELP) helpbutton.connect('clicked', self._display_help) self._dialog.show_all() print("shown") while True: response = self._dialog.run() print("and run") if response != Gtk.ResponseType.HELP: break self._dialog.hide() print("DEBUG - display_preferences_dialog end") def _display_help(self, *args): peas = Peas.Engine.get_default() uri = peas.get_plugin_info('coverart_browser').get_help_uri() webbrowser.open(uri) def _create_display_contents(self, plugin): print("DEBUG - create_display_contents") # create the ui self._first_run = True cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) builder = Gtk.Builder() builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN) builder.add_from_file( rb.find_plugin_file(plugin, 'ui/coverart_browser_prefs.ui')) self.launchpad_button = builder.get_object('show_launchpad') self.launchpad_label = builder.get_object('launchpad_label') builder.connect_signals(self) # . TRANSLATORS: Do not translate this string. translators = _('translator-credits') if translators != "translator-credits": self.launchpad_label.set_text(translators) else: self.launchpad_button.set_visible(False) gs = GSetting() # bind the toggles to the settings toggle_statusbar = builder.get_object('custom_statusbar_checkbox') self.settings.bind(gs.PluginKey.CUSTOM_STATUSBAR, toggle_statusbar, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_text = builder.get_object('display_text_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, toggle_text, 'active', Gio.SettingsBindFlags.DEFAULT) box_text = builder.get_object('display_text_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, box_text, 'sensitive', Gio.SettingsBindFlags.GET) self.display_text_pos = self.settings[gs.PluginKey.DISPLAY_TEXT_POS] self.display_text_under_radiobutton = builder.get_object( 'display_text_under_radiobutton') self.display_text_within_radiobutton = builder.get_object( 'display_text_within_radiobutton') if self.display_text_pos: self.display_text_under_radiobutton.set_active(True) else: self.display_text_within_radiobutton.set_active(True) random_scale = builder.get_object('random_adjustment') self.settings.bind(gs.PluginKey.RANDOM, random_scale, 'value', Gio.SettingsBindFlags.DEFAULT) toggle_text_ellipsize = builder.get_object( 'display_text_ellipsize_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, toggle_text_ellipsize, 'active', Gio.SettingsBindFlags.DEFAULT) box_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, box_text_ellipsize_length, 'sensitive', Gio.SettingsBindFlags.GET) spinner_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_spin') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE_LENGTH, spinner_text_ellipsize_length, 'value', Gio.SettingsBindFlags.DEFAULT) spinner_font_size = builder.get_object('display_font_spin') self.settings.bind(gs.PluginKey.DISPLAY_FONT_SIZE, spinner_font_size, 'value', Gio.SettingsBindFlags.DEFAULT) cover_size_scale = builder.get_object('cover_size_adjustment') #self.settings.bind(gs.PluginKey.COVER_SIZE, cover_size_scale, 'value', # Gio.SettingsBindFlags.DEFAULT) self._cover_size = self.settings[gs.PluginKey.COVER_SIZE] cover_size_scale.set_value(self._cover_size) cover_size_scale.connect('value-changed', self.on_cover_size_scale_changed) add_shadow = builder.get_object('add_shadow_checkbox') self.settings.bind(gs.PluginKey.ADD_SHADOW, add_shadow, 'active', Gio.SettingsBindFlags.DEFAULT) rated_box = builder.get_object('rated_box') self.stars = ReactiveStar(size=StarSize.BIG) self.stars.connect('changed', self.rating_changed_callback) align = Gtk.Alignment.new(0.5, 0, 0, 0.1) align.add(self.stars) rated_box.add(align) self.stars.set_rating(self.settings[gs.PluginKey.RATING]) autostart = builder.get_object('autostart_checkbox') self.settings.bind(gs.PluginKey.AUTOSTART, autostart, 'active', Gio.SettingsBindFlags.DEFAULT) toolbar_pos_combo = builder.get_object('show_in_combobox') renderer = Gtk.CellRendererText() toolbar_pos_combo.pack_start(renderer, True) toolbar_pos_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.TOOLBAR_POS, toolbar_pos_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) light_source_combo = builder.get_object('light_source_combobox') renderer = Gtk.CellRendererText() light_source_combo.pack_start(renderer, True) light_source_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.SHADOW_IMAGE, light_source_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) combo_liststore = builder.get_object('combo_liststore') from coverart_utils import Theme for theme in Theme(self).themes: combo_liststore.append([theme, theme]) theme_combo = builder.get_object('theme_combobox') renderer = Gtk.CellRendererText() theme_combo.pack_start(renderer, True) theme_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.THEME, theme_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) button_relief = builder.get_object('button_relief_checkbox') self.settings.bind(gs.PluginKey.BUTTON_RELIEF, button_relief, 'active', Gio.SettingsBindFlags.DEFAULT) # create user data files cachedir = RB.user_cache_dir() + "/coverart_browser/usericons" if not os.path.exists(cachedir): os.makedirs(cachedir) popup = cachedir + "/popups.xml" temp = RB.find_user_data_file( 'plugins/coverart_browser/img/usericons/popups.xml') # lets see if there is a legacy file - if necessary copy it to the cache dir if os.path.isfile(temp) and not os.path.isfile(popup): shutil.copyfile(temp, popup) if not os.path.isfile(popup): template = rb.find_plugin_file(plugin, 'template/popups.xml') folder = os.path.split(popup)[0] if not os.path.exists(folder): os.makedirs(folder) shutil.copyfile(template, popup) # now prepare the genre tab from coverart_utils import GenreConfiguredSpriteSheet from coverart_utils import get_stock_size self._sheet = GenreConfiguredSpriteSheet(plugin, "genre", get_stock_size()) self.alt_liststore = builder.get_object('alt_liststore') self.alt_user_liststore = builder.get_object('alt_user_liststore') self._iters = {} for key in list(self._sheet.keys()): store_iter = self.alt_liststore.append([key, self._sheet[key]]) self._iters[(key, self.GENRE_POPUP)] = store_iter for key, value in self._sheet.genre_alternate.items(): if key.genre_type == GenreConfiguredSpriteSheet.GENRE_USER: store_iter = self.alt_user_liststore.append([ key.name, self._sheet[self._sheet.genre_alternate[key]], self._sheet.genre_alternate[key] ]) self._iters[(key.name, self.GENRE_LIST)] = store_iter self.amend_mode = False self.blank_iter = None self.genre_combobox = builder.get_object('genre_combobox') self.genre_entry = builder.get_object('genre_entry') self.genre_view = builder.get_object('genre_view') self.save_button = builder.get_object('save_button') self.filechooserdialog = builder.get_object('filechooserdialog') last_genre_folder = self.settings[gs.PluginKey.LAST_GENRE_FOLDER] if last_genre_folder != "": self.filechooserdialog.set_current_folder(last_genre_folder) padding_scale = builder.get_object('padding_adjustment') self.settings.bind(gs.PluginKey.ICON_PADDING, padding_scale, 'value', Gio.SettingsBindFlags.DEFAULT) spacing_scale = builder.get_object('spacing_adjustment') self.settings.bind(gs.PluginKey.ICON_SPACING, spacing_scale, 'value', Gio.SettingsBindFlags.DEFAULT) icon_automatic = builder.get_object('icon_automatic_checkbox') self.settings.bind(gs.PluginKey.ICON_AUTOMATIC, icon_automatic, 'active', Gio.SettingsBindFlags.DEFAULT) #flow tab flow_combo = builder.get_object('flow_combobox') renderer = Gtk.CellRendererText() flow_combo.pack_start(renderer, True) flow_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.FLOW_APPEARANCE, flow_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) flow_hide = builder.get_object('hide_caption_checkbox') self.settings.bind(gs.PluginKey.FLOW_HIDE_CAPTION, flow_hide, 'active', Gio.SettingsBindFlags.DEFAULT) flow_scale = builder.get_object('cover_scale_adjustment') self.settings.bind(gs.PluginKey.FLOW_SCALE, flow_scale, 'value', Gio.SettingsBindFlags.DEFAULT) flow_width = builder.get_object('cover_width_adjustment') self.settings.bind(gs.PluginKey.FLOW_WIDTH, flow_width, 'value', Gio.SettingsBindFlags.DEFAULT) flow_max = builder.get_object('flow_max_adjustment') self.settings.bind(gs.PluginKey.FLOW_MAX, flow_max, 'value', Gio.SettingsBindFlags.DEFAULT) flow_automatic = builder.get_object('automatic_checkbox') self.settings.bind(gs.PluginKey.FLOW_AUTOMATIC, flow_automatic, 'active', Gio.SettingsBindFlags.DEFAULT) self.background_colour = self.settings[ gs.PluginKey.FLOW_BACKGROUND_COLOUR] self.white_radiobutton = builder.get_object('white_radiobutton') self.black_radiobutton = builder.get_object('black_radiobutton') if self.background_colour == 'W': self.white_radiobutton.set_active(True) else: self.black_radiobutton.set_active(True) self.text_alignment = self.settings[gs.PluginKey.TEXT_ALIGNMENT] self.text_alignment_left_radiobutton = builder.get_object( 'left_alignment_radiobutton') self.text_alignment_centre_radiobutton = builder.get_object( 'centre_alignment_radiobutton') self.text_alignment_right_radiobutton = builder.get_object( 'right_alignment_radiobutton') if self.text_alignment == 0: self.text_alignment_left_radiobutton.set_active(True) elif self.text_alignment == 1: self.text_alignment_centre_radiobutton.set_active(True) else: self.text_alignment_right_radiobutton.set_active(True) # return the dialog self._first_run = False print("end create dialog contents") return builder.get_object('main_notebook') def on_cover_size_scale_changed(self, scale): self._cover_size = scale.get_value() def delay(*args): print('delay') print(self._cover_size_delay) self._cover_size_delay = self._cover_size_delay + 1 if self._cover_size_delay >= 8: gs = GSetting() self.settings[gs.PluginKey.COVER_SIZE] = self._cover_size self._cover_size_delay = 0 return False return True if self._cover_size_delay == 0: Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 100, delay, None) else: self._cover_size_delay = 1 def on_flow_combobox_changed(self, combobox): current_val = combobox.get_model()[combobox.get_active()][0] gs = GSetting() if self.settings[gs.PluginKey.FLOW_APPEARANCE] != current_val: if current_val == 'flow-vert': default_size = 150 else: default_size = 600 self.settings[gs.PluginKey.FLOW_WIDTH] = default_size if current_val == 'carousel': self.settings[gs.PluginKey.FLOW_HIDE_CAPTION] = True def on_background_radio_toggled(self, button): if button.get_active(): gs = GSetting() if button == self.white_radiobutton: self.settings[gs.PluginKey.FLOW_BACKGROUND_COLOUR] = 'W' else: self.settings[gs.PluginKey.FLOW_BACKGROUND_COLOUR] = 'B' def on_display_text_pos_radio_toggled(self, button): if self._first_run: return if button.get_active(): gs = GSetting() if button == self.display_text_under_radiobutton: self.settings[gs.PluginKey.DISPLAY_TEXT_POS] = True else: self.settings[gs.PluginKey.DISPLAY_TEXT_POS] = False self.settings[gs.PluginKey.ADD_SHADOW] = False def on_text_alignment_radiobutton_toggled(self, button): if self._first_run: return if button.get_active(): gs = GSetting() if button == self.text_alignment_left_radiobutton: self.settings[gs.PluginKey.TEXT_ALIGNMENT] = 0 elif button == self.text_alignment_centre_radiobutton: self.settings[gs.PluginKey.TEXT_ALIGNMENT] = 1 else: self.settings[gs.PluginKey.TEXT_ALIGNMENT] = 2 def on_add_shadow_checkbox_toggled(self, button): if button.get_active(): # gs = GSetting() #self.settings[gs.PluginKey.DISPLAY_TEXT_POS] = True self.display_text_under_radiobutton.set_active(True) def rating_changed_callback(self, stars): print("rating_changed_callback") gs = GSetting() self.settings[gs.PluginKey.RATING] = self.stars.get_rating() def on_save_button_clicked(self, button): ''' action when genre edit area is saved ''' entry_value = self.genre_entry.get_text() treeiter = self.genre_combobox.get_active_iter() icon_value = self.alt_liststore[treeiter][0] # model 0 is the icon name, model 1 is the pixbuf if self.amend_mode: key = self._sheet.amend_genre_info(self.current_genre, entry_value, icon_value) self.alt_user_liststore[self._iters[( self.current_genre, self.GENRE_LIST )]][1] = self._sheet[self._sheet.genre_alternate[key]] self.alt_user_liststore[self._iters[( self.current_genre, self.GENRE_LIST)]][0] = key.name store_iter = self._iters[(self.current_genre, self.GENRE_LIST)] del self._iters[(self.current_genre, self.GENRE_LIST)] self._iters[(key.name, self.GENRE_LIST)] = store_iter else: self.amend_mode = True key = self._sheet.amend_genre_info('', entry_value, icon_value) self.current_genre = key.name store_iter = self.alt_user_liststore.append([ key.name, self._sheet[self._sheet.genre_alternate[key]], self._sheet.genre_alternate[key] ]) self._iters[(key.name, self.GENRE_LIST)] = store_iter selection = self.genre_view.get_selection() selection.select_iter(store_iter) self.save_button.set_sensitive(False) self._toggle_new_genre_state() def on_genre_filechooserbutton_file_set(self, filechooser): ''' action when genre new icon button is pressed ''' key = self._sheet.add_genre_icon(self.filechooserdialog.get_filename()) store_iter = self.alt_liststore.append( [key.name, self._sheet[key.name]]) self._iters[(key.name, self.GENRE_POPUP)] = store_iter gs = GSetting() last_genre_folder = self.filechooserdialog.get_current_folder() print(last_genre_folder) print(self.filechooserdialog.get_filename()) if last_genre_folder: self.settings[gs.PluginKey.LAST_GENRE_FOLDER] = last_genre_folder def on_genre_view_selection_changed(self, view): ''' action when user selects a row in the list of genres ''' model, genre_iter = view.get_selected() if genre_iter: self.genre_entry.set_text(model[genre_iter][0]) index = model[genre_iter][2] if index != '': self.genre_combobox.set_active_iter( self._iters[(index, self.GENRE_POPUP)]) self.amend_mode = True self.current_genre = rb3compat.unicodestr( model[genre_iter][0], 'utf-8') else: self.genre_entry.set_text('') self.genre_combobox.set_active_iter(None) self.amend_mode = False if self.blank_iter and self.amend_mode: try: index = model[self.blank_iter][0] if index == '': model.remove(self.blank_iter) self.blank_iter = None except: self.blank_iter = None def on_add_button_clicked(self, button): ''' action when a new genre is added to the table ''' self.genre_entry.set_text('') self.genre_combobox.set_active(-1) self.amend_mode = False self.blank_iter = self.alt_user_liststore.append(['', None, '']) selection = self.genre_view.get_selection() selection.select_iter(self.blank_iter) def on_delete_button_clicked(self, button): ''' action when a genre is to be deleted ''' selection = self.genre_view.get_selection() model, genre_iter = selection.get_selected() if genre_iter: index = rb3compat.unicodestr(model[genre_iter][0], 'utf-8') model.remove(genre_iter) if index: del self._iters[(index, self.GENRE_LIST)] self._sheet.delete_genre(index) self._toggle_new_genre_state() def set_save_sensitivity(self, _): ''' action to toggle the state of the save button depending upon the values entered in the genre edit fields ''' entry_value = self.genre_entry.get_text() treeiter = self.genre_combobox.get_active_iter() entry_value = rb3compat.unicodestr(entry_value, 'utf-8') enable = False try: test = self._iters[(entry_value, self.GENRE_LIST)] if RB.search_fold( self.current_genre) == RB.search_fold(entry_value): # if the current entry is the same then could save enable = True except: # reach here if this is a brand new entry enable = True if treeiter == None or entry_value == None or entry_value == "": # no icon chosen, or no entry value then nothing to save enable = False self.save_button.set_sensitive(enable) def _toggle_new_genre_state(self): ''' fire an event - uses gsettings and an object such as a controller connects to receive the signal that a new or amended genre has been made ''' gs = GSetting() test = self.settings[gs.PluginKey.NEW_GENRE_ICON] if test: test = False else: test = True self.settings[gs.PluginKey.NEW_GENRE_ICON] = test def on_show_launchpad_toggled(self, button): self.launchpad_label.set_visible(button.get_active())
def do_create_configure_widget(self): ''' Creates the plugin's preferences dialog ''' # create the ui cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) builder = Gtk.Builder() builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN) builder.add_from_file(rb.find_plugin_file(self, 'ui/coverart_browser_prefs.ui')) self.launchpad_button = builder.get_object('show_launchpad') self.launchpad_label = builder.get_object('launchpad_label') builder.connect_signals(self) #. TRANSLATORS: Do not translate this string. translators = _('translator-credits') if translators != "translator-credits": self.launchpad_label.set_text(translators) else: self.launchpad_button.set_visible(False) gs = GSetting() # bind the toggles to the settings toggle_statusbar = builder.get_object('custom_statusbar_checkbox') self.settings.bind(gs.PluginKey.CUSTOM_STATUSBAR, toggle_statusbar, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_bottom = builder.get_object('display_bottom_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_BOTTOM, toggle_bottom, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_text = builder.get_object('display_text_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, toggle_text, 'active', Gio.SettingsBindFlags.DEFAULT) box_text = builder.get_object('display_text_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, box_text, 'sensitive', Gio.SettingsBindFlags.GET) toggle_text_ellipsize = builder.get_object( 'display_text_ellipsize_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, toggle_text_ellipsize, 'active', Gio.SettingsBindFlags.DEFAULT) box_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, box_text_ellipsize_length, 'sensitive', Gio.SettingsBindFlags.GET) spinner_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_spin') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE_LENGTH, spinner_text_ellipsize_length, 'value', Gio.SettingsBindFlags.DEFAULT) spinner_font_size = builder.get_object( 'display_font_spin') self.settings.bind(gs.PluginKey.DISPLAY_FONT_SIZE, spinner_font_size, 'value', Gio.SettingsBindFlags.DEFAULT) cover_size_scale = builder.get_object('cover_size_adjustment') self.settings.bind(gs.PluginKey.COVER_SIZE, cover_size_scale, 'value', Gio.SettingsBindFlags.DEFAULT) add_shadow = builder.get_object('add_shadow_checkbox') self.settings.bind(gs.PluginKey.ADD_SHADOW, add_shadow, 'active', Gio.SettingsBindFlags.DEFAULT) rated_box = builder.get_object('rated_box') self.stars = ReactiveStar(size=StarSize.BIG) self.stars.connect('changed', self.rating_changed_callback) align = Gtk.Alignment.new(0.5, 0, 0, 0.1) align.add(self.stars) rated_box.add(align) self.stars.set_rating(self.settings[gs.PluginKey.RATING]) autostart = builder.get_object('autostart_checkbox') self.settings.bind(gs.PluginKey.AUTOSTART, autostart, 'active', Gio.SettingsBindFlags.DEFAULT) toolbar_pos_combo = builder.get_object('show_in_combobox') renderer = Gtk.CellRendererText() toolbar_pos_combo.pack_start(renderer, True) toolbar_pos_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.TOOLBAR_POS, toolbar_pos_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) light_source_combo = builder.get_object('light_source_combobox') renderer = Gtk.CellRendererText() light_source_combo.pack_start(renderer, True) light_source_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.SHADOW_IMAGE, light_source_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) combo_liststore = builder.get_object('combo_liststore') from coverart_utils import Theme for theme in Theme(self).themes: combo_liststore.append([theme, theme]) theme_combo = builder.get_object('theme_combobox') renderer = Gtk.CellRendererText() theme_combo.pack_start(renderer, True) theme_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.THEME, theme_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) button_relief = builder.get_object('button_relief_checkbox') self.settings.bind(gs.PluginKey.BUTTON_RELIEF, button_relief, 'active', Gio.SettingsBindFlags.DEFAULT) # create user data files popup = RB.find_user_data_file('plugins/coverart_browser/img/usericons/popups.xml') if not os.path.isfile(popup): template = rb.find_plugin_file(self, 'template/popups.xml') folder = os.path.split(popup)[0] if not os.path.exists(folder): os.makedirs(folder) shutil.copyfile(template, popup) # now prepare the genre tab from coverart_utils import GenreConfiguredSpriteSheet from coverart_utils import get_stock_size from coverart_utils import GenreType self._sheet = GenreConfiguredSpriteSheet(self, "genre", get_stock_size()) self.alt_liststore = builder.get_object('alt_liststore') self.alt_user_liststore = builder.get_object('alt_user_liststore') self._iters = {} for key in list(self._sheet.keys()): store_iter = self.alt_liststore.append([key, self._sheet[key]]) self._iters[(key,self.GENRE_POPUP)] = store_iter for key, value in self._sheet.genre_alternate.items(): if key.genre_type == GenreConfiguredSpriteSheet.GENRE_USER: store_iter = self.alt_user_liststore.append([key.name, self._sheet[self._sheet.genre_alternate[key]], self._sheet.genre_alternate[key]]) self._iters[(key.name, self.GENRE_LIST)] = store_iter self.amend_mode = False self.blank_iter = None self.genre_combobox = builder.get_object('genre_combobox') self.genre_entry = builder.get_object('genre_entry') self.genre_view = builder.get_object('genre_view') self.save_button = builder.get_object('save_button') self.filechooserdialog = builder.get_object('filechooserdialog') padding_scale = builder.get_object('padding_adjustment') self.settings.bind(gs.PluginKey.ICON_PADDING, padding_scale, 'value', Gio.SettingsBindFlags.DEFAULT) spacing_scale = builder.get_object('spacing_adjustment') self.settings.bind(gs.PluginKey.ICON_SPACING, spacing_scale, 'value', Gio.SettingsBindFlags.DEFAULT) # return the dialog return builder.get_object('main_notebook')
def _create_display_contents(self, plugin): print("DEBUG - create_display_contents") # create the ui self._first_run = True cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) builder = Gtk.Builder() builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN) builder.add_from_file( rb.find_plugin_file(plugin, 'ui/coverart_browser_prefs.ui')) self.launchpad_button = builder.get_object('show_launchpad') self.launchpad_label = builder.get_object('launchpad_label') builder.connect_signals(self) # . TRANSLATORS: Do not translate this string. translators = _('translator-credits') if translators != "translator-credits": self.launchpad_label.set_text(translators) else: self.launchpad_button.set_visible(False) gs = GSetting() # bind the toggles to the settings toggle_statusbar = builder.get_object('custom_statusbar_checkbox') self.settings.bind(gs.PluginKey.CUSTOM_STATUSBAR, toggle_statusbar, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_text = builder.get_object('display_text_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, toggle_text, 'active', Gio.SettingsBindFlags.DEFAULT) box_text = builder.get_object('display_text_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, box_text, 'sensitive', Gio.SettingsBindFlags.GET) self.display_text_pos = self.settings[gs.PluginKey.DISPLAY_TEXT_POS] self.display_text_under_radiobutton = builder.get_object( 'display_text_under_radiobutton') self.display_text_within_radiobutton = builder.get_object( 'display_text_within_radiobutton') if self.display_text_pos: self.display_text_under_radiobutton.set_active(True) else: self.display_text_within_radiobutton.set_active(True) random_scale = builder.get_object('random_adjustment') self.settings.bind(gs.PluginKey.RANDOM, random_scale, 'value', Gio.SettingsBindFlags.DEFAULT) toggle_text_ellipsize = builder.get_object( 'display_text_ellipsize_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, toggle_text_ellipsize, 'active', Gio.SettingsBindFlags.DEFAULT) box_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, box_text_ellipsize_length, 'sensitive', Gio.SettingsBindFlags.GET) spinner_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_spin') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE_LENGTH, spinner_text_ellipsize_length, 'value', Gio.SettingsBindFlags.DEFAULT) spinner_font_size = builder.get_object('display_font_spin') self.settings.bind(gs.PluginKey.DISPLAY_FONT_SIZE, spinner_font_size, 'value', Gio.SettingsBindFlags.DEFAULT) cover_size_scale = builder.get_object('cover_size_adjustment') #self.settings.bind(gs.PluginKey.COVER_SIZE, cover_size_scale, 'value', # Gio.SettingsBindFlags.DEFAULT) self._cover_size = self.settings[gs.PluginKey.COVER_SIZE] cover_size_scale.set_value(self._cover_size) cover_size_scale.connect('value-changed', self.on_cover_size_scale_changed) add_shadow = builder.get_object('add_shadow_checkbox') self.settings.bind(gs.PluginKey.ADD_SHADOW, add_shadow, 'active', Gio.SettingsBindFlags.DEFAULT) rated_box = builder.get_object('rated_box') self.stars = ReactiveStar(size=StarSize.BIG) self.stars.connect('changed', self.rating_changed_callback) align = Gtk.Alignment.new(0.5, 0, 0, 0.1) align.add(self.stars) rated_box.add(align) self.stars.set_rating(self.settings[gs.PluginKey.RATING]) autostart = builder.get_object('autostart_checkbox') self.settings.bind(gs.PluginKey.AUTOSTART, autostart, 'active', Gio.SettingsBindFlags.DEFAULT) toolbar_pos_combo = builder.get_object('show_in_combobox') renderer = Gtk.CellRendererText() toolbar_pos_combo.pack_start(renderer, True) toolbar_pos_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.TOOLBAR_POS, toolbar_pos_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) light_source_combo = builder.get_object('light_source_combobox') renderer = Gtk.CellRendererText() light_source_combo.pack_start(renderer, True) light_source_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.SHADOW_IMAGE, light_source_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) combo_liststore = builder.get_object('combo_liststore') from coverart_utils import Theme for theme in Theme(self).themes: combo_liststore.append([theme, theme]) theme_combo = builder.get_object('theme_combobox') renderer = Gtk.CellRendererText() theme_combo.pack_start(renderer, True) theme_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.THEME, theme_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) button_relief = builder.get_object('button_relief_checkbox') self.settings.bind(gs.PluginKey.BUTTON_RELIEF, button_relief, 'active', Gio.SettingsBindFlags.DEFAULT) # create user data files cachedir = RB.user_cache_dir() + "/coverart_browser/usericons" if not os.path.exists(cachedir): os.makedirs(cachedir) popup = cachedir + "/popups.xml" temp = RB.find_user_data_file( 'plugins/coverart_browser/img/usericons/popups.xml') # lets see if there is a legacy file - if necessary copy it to the cache dir if os.path.isfile(temp) and not os.path.isfile(popup): shutil.copyfile(temp, popup) if not os.path.isfile(popup): template = rb.find_plugin_file(plugin, 'template/popups.xml') folder = os.path.split(popup)[0] if not os.path.exists(folder): os.makedirs(folder) shutil.copyfile(template, popup) # now prepare the genre tab from coverart_utils import GenreConfiguredSpriteSheet from coverart_utils import get_stock_size self._sheet = GenreConfiguredSpriteSheet(plugin, "genre", get_stock_size()) self.alt_liststore = builder.get_object('alt_liststore') self.alt_user_liststore = builder.get_object('alt_user_liststore') self._iters = {} for key in list(self._sheet.keys()): store_iter = self.alt_liststore.append([key, self._sheet[key]]) self._iters[(key, self.GENRE_POPUP)] = store_iter for key, value in self._sheet.genre_alternate.items(): if key.genre_type == GenreConfiguredSpriteSheet.GENRE_USER: store_iter = self.alt_user_liststore.append([ key.name, self._sheet[self._sheet.genre_alternate[key]], self._sheet.genre_alternate[key] ]) self._iters[(key.name, self.GENRE_LIST)] = store_iter self.amend_mode = False self.blank_iter = None self.genre_combobox = builder.get_object('genre_combobox') self.genre_entry = builder.get_object('genre_entry') self.genre_view = builder.get_object('genre_view') self.save_button = builder.get_object('save_button') self.filechooserdialog = builder.get_object('filechooserdialog') last_genre_folder = self.settings[gs.PluginKey.LAST_GENRE_FOLDER] if last_genre_folder != "": self.filechooserdialog.set_current_folder(last_genre_folder) padding_scale = builder.get_object('padding_adjustment') self.settings.bind(gs.PluginKey.ICON_PADDING, padding_scale, 'value', Gio.SettingsBindFlags.DEFAULT) spacing_scale = builder.get_object('spacing_adjustment') self.settings.bind(gs.PluginKey.ICON_SPACING, spacing_scale, 'value', Gio.SettingsBindFlags.DEFAULT) icon_automatic = builder.get_object('icon_automatic_checkbox') self.settings.bind(gs.PluginKey.ICON_AUTOMATIC, icon_automatic, 'active', Gio.SettingsBindFlags.DEFAULT) #flow tab flow_combo = builder.get_object('flow_combobox') renderer = Gtk.CellRendererText() flow_combo.pack_start(renderer, True) flow_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.FLOW_APPEARANCE, flow_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) flow_hide = builder.get_object('hide_caption_checkbox') self.settings.bind(gs.PluginKey.FLOW_HIDE_CAPTION, flow_hide, 'active', Gio.SettingsBindFlags.DEFAULT) flow_scale = builder.get_object('cover_scale_adjustment') self.settings.bind(gs.PluginKey.FLOW_SCALE, flow_scale, 'value', Gio.SettingsBindFlags.DEFAULT) flow_width = builder.get_object('cover_width_adjustment') self.settings.bind(gs.PluginKey.FLOW_WIDTH, flow_width, 'value', Gio.SettingsBindFlags.DEFAULT) flow_max = builder.get_object('flow_max_adjustment') self.settings.bind(gs.PluginKey.FLOW_MAX, flow_max, 'value', Gio.SettingsBindFlags.DEFAULT) flow_automatic = builder.get_object('automatic_checkbox') self.settings.bind(gs.PluginKey.FLOW_AUTOMATIC, flow_automatic, 'active', Gio.SettingsBindFlags.DEFAULT) self.background_colour = self.settings[ gs.PluginKey.FLOW_BACKGROUND_COLOUR] self.white_radiobutton = builder.get_object('white_radiobutton') self.black_radiobutton = builder.get_object('black_radiobutton') if self.background_colour == 'W': self.white_radiobutton.set_active(True) else: self.black_radiobutton.set_active(True) self.text_alignment = self.settings[gs.PluginKey.TEXT_ALIGNMENT] self.text_alignment_left_radiobutton = builder.get_object( 'left_alignment_radiobutton') self.text_alignment_centre_radiobutton = builder.get_object( 'centre_alignment_radiobutton') self.text_alignment_right_radiobutton = builder.get_object( 'right_alignment_radiobutton') if self.text_alignment == 0: self.text_alignment_left_radiobutton.set_active(True) elif self.text_alignment == 1: self.text_alignment_centre_radiobutton.set_active(True) else: self.text_alignment_right_radiobutton.set_active(True) # return the dialog self._first_run = False print("end create dialog contents") return builder.get_object('main_notebook')
class CoverArtBrowserSource(RB.Source): ''' Source utilized by the plugin to show all it's ui. ''' rating_threshold = GObject.property(type=float, default=0) artist_paned_pos = GObject.property(type=str) min_paned_pos = 40 # unique instance of the source instance = None def __init__(self, **kargs): ''' Initializes the source. ''' super(CoverArtBrowserSource, self).__init__(**kargs) # create source_source_settings and connect the source's properties self.gs = GSetting() self._connect_properties() self.hasActivated = False self.last_width = 0 self.last_selected_album = None self.click_count = 0 self.favourites = False self.task_progress = None def _connect_properties(self): ''' Connects the source properties to the saved preferences. ''' print("CoverArtBrowser DEBUG - _connect_properties") setting = self.gs.get_setting(self.gs.Path.PLUGIN) setting.bind( self.gs.PluginKey.RATING, self, 'rating_threshold', Gio.SettingsBindFlags.GET) print("CoverArtBrowser DEBUG - end _connect_properties") def do_get_status(self, *args): ''' Method called by Rhythmbox to figure out what to show on this source statusbar. If the custom statusbar is disabled, the source will show the selected album info. Also, it makes sure to show the progress on the album loading ''' try: # this will only work for RB3.0 and later if not self.task_progress: self.task_progress = RB.TaskProgressSimple.new() except: pass try: progress = self.album_manager.progress progress_text = _('Loading...') if progress < 1 else '' try: # this will only work for RB3.0 and later if progress < 1: if self.props.shell.props.task_list.get_model().n_items() == 0: self.props.shell.props.task_list.add_task(self.task_progress) self.task_progress.props.task_progress = progress self.task_progress.props.task_label=progress_text else: self.task_progress.props.task_outcome = RB.TaskOutcome.COMPLETE except: pass except: progress = 1 progress_text = '' try: # this will only work for RB3.0 and later self.task_progress.props.task_outcome = RB.TaskOutcome.COMPLETE except: pass return (self.status, progress_text, progress) def do_selected(self): ''' Called by Rhythmbox when the source is selected. It makes sure to create the ui the first time the source is showed. ''' print("CoverArtBrowser DEBUG - do_selected") # first time of activation -> add graphical stuff if not self.hasActivated: self.do_impl_activate() # indicate that the source was activated before self.hasActivated = True print("CoverArtBrowser DEBUG - end do_selected") def do_impl_activate(self): ''' Called by do_selected the first time the source is activated. It creates all the source ui and connects the necesary signals for it correct behavior. ''' print("CoverArtBrowser DEBUG - do_impl_activate") # initialise some variables self.plugin = self.props.plugin self.shell = self.props.shell self.status = '' self.search_text = '' self.actiongroup = ActionGroup(self.shell, 'coverplaylist_submenu') self._browser_preferences = None self._search_preferences = None # indicate that the source was activated before self.hasActivated = True self._create_ui() self._setup_source() self._apply_settings() print("CoverArtBrowser DEBUG - end do_impl_activate") def _create_ui(self): ''' Creates the ui for the source and saves the important widgets onto properties. ''' print("CoverArtBrowser DEBUG - _create_ui") # dialog has not been created so lets do so. cl = CoverLocale() ui = Gtk.Builder() ui.set_translation_domain(cl.Locale.LOCALE_DOMAIN) ui.add_from_file(rb.find_plugin_file(self.plugin, 'ui/coverart_browser.ui')) ui.connect_signals(self) # load the page and put it in the source self.page = ui.get_object('main_box') self.pack_start(self.page, True, True, 0) # get widgets for main icon-view self.status_label = ui.get_object('status_label') window = ui.get_object('scrolled_window') self.viewmgr = ViewManager(self, window) # get widgets for the artist paned self.artist_paned = ui.get_object('vertical_paned') Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 50, self._change_artist_paned_pos, self.viewmgr.view_name) self.viewmgr.connect('new-view', self.on_view_changed) self.artist_treeview = ui.get_object('artist_treeview') self.artist_scrolledwindow = ui.get_object('artist_scrolledwindow') # define menu's self.popup_menu = Menu(self.plugin, self.shell) self.popup_menu.load_from_file('ui/coverart_browser_pop_rb2.ui', 'ui/coverart_browser_pop_rb3.ui') self._external_plugins = None signals = \ { 'play_album_menu_item': self.play_album_menu_item_callback, 'queue_album_menu_item': self.queue_album_menu_item_callback, 'new_playlist': self.add_playlist_menu_item_callback, 'cover_search_menu_item': self.cover_search_menu_item_callback, 'export_embed_menu_item': self.export_embed_menu_item_callback, 'show_properties_menu_item': self.show_properties_menu_item_callback, 'play_similar_artist_menu_item': self.play_similar_artist_menu_item_callback} self.popup_menu.connect_signals(signals) self.popup_menu.connect('pre-popup', self.add_external_menu) self.status_label = ui.get_object('status_label') self.request_status_box = ui.get_object('request_status_box') self.request_spinner = ui.get_object('request_spinner') self.request_statusbar = ui.get_object('request_statusbar') self.request_cancel_button = ui.get_object('request_cancel_button') self.paned = ui.get_object('paned') self.notebook = ui.get_object('bottom_notebook') #---- set up info pane -----# info_scrolled_window = ui.get_object('info_scrolled_window') info_button_box = ui.get_object('info_button_box') artist_info_paned = ui.get_object('vertical_info_paned') self.artist_info = ArtistInfoPane(info_button_box, info_scrolled_window, artist_info_paned, self) # quick search self.quick_search = ui.get_object('quick_search_entry') print("CoverArtBrowser DEBUG - end _create_ui") def _setup_source(self): ''' Setups the differents parts of the source so they are ready to be used by the user. It also creates and configure some custom widgets. ''' print("CoverArtBrowser DEBUG - _setup_source") cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) # setup iconview popup self.viewmgr.current_view.set_popup_menu(self.popup_menu) # setup entry-view objects and widgets setting = self.gs.get_setting(self.gs.Path.PLUGIN) setting.bind(self.gs.PluginKey.PANED_POSITION, self.paned, 'collapsible-y', Gio.SettingsBindFlags.DEFAULT) setting.bind(self.gs.PluginKey.DISPLAY_BOTTOM, self.paned.get_child2(), 'visible', Gio.SettingsBindFlags.DEFAULT) # create entry view. Don't allow to reorder until the load is finished self.entry_view = EV(self.shell, self) self.entry_view.set_columns_clickable(False) self.shell.props.library_source.get_entry_view().set_columns_clickable( False) self.stars = ReactiveStar() self.stars.set_rating(0) a = Gtk.Alignment.new(0.5, 0.5, 0, 0) a.add(self.stars) self.stars.connect('changed', self.rating_changed_callback) vbox = Gtk.Box() vbox.set_orientation(Gtk.Orientation.VERTICAL) vbox.pack_start(self.entry_view, True, True, 0) vbox.pack_start(a, False, False, 1) vbox.show_all() self.notebook.append_page(vbox, Gtk.Label.new_with_mnemonic(_("Tracks"))) # create an album manager self.album_manager = AlbumManager(self.plugin, self.viewmgr.current_view) self.viewmgr.current_view.initialise(self) # setup cover search pane colour = self.viewmgr.get_selection_colour() self.cover_search_pane = CoverSearchPane(self.plugin, colour) self.notebook.append_page(self.cover_search_pane, Gtk.Label.new_with_mnemonic( _("Covers"))) # connect a signal to when the info of albums is ready self.load_fin_id = self.album_manager.loader.connect( 'model-load-finished', self.load_finished_callback) # prompt the loader to load the albums self.album_manager.loader.load_albums(self.props.base_query_model) # initialise the variables of the quick search self.quick_search_controller = AlbumQuickSearchController( self.album_manager) self.quick_search_controller.connect_quick_search(self.quick_search) # set sensitivity of export menu item for iconview self.popup_menu.set_sensitive('export_embed_menu_item', CoverArtExport(self.plugin, self.shell, self.album_manager).is_search_plugin_enabled()) # setup the statusbar component self.statusbar = Statusbar(self) # initialise the toolbar manager self.toolbar_manager = ToolbarManager(self.plugin, self.page, self.viewmgr) self.viewmgr.current_view.emit('update-toolbar') cl.switch_locale(cl.Locale.RB) # setup the artist paned artist_pview = None for view in self.shell.props.library_source.get_property_views(): print (view.props.title) print (_("Artist")) if view.props.title == _("Artist"): artist_pview = view break assert artist_pview, "cannot find artist property view" self.artist_treeview.set_model(artist_pview.get_model()) setting.bind(self.gs.PluginKey.ARTIST_PANED_POSITION, self, 'artist-paned-pos', Gio.SettingsBindFlags.DEFAULT) self.artist_paned.connect('button-release-event', self.artist_paned_button_release_callback) # intercept JumpToPlaying Song action so that we can scroll to the playing album appshell = rb3compat.ApplicationShell(self.shell) action = appshell.lookup_action("", "jump-to-playing", "win") action.action.connect("activate", self.jump_to_playing, None) self.echonest_similar_playlist = None print("CoverArtBrowser DEBUG - end _setup_source") def add_external_menu(self, *args): ''' Callback when the popup menu is about to be displayed ''' if not self._external_plugins: # initialise external plugin menu support self._external_plugins = \ CreateExternalPluginMenu("ca_covers_view", 7, self.popup_menu) self._external_plugins.create_menu('popup_menu', True) self.playlist_menu_item_callback() def jump_to_playing(self, *args): ''' Callback when the JumpToPlaying action is invoked This will scroll the view to the playing song ''' if not self.shell.props.selected_page.props.name == self.props.name: # if the source page that was played from is not the plugin then # nothing to do return album = None entry = self.shell.props.shell_player.get_playing_entry() if entry: album = self.album_manager.model.get_from_dbentry(entry) self.viewmgr.current_view.scroll_to_album(album) def artist_paned_button_release_callback(self, *args): ''' Callback when the artist paned handle is released from its mouse click. ''' child_width = self._get_child_width() paned_positions = eval(self.artist_paned_pos) found = None for viewpos in paned_positions: if self.viewmgr.view_name in viewpos: found = viewpos break if not found: return paned_positions.remove(found) if child_width <= self.min_paned_pos: child_width = 0 self.artist_paned.set_position(child_width) paned_positions.append(self.viewmgr.view_name + ":" + str(child_width)) self.artist_paned_pos = repr(paned_positions) def on_view_changed(self, widget, view_name): self._change_artist_paned_pos(view_name) def _change_artist_paned_pos(self, view_name): paned_positions = eval(self.artist_paned_pos) print (paned_positions) found = None for viewpos in paned_positions: if view_name in viewpos: found = viewpos break print (found) if not found: return child_width = int(found.split(":")[1]) print (child_width) # odd case - if the pane is not visible but the position is zero # then the paned position on visible=true is some large arbitary value # hence - set it to be 1 px larger than the real value, then set it back # to its expected value self.artist_paned.set_position(child_width + 1) self.artist_paned.set_visible(True) self.artist_paned.set_position(child_width) def _get_child_width(self): child = self.artist_paned.get_child1() return child.get_allocated_width() def on_artist_treeview_selection_changed(self, view): model, artist_iter = view.get_selected() if artist_iter: artist = model[artist_iter][0] cl = CoverLocale() cl.switch_locale(cl.Locale.RB) #. TRANSLATORS - "All" is used in the context of "All artist names" if artist == _('All'): self.album_manager.model.remove_filter('quick_artist') else: self.album_manager.model.replace_filter('quick_artist', artist) cl.switch_locale(cl.Locale.LOCALE_DOMAIN) def _apply_settings(self): ''' Applies all the settings related to the source and connects those that must be updated when the preferences dialog changes it's values. Also enables differents parts of the ui if the settings says so. ''' print("CoverArtBrowser DEBUG - _apply_settings") # connect some signals to the loader to keep the source informed self.album_mod_id = self.album_manager.model.connect('album-updated', self.on_album_updated) self.notify_prog_id = self.album_manager.connect( 'notify::progress', lambda *args: self.notify_status_changed()) print("CoverArtBrowser DEBUG - end _apply_settings") def load_finished_callback(self, _): ''' Callback called when the loader finishes loading albums into the covers view model. ''' print("CoverArtBrowser DEBUG - load_finished_callback") #if not self.request_status_box.get_visible(): # it should only be enabled if no cover request is going on #self.source_menu_search_all_item.set_sensitive(True) # enable sorting on the entryview self.entry_view.set_columns_clickable(True) self.shell.props.library_source.get_entry_view().set_columns_clickable( True) print("CoverArtBrowser DEBUG - end load_finished_callback") def get_entry_view(self): return self.entry_view def on_album_updated(self, model, path, tree_iter): ''' Callback called by the album loader when one of the albums managed by him gets modified in some way. ''' album = model.get_from_path(path) selected = self.viewmgr.current_view.get_selected_objects() if album in selected: # update the selection since it may have changed self.viewmgr.current_view.selectionchanged_callback() if album is selected[0] and \ self.notebook.get_current_page() == \ self.notebook.page_num(self.cover_search_pane): # also, if it's the first, update the cover search pane self.cover_search_pane.clear() self.cover_search_pane.do_search(album, self.album_manager.cover_man.update_cover) def play_similar_artist_menu_item_callback(self, *args): ''' Callback called when the play similar artist option is selected from the cover view popup. It plays similar artists music. ''' def play_similar_artist_menu_item_callback(self, *args): if not self.echonest_similar_playlist: self.echonest_similar_playlist = \ EchoNestPlaylist( self.shell, self.shell.props.queue_source) selected_albums = self.viewmgr.current_view.get_selected_objects() album = selected_albums[0] tracks = album.get_tracks() entry = tracks[0].entry self.echonest_similar_playlist.start(entry, reinitialise=True) def show_properties_menu_item_callback(self, *args): ''' Callback called when the show album properties option is selected from the cover view popup. It shows a SongInfo dialog showing the selected albums' entries info, which can be modified. ''' print("CoverArtBrowser DEBUG - show_properties_menu_item_callback") self.entry_view.select_all() info_dialog = RB.SongInfo(source=self, entry_view=self.entry_view) info_dialog.show_all() print("CoverArtBrowser DEBUG - end show_properties_menu_item_callback") def play_selected_album(self, favourites=False): ''' Utilitary method that plays all entries from an album into the play queue. ''' # callback when play an album print("CoverArtBrowser DEBUG - play_selected_album") query_model = RB.RhythmDBQueryModel.new_empty(self.shell.props.db) self.queue_selected_album(query_model, favourites) if len(query_model) > 0: self.props.query_model = query_model # Start the music player = self.shell.props.shell_player player.play_entry(query_model[0][0], self) print("CoverArtBrowser DEBUG - end play_selected_album") def queue_selected_album(self, source, favourites=False): ''' Utilitary method that queues all entries from an album into the play queue. ''' print("CoverArtBrowser DEBUG - queue_selected_album") selected_albums = self.viewmgr.current_view.get_selected_objects() threshold = self.rating_threshold if favourites else 0 total = 0 for album in selected_albums: # Retrieve and sort the entries of the album tracks = album.get_tracks(threshold) total = total + len(tracks) # Add the songs to the play queue for track in tracks: source.add_entry(track.entry, -1) if total == 0 and threshold: dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("No tracks have been added because no tracks meet the favourite rating threshold")) dialog.run() dialog.destroy() print("CoverArtBrowser DEBUG - end queue_select_album") def play_album_menu_item_callback(self, *args): ''' Callback called when the play album item from the cover view popup is selected. It cleans the play queue and queues the selected album. ''' print("CoverArtBrowser DEBUG - play_album_menu_item_callback") self.play_selected_album(self.favourites) print("CoverArtBrowser DEBUG - end play_album_menu_item_callback") def queue_album_menu_item_callback(self, *args): ''' Callback called when the queue album item from the cover view popup is selected. It queues the selected album at the end of the play queue. ''' print("CoverArtBrowser DEBUG - queue_album_menu_item_callback()") self.queue_selected_album(self.shell.props.queue_source, self.favourites) print("CoverArtBrowser DEBUG - end queue_album_menu_item_callback()") def playlist_menu_item_callback(self, *args): print("CoverArtBrowser DEBUG - playlist_menu_item_callback") self.playlist_fillmenu(self.popup_menu, 'playlist_submenu', 'playlist_section', self.actiongroup, self.add_to_static_playlist_menu_item_callback, self.favourites) def playlist_fillmenu(self, popup_menu, menubar, section_name, actiongroup, func, favourite=False): print("CoverArtBrowser DEBUG - playlist_fillmenu") playlist_manager = self.shell.props.playlist_manager playlists_entries = playlist_manager.get_playlists() # tidy up old playlists menu items before recreating the list actiongroup.remove_actions() popup_menu.remove_menu_items(menubar, section_name) if playlists_entries: for playlist in playlists_entries: if playlist.props.is_local and \ isinstance(playlist, RB.StaticPlaylistSource): args=(playlist, favourite) # take the name of the playlist, strip out non-english characters and reduce the string # to just a-to-z characters i.e. this will make the action_name valid in RB3 ascii_name = unicodedata.normalize('NFKD', \ rb3compat.unicodestr(playlist.props.name, 'utf-8')).encode('ascii','ignore') ascii_name = ascii_name.decode(encoding='UTF-8') ascii_name = re.sub(r'[^a-zA-Z]', '', ascii_name) action = actiongroup.add_action(func=func, action_name=ascii_name, playlist=playlist,favourite=favourite, label=playlist.props.name) popup_menu.add_menu_item( menubar, section_name, action ) def add_to_static_playlist_menu_item_callback(self, action, param, args): print('''CoverArtBrowser DEBUG - add_to_static_playlist_menu_item_callback''') playlist=args['playlist'] favourite = args['favourite'] self.queue_selected_album(playlist, favourite) def add_playlist_menu_item_callback(self, *args): print('''CoverArtBrowser DEBUG - add_playlist_menu_item_callback''') playlist_manager = self.shell.props.playlist_manager playlist = playlist_manager.new_playlist(_('New Playlist'), False) self.queue_selected_album(playlist, self.favourites) def play_random_album_menu_item_callback(self, favourites=False): print('''CoverArtBrowser DEBUG - play_random_album_menu_item_callback''') query_model = RB.RhythmDBQueryModel.new_empty(self.shell.props.db) num_albums = len(self.album_manager.model.store) #random_list = [] selected_albums = [] gs = GSetting() settings = gs.get_setting(gs.Path.PLUGIN) to_queue = settings[gs.PluginKey.RANDOM] if num_albums <= to_queue: dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("The number of albums to randomly play is less than that displayed.")) dialog.run() dialog.destroy() return album_col = self.album_manager.model.columns['album'] chosen = {} # now loop through finding unique random albums # i.e. ensure we dont queue the same album twice for loop in range(0, to_queue): while True: pos = random.randint(0, num_albums - 1) if pos not in chosen: chosen[pos] = None selected_albums.append(self.album_manager.model.store[pos][album_col]) break threshold = self.rating_threshold if favourites else 0 total = 0 for album in selected_albums: # Retrieve and sort the entries of the album tracks = album.get_tracks(threshold) total = total + len(tracks) # Add the songs to the play queue for track in tracks: query_model.add_entry(track.entry, -1) if total == 0 and threshold: dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("No tracks have been added because no tracks meet the favourite rating threshold")) dialog.run() dialog.destroy() self.props.query_model = query_model # Start the music player = self.shell.props.shell_player player.play_entry(query_model[0][0], self) print("CoverArtBrowser DEBUG - end play_selected_album") def cover_search_menu_item_callback(self, *args): ''' Callback called when the search cover option is selected from the cover view popup. It prompts the album loader to retrieve the selected album cover ''' print("CoverArtBrowser DEBUG - cover_search_menu_item_callback()") selected_albums = self.viewmgr.current_view.get_selected_objects() self.request_status_box.show_all() self.album_manager.cover_man.search_covers(selected_albums, self.update_request_status_bar) print("CoverArtBrowser DEBUG - end cover_search_menu_item_callback()") def export_embed_menu_item_callback(self, *args): ''' Callback called when the export and embed coverart option is selected from the cover view popup. It prompts the exporter to copy and embed art for the albums chosen ''' print("CoverArtBrowser DEBUG - export_embed_menu_item_callback()") selected_albums = self.viewmgr.current_view.get_selected_objects() CoverArtExport(self.plugin, self.shell, self.album_manager).embed_albums(selected_albums) print("CoverArtBrowser DEBUG - export_embed_menu_item_callback()") def update_request_status_bar(self, coverobject): ''' Callback called by the album loader starts performing a new cover request. It prompts the source to change the content of the request statusbar. ''' print("CoverArtBrowser DEBUG - update_request_status_bar") if coverobject: # for example "Requesting the picture cover for the music artist Michael Jackson" tranlation_string = _('Requesting cover for %s...') self.request_statusbar.set_text( rb3compat.unicodedecode(_('Requesting cover for %s...') % (coverobject.name), 'UTF-8')) else: self.request_status_box.hide() self.popup_menu.set_sensitive('cover_search_menu_item', True) self.request_cancel_button.set_sensitive(True) print("CoverArtBrowser DEBUG - end update_request_status_bar") def cancel_request_callback(self, _): ''' Callback connected to the cancel button on the request statusbar. When called, it prompts the album loader to cancel the full cover search after the current cover. ''' print("CoverArtBrowser DEBUG - cancel_request_callback") self.request_cancel_button.set_sensitive(False) self._cover_search_manager.cover_man.cancel_cover_request() print("CoverArtBrowser DEBUG - end cancel_request_callback") def notebook_switch_page_callback(self, notebook, page, page_num): ''' Callback called when the notebook page gets switched. It initiates the cover search when the cover search pane's page is selected. ''' print("CoverArtBrowser DEBUG - notebook_switch_page_callback") if page_num == 1: self.viewmgr.current_view.switch_to_coverpane(self.cover_search_pane) print("CoverArtBrowser DEBUG - end notebook_switch_page_callback") def rating_changed_callback(self, widget): ''' Callback called when the Rating stars is changed ''' print("CoverArtBrowser DEBUG - rating_changed_callback") rating = widget.get_rating() for album in self.viewmgr.current_view.get_selected_objects(): album.rating = rating print("CoverArtBrowser DEBUG - end rating_changed_callback") def show_hide_pane(self, params): ''' helper function - if the entry is manually expanded then if necessary scroll the view to the last selected album params is "album" or a tuple of "album" and "force_expand" boolean ''' if isinstance(params, tuple): album, force = params else: album = params force = PanedCollapsible.Paned.DEFAULT if (album and self.click_count == 1 \ and self.last_selected_album is album) or force != PanedCollapsible.Paned.DEFAULT: # check if it's a second or third click on the album and expand # or collapse the entry view accordingly self.paned.expand(force) # update the selected album selected = self.viewmgr.current_view.get_selected_objects() self.last_selected_album = selected[0] if len(selected) == 1 else None # clear the click count self.click_count = 0 def update_with_selection(self): ''' Update the source view when an item gets selected. ''' selected = self.viewmgr.current_view.get_selected_objects() # clear the entry view self.entry_view.clear() cover_search_pane_visible = self.notebook.get_current_page() == \ self.notebook.page_num(self.cover_search_pane) if not selected: # clean cover tab if selected if cover_search_pane_visible: self.cover_search_pane.clear() return elif len(selected) == 1: self.stars.set_rating(selected[0].rating) if selected[0] is not self.last_selected_album: # when the selection changes we've to take into account two # things if not self.click_count: # we may be using the arrows, so if there is no mouse # involved, we should change the last selected self.last_selected_album = selected[0] else: # we may've doing a fast change after a valid second click, # so it shouldn't be considered a double click self.click_count -= 1 else: self.stars.set_rating(0) if len(selected) == 1: self.artist_info.emit('selected', selected[0].artist, selected[0].name) for album in selected: # add the album to the entry_view self.entry_view.add_album(album) # update the cover search pane with the first selected album if cover_search_pane_visible: self.cover_search_pane.do_search(selected[0], self.album_manager.cover_man.update_cover) self.statusbar.emit('display-status', self.viewmgr.current_view) def propertiesbutton_callback(self, choice): if choice == 'download': self.request_status_box.show_all() self._cover_search_manager = self.viewmgr.current_view.get_default_manager() self._cover_search_manager.cover_man.search_covers( callback=self.update_request_status_bar) elif choice == 'random': self.play_random_album_menu_item_callback() elif choice == 'random favourite': self.play_random_album_menu_item_callback(True) elif choice == 'favourite': self.favourites = not self.favourites self.viewmgr.current_view.set_popup_menu(self.popup_menu) elif choice == 'browser prefs': if not self._browser_preferences: self._browser_preferences = Preferences() self._browser_preferences.display_preferences_dialog(self.plugin) elif choice == 'search prefs': try: if not self._search_preferences: from gi.repository import Peas peas = Peas.Engine.get_default() plugin_info = peas.get_plugin_info('coverart_search_providers') module_name = plugin_info.get_module_name() mod = __import__(module_name) sp = getattr(mod, "SearchPreferences") self._search_preferences = sp() self._search_preferences.plugin_info = plugin_info self._search_preferences.display_preferences_dialog(self._search_preferences) except: dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("Please install and activate the latest version of the Coverart Search Providers plugin")) dialog.run() dialog.destroy() else: assert 1==2, ("unknown choice %s", choice) @classmethod def get_instance(cls, **kwargs): ''' Returns the unique instance of the manager. ''' if not cls.instance: cls.instance = CoverArtBrowserSource(**kwargs) return cls.instance
def do_create_configure_widget(self): ''' Creates the plugin's preferences dialog ''' # create the ui builder = Gtk.Builder() builder.add_from_file(rb.find_plugin_file(self, 'ui/coverart_browser_prefs.ui')) builder.connect_signals(self) gs = GSetting() # bind the toggles to the settings toggle_statusbar = builder.get_object('custom_statusbar_checkbox') self.settings.bind(gs.PluginKey.CUSTOM_STATUSBAR, toggle_statusbar, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_bottom = builder.get_object('display_bottom_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_BOTTOM, toggle_bottom, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_text = builder.get_object('display_text_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, toggle_text, 'active', Gio.SettingsBindFlags.DEFAULT) box_text = builder.get_object('display_text_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, box_text, 'sensitive', Gio.SettingsBindFlags.GET) toggle_text_ellipsize = builder.get_object( 'display_text_ellipsize_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, toggle_text_ellipsize, 'active', Gio.SettingsBindFlags.DEFAULT) box_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, box_text_ellipsize_length, 'sensitive', Gio.SettingsBindFlags.GET) spinner_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_spin') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE_LENGTH, spinner_text_ellipsize_length, 'value', Gio.SettingsBindFlags.DEFAULT) spinner_font_size = builder.get_object( 'display_font_spin') self.settings.bind(gs.PluginKey.DISPLAY_FONT_SIZE, spinner_font_size, 'value', Gio.SettingsBindFlags.DEFAULT) cover_size_scale = builder.get_object('cover_size_adjustment') self.settings.bind(gs.PluginKey.COVER_SIZE, cover_size_scale, 'value', Gio.SettingsBindFlags.DEFAULT) add_shadow = builder.get_object('add_shadow_checkbox') self.settings.bind(gs.PluginKey.ADD_SHADOW, add_shadow, 'active', Gio.SettingsBindFlags.DEFAULT) rated_box = builder.get_object('rated_box') self.stars = ReactiveStar(size=StarSize.BIG) self.stars.connect('changed', self.rating_changed_callback) align = Gtk.Alignment.new(0.5, 0, 0, 0.1) align.add(self.stars) rated_box.add(align) self.stars.set_rating(self.settings[gs.PluginKey.RATING]) autostart = builder.get_object('autostart_checkbox') self.settings.bind(gs.PluginKey.AUTOSTART, autostart, 'active', Gio.SettingsBindFlags.DEFAULT) embedded_search = builder.get_object('embedded_checkbox') self.settings.bind(gs.PluginKey.EMBEDDED_SEARCH, embedded_search, 'active', Gio.SettingsBindFlags.DEFAULT) discogs_search = builder.get_object('discogs_checkbox') self.settings.bind(gs.PluginKey.DISCOGS_SEARCH, discogs_search, 'active', Gio.SettingsBindFlags.DEFAULT) toolbar_pos_combo = builder.get_object('show_in_combobox') renderer = Gtk.CellRendererText() toolbar_pos_combo.pack_start(renderer, True) toolbar_pos_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.TOOLBAR_POS, toolbar_pos_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) light_source_combo = builder.get_object('light_source_combobox') renderer = Gtk.CellRendererText() light_source_combo.pack_start(renderer, True) light_source_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.SHADOW_IMAGE, light_source_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) # return the dialog return builder.get_object('main_notebook')
def _create_display_contents(self, plugin): print ("DEBUG - create_display_contents") # create the ui self._first_run = True cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) builder = Gtk.Builder() builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN) builder.add_from_file(rb.find_plugin_file(plugin, 'ui/coverart_browser_prefs.ui')) self.launchpad_button = builder.get_object('show_launchpad') self.launchpad_label = builder.get_object('launchpad_label') builder.connect_signals(self) #. TRANSLATORS: Do not translate this string. translators = _('translator-credits') if translators != "translator-credits": self.launchpad_label.set_text(translators) else: self.launchpad_button.set_visible(False) gs = GSetting() # bind the toggles to the settings toggle_statusbar = builder.get_object('custom_statusbar_checkbox') self.settings.bind(gs.PluginKey.CUSTOM_STATUSBAR, toggle_statusbar, 'active', Gio.SettingsBindFlags.DEFAULT) toggle_text = builder.get_object('display_text_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, toggle_text, 'active', Gio.SettingsBindFlags.DEFAULT) box_text = builder.get_object('display_text_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT, box_text, 'sensitive', Gio.SettingsBindFlags.GET) self.display_text_pos = self.settings[gs.PluginKey.DISPLAY_TEXT_POS] self.display_text_under_radiobutton = builder.get_object('display_text_under_radiobutton') self.display_text_within_radiobutton = builder.get_object('display_text_within_radiobutton') if self.display_text_pos: self.display_text_under_radiobutton.set_active(True) else: self.display_text_within_radiobutton.set_active(True) random_scale = builder.get_object('random_adjustment') self.settings.bind(gs.PluginKey.RANDOM, random_scale, 'value', Gio.SettingsBindFlags.DEFAULT) toggle_text_ellipsize = builder.get_object( 'display_text_ellipsize_checkbox') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, toggle_text_ellipsize, 'active', Gio.SettingsBindFlags.DEFAULT) box_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_box') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE, box_text_ellipsize_length, 'sensitive', Gio.SettingsBindFlags.GET) spinner_text_ellipsize_length = builder.get_object( 'display_text_ellipsize_length_spin') self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE_LENGTH, spinner_text_ellipsize_length, 'value', Gio.SettingsBindFlags.DEFAULT) spinner_font_size = builder.get_object( 'display_font_spin') self.settings.bind(gs.PluginKey.DISPLAY_FONT_SIZE, spinner_font_size, 'value', Gio.SettingsBindFlags.DEFAULT) cover_size_scale = builder.get_object('cover_size_adjustment') self.settings.bind(gs.PluginKey.COVER_SIZE, cover_size_scale, 'value', Gio.SettingsBindFlags.DEFAULT) add_shadow = builder.get_object('add_shadow_checkbox') self.settings.bind(gs.PluginKey.ADD_SHADOW, add_shadow, 'active', Gio.SettingsBindFlags.DEFAULT) rated_box = builder.get_object('rated_box') self.stars = ReactiveStar(size=StarSize.BIG) self.stars.connect('changed', self.rating_changed_callback) align = Gtk.Alignment.new(0.5, 0, 0, 0.1) align.add(self.stars) rated_box.add(align) self.stars.set_rating(self.settings[gs.PluginKey.RATING]) autostart = builder.get_object('autostart_checkbox') self.settings.bind(gs.PluginKey.AUTOSTART, autostart, 'active', Gio.SettingsBindFlags.DEFAULT) toolbar_pos_combo = builder.get_object('show_in_combobox') renderer = Gtk.CellRendererText() toolbar_pos_combo.pack_start(renderer, True) toolbar_pos_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.TOOLBAR_POS, toolbar_pos_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) light_source_combo = builder.get_object('light_source_combobox') renderer = Gtk.CellRendererText() light_source_combo.pack_start(renderer, True) light_source_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.SHADOW_IMAGE, light_source_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) combo_liststore = builder.get_object('combo_liststore') from coverart_utils import Theme for theme in Theme(self).themes: combo_liststore.append([theme, theme]) theme_combo = builder.get_object('theme_combobox') renderer = Gtk.CellRendererText() theme_combo.pack_start(renderer, True) theme_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.THEME, theme_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) button_relief = builder.get_object('button_relief_checkbox') self.settings.bind(gs.PluginKey.BUTTON_RELIEF, button_relief, 'active', Gio.SettingsBindFlags.DEFAULT) # create user data files cachedir = RB.user_cache_dir() + "/coverart_browser/usericons" if not os.path.exists(cachedir): os.makedirs(cachedir) popup = cachedir + "/popups.xml" temp = RB.find_user_data_file('plugins/coverart_browser/img/usericons/popups.xml') # lets see if there is a legacy file - if necessary copy it to the cache dir if os.path.isfile(temp) and not os.path.isfile(popup): shutil.copyfile(temp, popup) if not os.path.isfile(popup): template = rb.find_plugin_file(plugin, 'template/popups.xml') folder = os.path.split(popup)[0] if not os.path.exists(folder): os.makedirs(folder) shutil.copyfile(template, popup) # now prepare the genre tab from coverart_utils import GenreConfiguredSpriteSheet from coverart_utils import get_stock_size self._sheet = GenreConfiguredSpriteSheet(plugin, "genre", get_stock_size()) self.alt_liststore = builder.get_object('alt_liststore') self.alt_user_liststore = builder.get_object('alt_user_liststore') self._iters = {} for key in list(self._sheet.keys()): store_iter = self.alt_liststore.append([key, self._sheet[key]]) self._iters[(key, self.GENRE_POPUP)] = store_iter for key, value in self._sheet.genre_alternate.items(): if key.genre_type == GenreConfiguredSpriteSheet.GENRE_USER: store_iter = self.alt_user_liststore.append([key.name, self._sheet[self._sheet.genre_alternate[key]], self._sheet.genre_alternate[key]]) self._iters[(key.name, self.GENRE_LIST)] = store_iter self.amend_mode = False self.blank_iter = None self.genre_combobox = builder.get_object('genre_combobox') self.genre_entry = builder.get_object('genre_entry') self.genre_view = builder.get_object('genre_view') self.save_button = builder.get_object('save_button') self.filechooserdialog = builder.get_object('filechooserdialog') last_genre_folder = self.settings[gs.PluginKey.LAST_GENRE_FOLDER] if last_genre_folder != "": self.filechooserdialog.set_current_folder(last_genre_folder) padding_scale = builder.get_object('padding_adjustment') self.settings.bind(gs.PluginKey.ICON_PADDING, padding_scale, 'value', Gio.SettingsBindFlags.DEFAULT) spacing_scale = builder.get_object('spacing_adjustment') self.settings.bind(gs.PluginKey.ICON_SPACING, spacing_scale, 'value', Gio.SettingsBindFlags.DEFAULT) icon_automatic = builder.get_object('icon_automatic_checkbox') self.settings.bind(gs.PluginKey.ICON_AUTOMATIC, icon_automatic, 'active', Gio.SettingsBindFlags.DEFAULT) #flow tab flow_combo = builder.get_object('flow_combobox') renderer = Gtk.CellRendererText() flow_combo.pack_start(renderer, True) flow_combo.add_attribute(renderer, 'text', 1) self.settings.bind(gs.PluginKey.FLOW_APPEARANCE, flow_combo, 'active-id', Gio.SettingsBindFlags.DEFAULT) flow_hide = builder.get_object('hide_caption_checkbox') self.settings.bind(gs.PluginKey.FLOW_HIDE_CAPTION, flow_hide, 'active', Gio.SettingsBindFlags.DEFAULT) flow_scale = builder.get_object('cover_scale_adjustment') self.settings.bind(gs.PluginKey.FLOW_SCALE, flow_scale, 'value', Gio.SettingsBindFlags.DEFAULT) flow_width = builder.get_object('cover_width_adjustment') self.settings.bind(gs.PluginKey.FLOW_WIDTH, flow_width, 'value', Gio.SettingsBindFlags.DEFAULT) flow_max = builder.get_object('flow_max_adjustment') self.settings.bind(gs.PluginKey.FLOW_MAX, flow_max, 'value', Gio.SettingsBindFlags.DEFAULT) flow_automatic = builder.get_object('automatic_checkbox') self.settings.bind(gs.PluginKey.FLOW_AUTOMATIC, flow_automatic, 'active', Gio.SettingsBindFlags.DEFAULT) self.background_colour = self.settings[gs.PluginKey.FLOW_BACKGROUND_COLOUR] self.white_radiobutton = builder.get_object('white_radiobutton') self.black_radiobutton = builder.get_object('black_radiobutton') if self.background_colour == 'W': self.white_radiobutton.set_active(True) else: self.black_radiobutton.set_active(True) # return the dialog self._first_run = False print ("end create dialog contents") return builder.get_object('main_notebook')
def _setup_source(self): ''' Setups the differents parts of the source so they are ready to be used by the user. It also creates and configure some custom widgets. ''' print "CoverArtBrowser DEBUG - _setup_source" # setup iconview drag&drop support self.covers_view.enable_model_drag_dest([], Gdk.DragAction.COPY) self.covers_view.drag_dest_add_image_targets() self.covers_view.drag_dest_add_text_targets() self.covers_view.connect('drag-drop', self.on_drag_drop) self.covers_view.connect('drag-data-received', self.on_drag_data_received) # setup entry-view objects and widgets y = self.gs.get_value(self.gs.Path.PLUGIN, self.gs.PluginKey.PANED_POSITION) self.paned.set_position(y) # create entry view. Don't allow to reorder until the load is finished self.entry_view = EV(self.shell, self) self.entry_view.set_columns_clickable(False) self.shell.props.library_source.get_entry_view().set_columns_clickable( False) self.stars = ReactiveStar() self.stars.set_rating(0) a = Gtk.Alignment.new(0.5, 0.5, 0, 0) a.add(self.stars) self.stars.connect('changed', self.rating_changed_callback) vbox = Gtk.Box() vbox.set_orientation(Gtk.Orientation.VERTICAL) vbox.pack_start(self.entry_view, True, True, 0) vbox.pack_start(a, False, False, 1) vbox.show_all() self.notebook.append_page(vbox, Gtk.Label(_("Tracks"))) # create an album manager self.album_manager = AlbumManager(self.plugin, self.covers_view) # setup cover search pane try: color = self.covers_view.get_style_context().get_background_color( Gtk.StateFlags.SELECTED) color = '#%s%s%s' % ( str(hex(int(color.red * 255))).replace('0x', ''), str(hex(int(color.green * 255))).replace('0x', ''), str(hex(int(color.blue * 255))).replace('0x', '')) except: color = '#0000FF' self.cover_search_pane = CoverSearchPane(self.plugin, self.album_manager, color) self.notebook.append_page(self.cover_search_pane, Gtk.Label( _("Covers"))) # connect a signal to when the info of albums is ready self.load_fin_id = self.album_manager.loader.connect( 'model-load-finished', self.load_finished_callback) # prompt the loader to load the albums self.album_manager.loader.load_albums(self.props.query_model) # set the model to the view self.covers_view.set_model(self.album_manager.model.store) print "CoverArtBrowser DEBUG - end _setup_source"