Example #1
0
    def do_button_press_event(self, event):
        '''
        Other than the default behavior, adds an event firing when the mouse
        has clicked on top of a current item, informing the listeners of the
        path of the clicked item.
        '''
        x = int(event.x)
        y = int(event.y)
        current_path = self.get_path_at_pos(x, y)

        if event.type is Gdk.EventType.BUTTON_PRESS and current_path:
            if event.triggers_context_menu():
                # if the item being clicked isn't selected, we should clear
                # the current selection
                if len(self.get_selected_objects()) > 0 and \
                    not self.path_is_selected(current_path):
                    self.unselect_all()

                self.select_path(current_path)
                self.set_cursor(current_path, None, False)

                if self.popup:
                    if not self._external_plugins:
                        # initialise external plugin menu support
                        self._external_plugins = \
                        CreateExternalPluginMenu("ca_covers_view", self.shell)
                    self._external_plugins.create_menu(self.popup,
                                                       self.ext_menu_pos, True)
                    self.popup.popup(None, None, None, None, event.button,
                                     event.time)
            else:
                self.emit('item-clicked', event, current_path)

        Gtk.IconView.do_button_press_event(self, event)
Example #2
0
class CoverArtPlayEntryView(CoverArtEntryView):
    __hash__ = GObject.__hash__

    def __init__(self, shell, source):
        '''
        Initializes the entryview.
        '''
        super(CoverArtPlayEntryView, self).__init__(shell, source)

    def define_menu(self):
        popup = Menu(self.plugin, self.shell)
        popup.load_from_file('N/A', 'ui/coverart_play_pop_rb3.ui')
        signals = {
            'remove_from_playlist_menu_item':
            self.remove_from_playlist_menu_item_callback
        }

        popup.connect_signals(signals)
        popup.connect('pre-popup', self.pre_popup_menu_callback)
        self.popup = popup

    def pre_popup_menu_callback(self, *args):
        '''
        Callback when the popup menu is about to be displayed
        '''

        if not self.external_plugins:
            self.external_plugins = \
                CreateExternalPluginMenu("playlist_entry_view", 1, self.popup)
            self.external_plugins.create_menu('play_popup_menu')

    def remove_from_playlist_menu_item_callback(self, *args):
        print("remove_from_playlist_menu_item_callback")
        entries = self.get_selected_entries()
        for entry in entries:
            print(entry)
            self.source.source_query_model.remove_entry(entry)

    def do_show_popup(self, over_entry):
        if over_entry:
            print("CoverArtBrowser DEBUG - do_show_popup()")

            self.popup.popup(self.source, 'play_popup_menu', 0,
                             Gtk.get_current_event_time())

        return over_entry

    def play_track_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - play_track_menu_item_callback()")

        selected = self.get_selected_entries()
        entry = selected[0]

        # Start the music
        player = self.shell.props.shell_player
        player.play_entry(entry, self.source)

        print("CoverArtBrowser DEBUG - play_track_menu_item_callback()")
class CoverArtPlayEntryView(CoverArtEntryView):
    __hash__ = GObject.__hash__

    def __init__(self, shell, source):
        '''
        Initializes the entryview.
        '''
        super(CoverArtPlayEntryView, self).__init__(shell, source)

    def define_menu(self):
        popup = Menu(self.plugin, self.shell)
        popup.load_from_file('N/A',
                             'ui/coverart_play_pop_rb3.ui')
        signals = {
            'remove_from_playlist_menu_item': self.remove_from_playlist_menu_item_callback
        }

        popup.connect_signals(signals)
        popup.connect('pre-popup', self.pre_popup_menu_callback)
        self.popup = popup

    def pre_popup_menu_callback(self, *args):
        '''
        Callback when the popup menu is about to be displayed
        '''

        if not self.external_plugins:
            self.external_plugins = \
                CreateExternalPluginMenu("playlist_entry_view", 1, self.popup)
            self.external_plugins.create_menu('play_popup_menu')

    def remove_from_playlist_menu_item_callback(self, *args):
        print("remove_from_playlist_menu_item_callback")
        entries = self.get_selected_entries()
        for entry in entries:
            print(entry)
            self.source.source_query_model.remove_entry(entry)

    def do_show_popup(self, over_entry):
        if over_entry:
            print("CoverArtBrowser DEBUG - do_show_popup()")

            self.popup.popup(self.source,
                             'play_popup_menu', 0, Gtk.get_current_event_time())

        return over_entry

    def play_track_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - play_track_menu_item_callback()")

        selected = self.get_selected_entries()
        entry = selected[0]

        # Start the music
        player = self.shell.props.shell_player
        player.play_entry(entry, self.source)

        print("CoverArtBrowser DEBUG - play_track_menu_item_callback()")
Example #4
0
    def pre_popup_menu_callback(self, *args):
        '''
        Callback when the popup menu is about to be displayed
        '''

        if not self.external_plugins:
            self.external_plugins = \
                CreateExternalPluginMenu("playlist_entry_view", 1, self.popup)
            self.external_plugins.create_menu('play_popup_menu')
    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 pre_display_popup(self):
     if not self._external_plugins:
         # initialise external plugin menu support
         self._external_plugins = \
         CreateExternalPluginMenu("ca_covers_view",
             self.ext_menu_pos, self.popup)
         self._external_plugins.create_menu('popup_menu', True)
    def do_button_press_event(self, event):
        '''
        Other than the default behavior, adds an event firing when the mouse
        has clicked on top of a current item, informing the listeners of the
        path of the clicked item.
        '''
        x = int(event.x)
        y = int(event.y)
        current_path = self.get_path_at_pos(x, y)

        if event.type is Gdk.EventType.BUTTON_PRESS and current_path:
            if event.triggers_context_menu():
                # if the item being clicked isn't selected, we should clear
                # the current selection
                if len(self.get_selected_objects()) > 0 and \
                    not self.path_is_selected(current_path):
                    self.unselect_all()

                self.select_path(current_path)
                self.set_cursor(current_path, None, False)

                if self.popup:
                    if not self._external_plugins:
                        # initialise external plugin menu support
                        self._external_plugins = \
                        CreateExternalPluginMenu("ca_covers_view", self.shell)
                    self._external_plugins.create_menu(self.popup,
                        self.ext_menu_pos, True)
                    self.popup.popup(None, None, None, None, event.button,
                        event.time)
            else:
                self.emit('item-clicked', event, current_path)

        Gtk.IconView.do_button_press_event(self, event)
    def pre_popup_menu_callback(self, *args):
        '''
        Callback when the popup menu is about to be displayed
        '''

        if not self.external_plugins:
            self.external_plugins = \
                CreateExternalPluginMenu("playlist_entry_view", 1, self.popup)
            self.external_plugins.create_menu('play_popup_menu')
    def pre_popup_menu_callback(self, *args):
        '''
        Callback when the popup menu is about to be displayed
        '''

        state, sensitive = self.shell.props.shell_player.get_playing()
        if not state:
            sensitive = False

        self.popup.set_sensitive('ev_full_add_to_playing_menu_item', sensitive)
        self.popup.set_sensitive('ev_full_play_next_track_menu_item', sensitive)

        if not self.external_plugins:
            self.external_plugins = \
                CreateExternalPluginMenu("ev_full_entryview", 6, self.popup)
            self.external_plugins.create_menu('entryview_full_popup_menu')

        self.playlist_menu_item_callback()
 def add_external_menu(self, *args):
     '''
     Callback when the popup menu is about to be displayed
     '''
     if not self.external_plugins:
         self.external_plugins = \
                 CreateExternalPluginMenu("ev_entryview", 4, self.popup)
         self.external_plugins.create_menu('entryview_popup_menu')
         
     self.playlist_menu_item_callback()
    def do_show_popup(self, over_entry):
        if over_entry:
            print("CoverArtBrowser DEBUG - do_show_popup()")
            if not self.external_plugins:
                self.external_plugins = \
                    CreateExternalPluginMenu("ev_entryview", 3, self.popup)
            self.external_plugins.create_menu('entryview_popup_menu')
            self.popup.get_gtkmenu(self.source,
                'entryview_popup_menu').popup(None, None, None, None, 0,
                Gtk.get_current_event_time())

        return over_entry
    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 item_rightclicked_callback(self, album):
     if not self._external_plugins:
         # initialise external plugin menu support
         self._external_plugins = \
         CreateExternalPluginMenu("ca_covers_view",
             self.ext_menu_pos, self.popup)
         self._external_plugins.create_menu('popup_menu', True)
         
     self.last_album = album
     
     self.popup.get_gtkmenu(self.source, 'popup_menu').popup(None,
                     None, 
                     None,
                     None,
                     3,
                     Gtk.get_current_event_time())
    def pre_popup_menu_callback(self, *args):
        '''
        Callback when the popup menu is about to be displayed
        '''

        state, sensitive = self.shell.props.shell_player.get_playing()
        if not state:
            sensitive = False

        self.popup.set_sensitive('ev_full_add_to_playing_menu_item', sensitive)
        self.popup.set_sensitive('ev_full_play_next_track_menu_item', sensitive)

        if not self.external_plugins:
            self.external_plugins = \
                CreateExternalPluginMenu("ev_full_entryview", 6, self.popup)
            self.external_plugins.create_menu('entryview_full_popup_menu')

        self.playlist_menu_item_callback()
class CoverIconView(EnhancedIconView, AbstractView):
    __gtype_name__ = "CoverIconView"

    icon_spacing = GObject.property(type=int, default=0)
    icon_padding = GObject.property(type=int, default=0)
    icon_automatic = GObject.property(type=bool, default=True)

    display_text_enabled = GObject.property(type=bool, default=False)
    name = 'coverview'

    def __init__(self, *args, **kwargs):
        super(CoverIconView, self).__init__(*args, **kwargs)
        
        self.ext_menu_pos = 0
        self._external_plugins = None
        self.gs = GSetting()
        # custom text renderer
        self._text_renderer = None
        self.show_policy = AlbumShowingPolicy(self)
        self.view = self
        self._has_initialised = False
        
    def initialise(self, source):
        if self._has_initialised:
            return
            
        self._has_initialised = True

        self.view_name = "covers_view"
        self.source = source
        self.plugin = source.plugin
        self.shell = source.shell
        self.album_manager = source.album_manager
        self.ext_menu_pos = 10

        # setup iconview drag&drop support
        # first drag and drop on the coverart view to receive coverart
        self.enable_model_drag_dest([], Gdk.DragAction.COPY)
        self.drag_dest_add_image_targets()
        self.drag_dest_add_text_targets()
        self.connect('drag-drop', self.on_drag_drop)
        self.connect('drag-data-received',
            self.on_drag_data_received)
        self.connect('drag-begin', self.on_drag_begin)
        self.source.paned.connect("expanded", self.bottom_expander_expanded_callback)
        
        self.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK,
            [], Gdk.DragAction.COPY)

        # lastly support drag-drop from coverart to devices/nautilus etc
        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.drag_source_set_target_list(targets)
        self.connect("drag-data-get", self.on_drag_data_get)

        # set the model to the view
        self.set_model(self.album_manager.model.store)

        self._connect_properties()
        self._connect_signals()

        self._activate_markup()
        self.on_notify_icon_padding()
        self.on_notify_icon_spacing()

    def _connect_properties(self):
        setting = self.gs.get_setting(self.gs.Path.PLUGIN)
        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)

        setting.bind(self.gs.PluginKey.DISPLAY_TEXT, self,
            'display_text_enabled', Gio.SettingsBindFlags.GET)

        setting.bind(self.gs.PluginKey.ICON_AUTOMATIC, self,
            'icon_automatic', Gio.SettingsBindFlags.GET)

    def _connect_signals(self):
        self.connect("item-clicked", self.item_clicked_callback)
        self.connect("selection-changed", self.selectionchanged_callback)
        self.connect("item-activated", self.item_activated_callback)        
        self.connect('notify::icon-spacing',
            self.on_notify_icon_spacing)
        self.connect('notify::icon-padding',
            self.on_notify_icon_padding)
        self.connect('notify::display-text-enabled',
            self._activate_markup)

    def get_view_icon_name(self):
        return "iconview.png"

    def resize_icon(self, cover_size):
        '''
        Callback called when to resize the icon
        [common to all views]
        '''
        self.set_item_width(cover_size)

    def pre_display_popup(self):
        if not self._external_plugins:
            # initialise external plugin menu support
            self._external_plugins = \
            CreateExternalPluginMenu("ca_covers_view",
                self.ext_menu_pos, self.popup)
            self._external_plugins.create_menu('popup_menu', True)
            
    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.
        '''

        # stop the propagation of the signal (deactivates superclass callback)
        if rb3compat.is_rb3(self.shell):
            widget.stop_emission_by_name('drag-drop')
        else:
            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.drag_dest_find_target(context, None)
            widget.drag_get_data(context, target, time)

        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.
        '''

        # stop the propagation of the signal (deactivates superclass callback)
        if rb3compat.is_rb3(self.shell):
            widget.stop_emission_by_name('drag-data-received')
        else:
            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)


    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
        '''

        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)
        if rb3compat.is_rb3(self.shell):
            widget.stop_emission_by_name('drag-data-get')
        else:
            widget.stop_emission('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)
        if rb3compat.is_rb3(self.shell):
            widget.stop_emission_by_name('drag-begin')
        else:
            widget.stop_emission('drag-begin')

    def item_clicked_callback(self, iconview, event, path):
        '''
        Callback called when the user clicks somewhere on the cover_view.
        Along with source "show_hide_pane", 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

        if self.icon_automatic:
            self.source.click_count += 1 if not ctrl and not shift else 0

        if self.source.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.source.show_hide_pane, album)

    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.source.play_selected_album()

        return True

    def on_notify_icon_padding(self, *args):
        '''
        Callback called when the icon-padding gsetting value is changed
        '''
        self.set_item_padding(self.icon_padding)

    def on_notify_icon_spacing(self, *args):
        '''
        Callback called when the icon-spacing gsetting value is changed
        '''
        self.set_row_spacing(self.icon_spacing)
        self.set_column_spacing(self.icon_spacing)

    def _create_and_configure_renderer(self):
        self._text_renderer = Gtk.CellRendererText()

        self._text_renderer.props.alignment = Pango.Alignment.CENTER
        self._text_renderer.props.wrap_mode = Pango.WrapMode.WORD
        self._text_renderer.props.xalign = 0.5
        self._text_renderer.props.yalign = 0
        self._text_renderer.props.width = \
            self.album_manager.cover_man.cover_size
        self._text_renderer.props.wrap_width = \
            self.album_manager.cover_man.cover_size

    def _activate_markup(self, *args):
        '''
        Utility method to activate/deactivate the markup text on the
        cover view.
        '''
        if self.display_text_enabled:
            if not self._text_renderer:
                # create and configure the custom cell renderer
                self._create_and_configure_renderer()

            # set the renderer
            self.pack_end(self._text_renderer, False)
            self.add_attribute(self._text_renderer,
                'markup', AlbumsModel.columns['markup']) 

        elif self._text_renderer:
            # remove the cell renderer
            self.props.cell_area.remove(self._text_renderer)
            
    def bottom_expander_expanded_callback(self, paned, expand):
        '''
        Callback connected to expanded signal of the paned GtkExpander
        '''
        if expand:
            # accommodate the viewport if there's an album selected
            if self.source.last_selected_album:
                def scroll_to_album(*args):
                    # accommodate the viewport if there's an album selected
                    path = self.album_manager.model.get_path(
                        self.source.last_selected_album)

                    self.scroll_to_path(path, False, 0, 0)
                    
                    return False

                Gdk.threads_add_idle(GObject.PRIORITY_DEFAULT_IDLE,
                    scroll_to_album, None)


    def switch_to_view(self, source, album):
        self.initialise(source)
        self.show_policy.initialise(source.album_manager)
        if album:
            path = source.album_manager.model.get_path(album)
            self.select_and_scroll_to_path(path)

    def grab_focus(self):
        super(EnhancedIconView, self).grab_focus()
class EnhancedIconView(Gtk.IconView):
    __gtype_name__ = "EnhancedIconView"

    # signals
    __gsignals__ = {
        'item-clicked': (GObject.SIGNAL_RUN_LAST, None, (object, object))
        }

    object_column = GObject.property(type=int, default= -1)

    def __init__(self, *args, **kwargs):
        super(EnhancedIconView, self).__init__(*args, **kwargs)

        self.popup = None
        self._reallocate_count = 0
        self.view_name = None
        self._external_plugins = None
        self.shell = None
        self.ext_menu_pos = 0

    def do_size_allocate(self, allocation):
        '''
        Forces the reallocation of the IconView columns when the width of the
        widgets changes. Neverthless, it takes into account that multiple
        reallocations could happen in a short amount of time, so it avoids
        trying to refresh until the user has stopped resizing the component.
        '''
        if self.get_allocated_width() != allocation.width:
            # don't need to reaccommodate if it's a vertical change
            self._reallocate_count += 1
            Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 500,
                        self._reallocate_columns, None)

        Gtk.IconView.do_size_allocate(self, allocation)

    def _reallocate_columns(self, *args):
        self._reallocate_count -= 1

        if not self._reallocate_count:
            self.set_columns(0)
            self.set_columns(-1)

    def do_button_press_event(self, event):
        '''
        Other than the default behavior, adds an event firing when the mouse
        has clicked on top of a current item, informing the listeners of the
        path of the clicked item.
        '''
        x = int(event.x)
        y = int(event.y)
        current_path = self.get_path_at_pos(x, y)

        if event.type is Gdk.EventType.BUTTON_PRESS and current_path:
            if event.triggers_context_menu():
                # if the item being clicked isn't selected, we should clear
                # the current selection
                if len(self.get_selected_objects()) > 0 and \
                    not self.path_is_selected(current_path):
                    self.unselect_all()

                self.select_path(current_path)
                self.set_cursor(current_path, None, False)

                if self.popup:
                    if not self._external_plugins:
                        # initialise external plugin menu support
                        self._external_plugins = \
                        CreateExternalPluginMenu("ca_covers_view", self.shell)
                    self._external_plugins.create_menu(self.popup,
                        self.ext_menu_pos, True)
                    self.popup.popup(None, None, None, None, event.button,
                        event.time)
            else:
                self.emit('item-clicked', event, current_path)

        Gtk.IconView.do_button_press_event(self, event)

    def get_selected_objects(self):
        '''
        Helper method that simplifies getting the objects stored on the
        selected items, givent that the object_column property is setted.
        This way there's no need for the client class to repeateadly access the
        correct column to retrieve the object from the raw rows.
        '''
        selected_items = self.get_selected_items()

        if not self.object_column:
            # if no object_column is setted, return the selected rows
            return selected_items

        model = self.get_model()
        selected_objects = list(reversed([model[selected][self.object_column]
            for selected in selected_items]))

        return selected_objects

    def select_and_scroll_to_path(self, path):
        '''
        Helper method to select and scroll to a given path on the IconView.
        '''
        self.unselect_all()
        self.select_path(path)
        self.set_cursor(path, None, False)
        self.scroll_to_path(path, True, 0.5, 0.5)
class CoverFlowView(AbstractView):
    __gtype_name__ = "CoverFlowView"

    name = 'coverflowview'

    #properties
    flow_background = GObject.property(type=str, default='W')
    flow_automatic = GObject.property(type=bool, default=False)
    flow_scale = GObject.property(type=int, default=100)
    flow_hide = GObject.property(type=bool, default=False)
    flow_width = GObject.property(type=int, default=600)
    flow_appearance = GObject.property(type=str, default='coverflow')
    flow_max = GObject.property(type=int, default=100)
    panedposition = PanedCollapsible.Paned.EXPAND

    def __init__(self):
        super(CoverFlowView, self).__init__()
        
        self.ext_menu_pos = 0
        self._external_plugins = None
        self.show_policy = FlowShowingPolicy(self)
        if webkit_support():
            from gi.repository import WebKit
            self.view = WebKit.WebView()
        else:
            self.view = None
            
        self._last_album = None
        self._has_initialised = False
        self._filter_changed_inprogress = False
        self._on_first_use = True
        
    def _connect_properties(self):
        gs = GSetting()
        settings = gs.get_setting(gs.Path.PLUGIN)
        settings.bind(gs.PluginKey.FLOW_APPEARANCE, self,
            'flow_appearance', Gio.SettingsBindFlags.GET)
        settings.bind(gs.PluginKey.FLOW_HIDE_CAPTION, self,
            'flow_hide', Gio.SettingsBindFlags.GET)
        settings.bind(gs.PluginKey.FLOW_SCALE, self,
            'flow_scale', Gio.SettingsBindFlags.GET)
        settings.bind(gs.PluginKey.FLOW_AUTOMATIC, self,
            'flow_automatic', Gio.SettingsBindFlags.GET)
        settings.bind(gs.PluginKey.FLOW_BACKGROUND_COLOUR, self,
            'flow_background', Gio.SettingsBindFlags.GET)
        settings.bind(gs.PluginKey.FLOW_WIDTH, self,
            'flow_width', Gio.SettingsBindFlags.GET)
        settings.bind(gs.PluginKey.FLOW_MAX, self,
            'flow_max', Gio.SettingsBindFlags.GET)
            
    def _connect_signals(self, source):
        self.connect('notify::flow-background',
            self.filter_changed)
        self.connect('notify::flow-scale',
            self.filter_changed)
        self.connect('notify::flow-hide',
            self.filter_changed)
        self.connect('notify::flow-width',
            self.filter_changed)
        self.connect('notify::flow-appearance',
            self.filter_changed)
        self.connect('notify::flow-max',
            self.filter_changed)

    def filter_changed(self, *args):
        # we can get several filter_changed calls per second
        # lets simplify the processing & potential flickering when the
        # call to this method has slowed stopped
        
        self._filter_changed_event = True

        if self._filter_changed_inprogress:
            return

        self._filter_changed_inprogress = True

        def filter_events(*args):
            if not self._filter_changed_event:
                self._filter_changed()
                self._filter_changed_inprogress = False
            else:
                self._filter_changed_event = False
                return True
                
        Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250, filter_events, None)
        
    
    def _filter_changed(self, *args):
        path = rb.find_plugin_file(self.plugin, 'coverflow/index.html')
        f = open(path)
        string = f.read()
        f.close()
    
        if self.flow_background == 'W':
            background_colour = 'white'
            if len(self.album_manager.model.store) <= self.flow_max:
                foreground_colour = 'white'
            else:
                foreground_colour = 'black'
        else:
            background_colour = 'black'
            if len(self.album_manager.model.store) <= self.flow_max:
                foreground_colour = 'black'
            else:
                foreground_colour = 'white'

        string = string.replace('#BACKGROUND_COLOUR', background_colour)
        string = string.replace('#FOREGROUND_COLOUR', foreground_colour)
        string = string.replace('#FACTOR', str(float(self.flow_scale)/100))

        if  self.flow_hide:
            caption = ""
        else:
            caption = '<div class="globalCaption"></div>'
            
        string = string.replace('#GLOBAL_CAPTION', caption)

        addon = background_colour
        if self.flow_appearance == 'flow-vert':
            addon += " vertical"
        elif self.flow_appearance == 'carousel':
            addon += " carousel"
        elif self.flow_appearance == 'roundabout':
            addon += " roundabout"

        string = string.replace('#ADDON', addon)

        string = string.replace('#WIDTH', str(self.flow_width))

        identifier = self.flow.get_identifier(self.last_album)
        if not identifier:
            identifier = "'start'"
        else:
            identifier = str(identifier)
            
        string = string.replace('#START', identifier)
        
        #TRANSLATORS: for example 'Number of covers limited to 150'
        display_message = _("Number of covers limited to %d") % self.flow_max
        string = string.replace('#MAXCOVERS',
          '<p>' + display_message + '</p>')

        items = self.flow.initialise(self.album_manager.model, self.flow_max)

        string = string.replace('#ITEMS', items)
        
        base =  os.path.dirname(path) + "/"
        Gdk.threads_enter()
        print (string)
        self.view.load_string(string, "text/html", "UTF-8", "file://" + base)
        Gdk.threads_leave()

        if self._on_first_use:
            self._on_first_use = False
            Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250,
                    self.source.show_hide_pane, (self.last_album, PanedCollapsible.Paned.EXPAND))

    def get_view_icon_name(self):
        return "flowview.png"

    def scroll_to_album(self):
        self.flow.scroll_to_album(self.last_album, self.view)
        
    def initialise(self, source):
        if self._has_initialised:
            return
            
        self._has_initialised = True

        self.source = source
        self.plugin = source.plugin
        self.album_manager = source.album_manager
        self.ext_menu_pos = 10
        
        self._connect_properties()
        self._connect_signals(source)
        
        # lets check that all covers have finished loading before
        # initialising the flowcontrol and other signals
        if not self.album_manager.cover_man.has_finished_loading:
            self.album_manager.cover_man.connect('load-finished', self._covers_loaded)
        else:
            self._covers_loaded()

    def _covers_loaded(self, *args):
        self.flow = FlowControl(self)
        self.view.connect("notify::title", self.flow.receive_message_signal)

        #self.album_manager.model.connect('album-updated', self.flow.update_album, self.view)
        #self.album_manager.model.connect('visual-updated', self.flow.update_album, self.view)
        self.album_manager.model.connect('album-updated', self.filter_changed)
        self.album_manager.model.connect('visual-updated', self.filter_changed)
        self.album_manager.model.connect('filter-changed', self.filter_changed)
        
        self.filter_changed()

    @property
    def last_album(self):
        return self._last_album

    @last_album.setter
    def last_album(self, new_album):
        if self._last_album != new_album:
            self._last_album = new_album
            self.source.click_count = 0
            self.selectionchanged_callback()

    def item_rightclicked_callback(self, album):
        if not self._external_plugins:
            # initialise external plugin menu support
            self._external_plugins = \
            CreateExternalPluginMenu("ca_covers_view",
                self.ext_menu_pos, self.popup)
            self._external_plugins.create_menu('popup_menu', True)
            
        self.last_album = album
        
        self.popup.get_gtkmenu(self.source, 'popup_menu').popup(None,
                        None, 
                        None,
                        None,
                        3,
                        Gtk.get_current_event_time())
            
    def item_clicked_callback(self, album):
        '''
        Callback called when the user clicks somewhere on the flow_view.
        Along with source "show_hide_pane", takes care of showing/hiding the bottom
        pane after a second click on a selected album.
        '''
        # to expand the entry view
        if self.flow_automatic:
            self.source.click_count += 1
            
        self.last_album = album

        if self.source.click_count == 1:
            Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250,
                self.source.show_hide_pane, album)

    def item_activated_callback(self, album):
        '''
        Callback called when the flow view is double clicked. It plays the selected album
        '''
        self.last_album = album
        self.source.play_selected_album()

        return True

    def item_drop_callback(self, album, webpath):
        '''
        Callback called when something is dropped onto the flow view - hopefully a webpath
        to a picture
        '''
        print ("item_drop_callback %s" % webpath)
        print ("dropped on album %s" % album)
        self.album_manager.cover_man.update_cover(album, uri=webpath)

    def get_selected_objects(self):
        if self.last_album:
            return [self.last_album]
        else:
            return []

    def select_and_scroll_to_path(self, path):
        album = self.source.album_manager.model.get_from_path(path)
        self.flow.scroll_to_album(album, self.view)
        self.item_clicked_callback(album)

    def switch_to_view(self, source, album):
        self.initialise(source)
        self.show_policy.initialise(source.album_manager)
        
        self.last_album = album
        self.scroll_to_album()
        
    def grab_focus(self):
        self.view.grab_focus()
class CoverArtEntryView(RB.EntryView):

    def __init__(self, shell, source):
        '''
        Initializes the entryview.
        '''
        self.shell = shell
        self.source = source
        self.plugin = self.source.props.plugin

        super(RB.EntryView, self).__init__(db=shell.props.db,
            shell_player=shell.props.shell_player, is_drag_source=True,
            visible_columns=[])

        cl = CoverLocale()
        cl.switch_locale(cl.Locale.RB)

        #self.append_column(RB.EntryViewColumn.TITLE, True)  # always shown
        
        self.col_map = OrderedDict([
                        ('track-number', RB.EntryViewColumn.TRACK_NUMBER),
                        ('title', RB.EntryViewColumn.TITLE),
                        ('genre', RB.EntryViewColumn.GENRE),
                        ('artist', RB.EntryViewColumn.ARTIST),
                        ('album', RB.EntryViewColumn.ALBUM),
                        ('composer', None),
                        ('date', RB.EntryViewColumn.YEAR),
                        ('duration', RB.EntryViewColumn.DURATION),
                        ('bitrate', RB.EntryViewColumn.QUALITY),
                        ('play-count', RB.EntryViewColumn.PLAY_COUNT),
                        ('beats-per-minute', RB.EntryViewColumn.BPM),
                        ('comment', RB.EntryViewColumn.COMMENT),
                        ('location', RB.EntryViewColumn.LOCATION),
                        ('rating', RB.EntryViewColumn.RATING),
                        ('last-played', RB.EntryViewColumn.LAST_PLAYED),
                        ('first-seen', RB.EntryViewColumn.FIRST_SEEN)
                        ])
                        
        # now remove some columns that are only applicable from RB3.0 onwards
        # N.B. 'beats-per-minute': RB.EntryViewColumn.BPM - RB crashes with this - issue#188
        try:
            self.col_map['composer'] = RB.EntryViewColumn.COMPOSER
            # i.e. composer only exists in RB3.0
        except:
            del self.col_map['composer']
            del self.col_map['beats-per-minute']
        
        for entry in self.col_map:
            visible = True if entry == 'title' else False
            self.append_column(self.col_map[entry], visible)
                
        cl.switch_locale(cl.Locale.LOCALE_DOMAIN)

        popup = Menu(self.plugin, self.shell)
        popup.load_from_file('ui/coverart_entryview_pop_rb2.ui',
                             'ui/coverart_entryview_pop_rb3.ui')
        signals = {
            'ev_play_track_menu_item': self.play_track_menu_item_callback,
            'ev_queue_track_menu_item': self.queue_track_menu_item_callback,
            'ev_new_playlist': self.add_playlist_menu_item_callback,
            'ev_show_properties_menu_item': self.show_properties_menu_item_callback,
            'ev_similar_track_menu_item': self.play_similar_track_menu_item_callback,
            'ev_similar_artist_menu_item': self.play_similar_artist_menu_item_callback }
            
        popup.connect_signals(signals)
        popup.connect('pre-popup', self.add_external_menu)
        self.popup = popup

        # connect signals to the shell to know when the playing state changes
        self.shell.props.shell_player.connect('playing-song-changed',
            self.playing_song_changed)
        self.shell.props.shell_player.connect('playing-changed',
            self.playing_changed)

        self.actiongroup = ActionGroup(self.shell, 'coverentryplaylist_submenu')
        
        self.external_plugins = None

        # connect the visible-columns global setting to update our entryview
        gs = GSetting()
        rhythm_settings = gs.get_setting(gs.Path.RBSOURCE)
        rhythm_settings.connect('changed::visible-columns',
            self.on_visible_columns_changed)
        self.on_visible_columns_changed(rhythm_settings, 'visible-columns')

        self.qm = RB.RhythmDBQueryModel.new_empty(self.shell.props.db)
        self.set_model(self.qm)

        # connect the sort-order to the library source sort
        library_view = self.shell.props.library_source.get_entry_view()
        library_view.connect('notify::sort-order',
            self._on_library_sorting_changed)
        self._on_library_sorting_changed(library_view,
            library_view.props.sort_order)

         # connect to the sort-order property
        self.connect('notify::sort-order', self._notify_sort_order,
            library_view)
            
        self.echonest_similar_playlist = None
        self.lastfm_similar_playlist = None

    def __del__(self):
        del self.action_group
        del self.play_action
        del self.queue_action

    def on_visible_columns_changed(self, settings, key):
        print("CoverArtBrowser DEBUG - on_visible_columns_changed()")
        # reset current columns
        print("CoverArtBrowser DEBUG - end on_visible_columns_changed()")
        for entry in self.col_map:
            col = self.get_column(self.col_map[entry])
            if entry in settings[key]:
                col.set_visible(True)
            else:
                if entry != 'title': 
                    col.set_visible(False)
            
        print ("CoverArtBrowser DEBUG - end on_visible_columns_changed()")

    def add_album(self, album):
        print("CoverArtBrowser DEBUG - add_album()")
        tracks = album.get_tracks()

        for track in tracks:
            self.qm.add_entry(track.entry, -1)

        (_, playing) = self.shell.props.shell_player.get_playing()
        self.playing_changed(self.shell.props.shell_player, playing)
        print("CoverArtBrowser DEBUG - add_album()")

    def clear(self):
        print("CoverArtBrowser DEBUG - clear()")
        # self.set_model(RB.RhythmDBQueryModel.new_empty(self.shell.props.db))
        for row in self.qm:
            self.qm.remove_entry(row[0])

        print("CoverArtBrowser DEBUG - clear()")

    def do_entry_activated(self, entry):
        print("CoverArtBrowser DEBUG - do_entry_activated()")
        self.select_entry(entry)
        self.play_track_menu_item_callback(entry)
        print("CoverArtBrowser DEBUG - do_entry_activated()")
        return True
        
    def add_external_menu(self, *args):
        '''
        Callback when the popup menu is about to be displayed
        '''
        if not self.external_plugins:
            self.external_plugins = \
                    CreateExternalPluginMenu("ev_entryview", 4, self.popup)
            self.external_plugins.create_menu('entryview_popup_menu')
            
        self.playlist_menu_item_callback()

    def do_show_popup(self, over_entry):
        if over_entry:
            print("CoverArtBrowser DEBUG - do_show_popup()")
            
            self.popup.popup(self.source,
                'entryview_popup_menu', 0, Gtk.get_current_event_time())

        return over_entry
        
    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 = self.get_selected_entries()
        entry = selected[0]
        self.echonest_similar_playlist.start(entry, reinitialise=True)
                                    
    def play_similar_track_menu_item_callback(self, *args):
        if not self.lastfm_similar_playlist:
            self.lastfm_similar_playlist = \
                LastFMTrackPlaylist(    self.shell,
                                        self.shell.props.queue_source)
                                    
        selected = self.get_selected_entries()
        entry = selected[0]
        self.lastfm_similar_playlist.start(entry, reinitialise=True)
    

    def play_track_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - play_track_menu_item_callback()")
        
        query_model = RB.RhythmDBQueryModel.new_empty(self.shell.props.db)

        selected = self.get_selected_entries()
        entry = selected[0]
        
        if len(selected) == 1:
            query_model.copy_contents(self.qm)
        else:
            self.add_tracks_to_source(query_model)
            
        self.source.props.query_model = query_model

        # Start the music
        player = self.shell.props.shell_player
        player.play_entry(entry, self.source)

        print("CoverArtBrowser DEBUG - play_track_menu_item_callback()")

    def queue_track_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - queue_track_menu_item_callback()")

        self.add_tracks_to_source(self.shell.props.queue_source)

    def add_tracks_to_source(self, source):
        selected = self.get_selected_entries()
        selected.reverse()

        selected = sorted(selected,
            key=lambda song: song.get_ulong(RB.RhythmDBPropType.TRACK_NUMBER))

        for entry in selected:
            source.add_entry(entry, -1)

        print("CoverArtBrowser DEBUG - queue_track_menu_item_callback()")

    def love_track(self, rating):
        '''
        utility function to set the rating for selected tracks
        '''
        selected = self.get_selected_entries()

        for entry in selected:
            self.shell.props.db.entry_set(entry, RB.RhythmDBPropType.RATING,
                rating)

        self.shell.props.db.commit()

    def show_properties_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - show_properties_menu_item_callback()")

        info_dialog = RB.SongInfo(source=self.source, entry_view=self)

        info_dialog.show_all()

        print("CoverArtBrowser DEBUG - show_properties_menu_item_callback()")

    def playing_song_changed(self, shell_player, entry):
        print("CoverArtBrowser DEBUG - playing_song_changed()")

        if entry is not None and self.get_entry_contained(entry):
            self.set_state(RB.EntryViewState.PLAYING)
        else:
            self.set_state(RB.EntryViewState.NOT_PLAYING)

        print("CoverArtBrowser DEBUG - playing_song_changed()")

    def playing_changed(self, shell_player, playing):
        print("CoverArtBrowser DEBUG - playing_changed()")
        entry = shell_player.get_playing_entry()

        if entry is not None and self.get_entry_contained(entry):
            if playing:
                self.set_state(RB.EntryViewState.PLAYING)
            else:
                self.set_state(RB.EntryViewState.PAUSED)
        else:
            self.set_state(RB.EntryViewState.NOT_PLAYING)

        print("CoverArtBrowser DEBUG - playing_changed()")

    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.add_tracks_to_source(playlist)

    def playlist_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - playlist_menu_item_callback")

        self.source.playlist_fillmenu(self.popup, 'ev_playlist_sub_menu_item', 'ev_playlist_section',
            self.actiongroup, self.add_to_static_playlist_menu_item_callback)

    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']
        self.add_tracks_to_source(playlist)

    def _on_library_sorting_changed(self, view, _):
        self._old_sort_order = self.props.sort_order

        self.set_sorting_type(view.props.sort_order)

    def _notify_sort_order(self, view, _, library_view):
        if self.props.sort_order != self._old_sort_order:
            self.resort_model()

            # update library source's view direction
            library_view.set_sorting_type(self.props.sort_order)
Example #19
0
    def __init__(self, shell, source):
        '''
        Initializes the entryview.
        '''
        self.shell = shell
        self.source = source
        self.plugin = self.source.props.plugin

        super(RB.EntryView, self).__init__(db=shell.props.db,
            shell_player=shell.props.shell_player, is_drag_source=True,
            visible_columns=[])

        cl = CoverLocale()
        cl.switch_locale(cl.Locale.RB)

        self.append_column(RB.EntryViewColumn.TRACK_NUMBER, False)
        self.append_column(RB.EntryViewColumn.TITLE, True)  # always shown
        self.append_column(RB.EntryViewColumn.GENRE, False)
        self.append_column(RB.EntryViewColumn.ARTIST, False)
        self.append_column(RB.EntryViewColumn.ALBUM, False)
        self.append_column(RB.EntryViewColumn.DURATION, False)
        self.append_column(RB.EntryViewColumn.COMMENT, False)
        self.append_column(RB.EntryViewColumn.RATING, False)
        self.append_column(RB.EntryViewColumn.QUALITY, False)
        self.append_column(RB.EntryViewColumn.PLAY_COUNT, False)
        self.append_column(RB.EntryViewColumn.LAST_PLAYED, False)
        self.append_column(RB.EntryViewColumn.YEAR, False)
        self.append_column(RB.EntryViewColumn.FIRST_SEEN, False)
        self.append_column(RB.EntryViewColumn.LOCATION, False)
        self.append_column(RB.EntryViewColumn.BPM, False)

        cl.switch_locale(cl.Locale.LOCALE_DOMAIN)

        # UI elements need to be imported.
        ui = Gtk.Builder()
        ui.set_translation_domain(cl.Locale.LOCALE_DOMAIN)
        ui.add_from_file(rb.find_plugin_file(self.plugin,
            'ui/coverart_entryview.ui'))
        ui.connect_signals(self)

        self.popup_menu = ui.get_object('entryview_popup_menu')

        # connect signals to the shell to know when the playing state changes
        self.shell.props.shell_player.connect('playing-song-changed',
            self.playing_song_changed)
        self.shell.props.shell_player.connect('playing-changed',
            self.playing_changed)

        self.playlist_sub_menu_item = ui.get_object('playlist_sub_menu_item')
        self.actiongroup = Gtk.ActionGroup('coverentryplaylist_submenu')
        uim = self.shell.props.ui_manager
        uim.insert_action_group(self.actiongroup)

        self.external_plugins = \
            CreateExternalPluginMenu("ca_entryview", self.shell)

        # connect the visible-columns global setting to update our entryview
        gs = GSetting()
        rhythm_settings = gs.get_setting(gs.Path.RBSOURCE)
        rhythm_settings.connect('changed::visible-columns',
            self.on_visible_columns_changed)
        self.on_visible_columns_changed(rhythm_settings, 'visible-columns')

        self.qm = RB.RhythmDBQueryModel.new_empty(self.shell.props.db)
        self.set_model(self.qm)

        # connect the sort-order to the library source sort
        library_view = self.shell.props.library_source.get_entry_view()
        library_view.connect('notify::sort-order',
            self._on_library_sorting_changed)
        self._on_library_sorting_changed(library_view,
            library_view.props.sort_order)

         # connect to the sort-order property
        self.connect('notify::sort-order', self._notify_sort_order,
            library_view)
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
class CoverArtCompactEntryView(BaseView):
    __hash__ = GObject.__hash__

    def __init__(self, shell, source):
        '''
        Initializes the entryview.
        '''
        super(CoverArtCompactEntryView, self).__init__(shell, source)

    def display_columns(self):

        self.col_map = OrderedDict([
            ('track-number', RB.EntryViewColumn.TRACK_NUMBER),
            ('title', RB.EntryViewColumn.TITLE),
            ('artist', RB.EntryViewColumn.ARTIST),
            ('rating', RB.EntryViewColumn.RATING),
            ('duration', RB.EntryViewColumn.DURATION)
        ])

        for entry in self.col_map:
            visible = False if entry == 'artist' else True
            self.append_column(self.col_map[entry], visible)

    def add_album(self, album):
        super(CoverArtCompactEntryView, self).add_album(album)

        if len(self.artists) > 1:
            self.get_column(RB.EntryViewColumn.ARTIST).set_visible(True)
        else:
            self.get_column(RB.EntryViewColumn.ARTIST).set_visible(False)

    def define_menu(self):
        popup = Menu(self.plugin, self.shell)
        popup.load_from_file('N/A',
                             'ui/coverart_entryview_compact_pop_rb3.ui')
        signals = {
            'ev_compact_play_track_menu_item': self.play_track_menu_item_callback,
            'ev_compact_play_next_track_menu_item': self.play_next_track_menu_item_callback,
            'ev_compact_queue_track_menu_item': self.queue_track_menu_item_callback,
            'ev_compact_add_to_playing_menu_item': self.add_to_playing_menu_item_callback,
            'ev_compact_new_playlist': self.add_playlist_menu_item_callback,
            'ev_compact_show_properties_menu_item': self.show_properties_menu_item_callback,
            'ev_compact_similar_track_menu_item': self.play_similar_track_menu_item_callback,
            'ev_compact_similar_artist_menu_item': self.play_similar_artist_menu_item_callback,
            'ev_compact_similar_genre_menu_item': self.play_similar_genre_menu_item_callback}

        popup.connect_signals(signals)
        popup.connect('pre-popup', self.pre_popup_menu_callback)
        self.popup = popup

    def playlist_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - playlist_menu_item_callback")

        self.source.playlist_fillmenu(self.popup, 'ev_compact_playlist_sub_menu_item', 'ev_compact_playlist_section',
                                      self.actiongroup, self.add_to_static_playlist_menu_item_callback)

    def pre_popup_menu_callback(self, *args):
        '''
        Callback when the popup menu is about to be displayed
        '''

        state, sensitive = self.shell.props.shell_player.get_playing()
        if not state:
            sensitive = False

        self.popup.set_sensitive('ev_compact_add_to_playing_menu_item', sensitive)
        self.popup.set_sensitive('ev_compact_play_next_track_menu_item', sensitive)

        if not self.external_plugins:
            self.external_plugins = \
                CreateExternalPluginMenu("ev_compact_entryview", 6, self.popup)
            self.external_plugins.create_menu('entryview_compact_popup_menu')

        self.playlist_menu_item_callback()
Example #22
0
class EnhancedIconView(Gtk.IconView):
    __gtype_name__ = "EnhancedIconView"

    # signals
    __gsignals__ = {
        'item-clicked': (GObject.SIGNAL_RUN_LAST, None, (object, object))
    }

    object_column = GObject.property(type=int, default=-1)

    def __init__(self, *args, **kwargs):
        super(EnhancedIconView, self).__init__(*args, **kwargs)

        self.popup = None
        self._reallocate_count = 0
        self.view_name = None
        self._external_plugins = None
        self.shell = None
        self.ext_menu_pos = 0

    def do_size_allocate(self, allocation):
        '''
        Forces the reallocation of the IconView columns when the width of the
        widgets changes. Neverthless, it takes into account that multiple
        reallocations could happen in a short amount of time, so it avoids
        trying to refresh until the user has stopped resizing the component.
        '''
        if self.get_allocated_width() != allocation.width:
            # don't need to reaccommodate if it's a vertical change
            self._reallocate_count += 1
            Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 500,
                                    self._reallocate_columns, None)

        Gtk.IconView.do_size_allocate(self, allocation)

    def _reallocate_columns(self, *args):
        self._reallocate_count -= 1

        if not self._reallocate_count:
            self.set_columns(0)
            self.set_columns(-1)

    def do_button_press_event(self, event):
        '''
        Other than the default behavior, adds an event firing when the mouse
        has clicked on top of a current item, informing the listeners of the
        path of the clicked item.
        '''
        x = int(event.x)
        y = int(event.y)
        current_path = self.get_path_at_pos(x, y)

        if event.type is Gdk.EventType.BUTTON_PRESS and current_path:
            if event.triggers_context_menu():
                # if the item being clicked isn't selected, we should clear
                # the current selection
                if len(self.get_selected_objects()) > 0 and \
                    not self.path_is_selected(current_path):
                    self.unselect_all()

                self.select_path(current_path)
                self.set_cursor(current_path, None, False)

                if self.popup:
                    if not self._external_plugins:
                        # initialise external plugin menu support
                        self._external_plugins = \
                        CreateExternalPluginMenu("ca_covers_view", self.shell)
                    self._external_plugins.create_menu(self.popup,
                                                       self.ext_menu_pos, True)
                    self.popup.popup(None, None, None, None, event.button,
                                     event.time)
            else:
                self.emit('item-clicked', event, current_path)

        Gtk.IconView.do_button_press_event(self, event)

    def get_selected_objects(self):
        '''
        Helper method that simplifies getting the objects stored on the
        selected items, givent that the object_column property is setted.
        This way there's no need for the client class to repeateadly access the
        correct column to retrieve the object from the raw rows.
        '''
        selected_items = self.get_selected_items()

        if not self.object_column:
            # if no object_column is setted, return the selected rows
            return selected_items

        model = self.get_model()
        selected_objects = list(
            reversed([
                model[selected][self.object_column]
                for selected in selected_items
            ]))

        return selected_objects

    def select_and_scroll_to_path(self, path):
        '''
        Helper method to select and scroll to a given path on the IconView.
        '''
        self.unselect_all()
        self.select_path(path)
        self.set_cursor(path, None, False)
        self.scroll_to_path(path, True, 0.5, 0.5)
class CoverArtEntryView(RB.EntryView):

    def __init__(self, shell, source):
        '''
        Initializes the entryview.
        '''
        self.shell = shell
        self.source = source
        self.plugin = self.source.props.plugin

        super(RB.EntryView, self).__init__(db=shell.props.db,
            shell_player=shell.props.shell_player, is_drag_source=True,
            visible_columns=[])

        cl = CoverLocale()
        cl.switch_locale(cl.Locale.RB)

        self.append_column(RB.EntryViewColumn.TRACK_NUMBER, False)
        self.append_column(RB.EntryViewColumn.TITLE, True)  # always shown
        self.append_column(RB.EntryViewColumn.GENRE, False)
        self.append_column(RB.EntryViewColumn.ARTIST, False)
        self.append_column(RB.EntryViewColumn.ALBUM, False)
        self.append_column(RB.EntryViewColumn.DURATION, False)
        self.append_column(RB.EntryViewColumn.COMMENT, False)
        self.append_column(RB.EntryViewColumn.RATING, False)
        self.append_column(RB.EntryViewColumn.QUALITY, False)
        self.append_column(RB.EntryViewColumn.PLAY_COUNT, False)
        self.append_column(RB.EntryViewColumn.LAST_PLAYED, False)
        self.append_column(RB.EntryViewColumn.YEAR, False)
        self.append_column(RB.EntryViewColumn.FIRST_SEEN, False)
        self.append_column(RB.EntryViewColumn.LOCATION, False)
        self.append_column(RB.EntryViewColumn.BPM, False)

        cl.switch_locale(cl.Locale.LOCALE_DOMAIN)

        popup = Menu(self.plugin, self.shell)
        popup.load_from_file('ui/coverart_entryview_pop_rb2.ui',
                             'ui/coverart_entryview_pop_rb3.ui')
        signals = {
            'ev_play_track_menu_item': self.play_track_menu_item_callback,
            'ev_queue_track_menu_item': self.queue_track_menu_item_callback,
            'ev_playlist_menu_item': self.playlist_menu_item_callback,
            'ev_new_playlist': self.add_playlist_menu_item_callback,
            'ev_show_properties_menu_item': self.show_properties_menu_item_callback }
            
        popup.connect_signals(signals)
        self.popup = popup

        # connect signals to the shell to know when the playing state changes
        self.shell.props.shell_player.connect('playing-song-changed',
            self.playing_song_changed)
        self.shell.props.shell_player.connect('playing-changed',
            self.playing_changed)

        self.actiongroup = ActionGroup(self.shell, 'coverentryplaylist_submenu')
        
        self.external_plugins = None

        # connect the visible-columns global setting to update our entryview
        gs = GSetting()
        rhythm_settings = gs.get_setting(gs.Path.RBSOURCE)
        rhythm_settings.connect('changed::visible-columns',
            self.on_visible_columns_changed)
        self.on_visible_columns_changed(rhythm_settings, 'visible-columns')

        self.qm = RB.RhythmDBQueryModel.new_empty(self.shell.props.db)
        self.set_model(self.qm)

        # connect the sort-order to the library source sort
        library_view = self.shell.props.library_source.get_entry_view()
        library_view.connect('notify::sort-order',
            self._on_library_sorting_changed)
        self._on_library_sorting_changed(library_view,
            library_view.props.sort_order)

         # connect to the sort-order property
        self.connect('notify::sort-order', self._notify_sort_order,
            library_view)

    def __del__(self):
        del self.action_group
        del self.play_action
        del self.queue_action

    def on_visible_columns_changed(self, settings, key):
        print("CoverArtBrowser DEBUG - on_visible_columns_changed()")
        # reset current columns
        self.props.visible_columns = settings[key]
        print("CoverArtBrowser DEBUG - end on_visible_columns_changed()")

    def add_album(self, album):
        print("CoverArtBrowser DEBUG - add_album()")
        tracks = album.get_tracks()

        for track in tracks:
            self.qm.add_entry(track.entry, -1)

        (_, playing) = self.shell.props.shell_player.get_playing()
        self.playing_changed(self.shell.props.shell_player, playing)
        print("CoverArtBrowser DEBUG - add_album()")

    def clear(self):
        print("CoverArtBrowser DEBUG - clear()")
        # self.set_model(RB.RhythmDBQueryModel.new_empty(self.shell.props.db))
        for row in self.qm:
            self.qm.remove_entry(row[0])

        print("CoverArtBrowser DEBUG - clear()")

    def do_entry_activated(self, entry):
        print("CoverArtBrowser DEBUG - do_entry_activated()")
        self.select_entry(entry)
        self.play_track_menu_item_callback(entry)
        print("CoverArtBrowser DEBUG - do_entry_activated()")
        return True

    def do_show_popup(self, over_entry):
        if over_entry:
            print("CoverArtBrowser DEBUG - do_show_popup()")
            if not self.external_plugins:
                self.external_plugins = \
                    CreateExternalPluginMenu("ev_entryview", 3, self.popup)
            self.external_plugins.create_menu('entryview_popup_menu')
            self.popup.get_gtkmenu(self.source,
                'entryview_popup_menu').popup(None, None, None, None, 0,
                Gtk.get_current_event_time())

        return over_entry

    def play_track_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - play_track_menu_item_callback()")
        
        query_model = RB.RhythmDBQueryModel.new_empty(self.shell.props.db)

        selected = self.get_selected_entries()
        entry = selected[0]
        
        if len(selected) == 1:
            query_model.copy_contents(self.qm)
        else:
            self.add_tracks_to_source(query_model)
            
        self.source.props.query_model = query_model

        # Start the music
        player = self.shell.props.shell_player
        player.play_entry(entry, self.source)

        print("CoverArtBrowser DEBUG - play_track_menu_item_callback()")

    def queue_track_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - queue_track_menu_item_callback()")

        self.add_tracks_to_source(self.shell.props.queue_source)

    def add_tracks_to_source(self, source):
        selected = self.get_selected_entries()
        selected.reverse()

        selected = sorted(selected,
            key=lambda song: song.get_ulong(RB.RhythmDBPropType.TRACK_NUMBER))

        for entry in selected:
            source.add_entry(entry, -1)

        print("CoverArtBrowser DEBUG - queue_track_menu_item_callback()")

    def love_track(self, rating):
        '''
        utility function to set the rating for selected tracks
        '''
        selected = self.get_selected_entries()

        for entry in selected:
            self.shell.props.db.entry_set(entry, RB.RhythmDBPropType.RATING,
                rating)

        self.shell.props.db.commit()

    def show_properties_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - show_properties_menu_item_callback()")

        info_dialog = RB.SongInfo(source=self.source, entry_view=self)

        info_dialog.show_all()

        print("CoverArtBrowser DEBUG - show_properties_menu_item_callback()")

    def playing_song_changed(self, shell_player, entry):
        print("CoverArtBrowser DEBUG - playing_song_changed()")

        if entry is not None and self.get_entry_contained(entry):
            self.set_state(RB.EntryViewState.PLAYING)
        else:
            self.set_state(RB.EntryViewState.NOT_PLAYING)

        print("CoverArtBrowser DEBUG - playing_song_changed()")

    def playing_changed(self, shell_player, playing):
        print("CoverArtBrowser DEBUG - playing_changed()")
        entry = shell_player.get_playing_entry()

        if entry is not None and self.get_entry_contained(entry):
            if playing:
                self.set_state(RB.EntryViewState.PLAYING)
            else:
                self.set_state(RB.EntryViewState.PAUSED)
        else:
            self.set_state(RB.EntryViewState.NOT_PLAYING)

        print("CoverArtBrowser DEBUG - playing_changed()")

    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('', False)

        self.add_tracks_to_source(playlist)

    def playlist_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - playlist_menu_item_callback")

        self.source.playlist_fillmenu(self.popup, 'ev_playlist_sub_menu_item', 'ev_playlist_section',
            self.actiongroup, self.add_to_static_playlist_menu_item_callback)

    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']
        self.add_tracks_to_source(playlist)

    def _on_library_sorting_changed(self, view, _):
        self._old_sort_order = self.props.sort_order

        self.set_sorting_type(view.props.sort_order)

    def _notify_sort_order(self, view, _, library_view):
        if self.props.sort_order != self._old_sort_order:
            self.resort_model()

            # update library source's view direction
            library_view.set_sorting_type(self.props.sort_order)
class CoverArtEntryView(BaseView):
    __hash__ = GObject.__hash__

    def __init__(self, shell, source):
        '''
        Initializes the entryview.
        '''
        super(CoverArtEntryView, self).__init__(shell, source)

    def display_columns(self):

        self.col_map = OrderedDict([
            ('track-number', RB.EntryViewColumn.TRACK_NUMBER),
            ('title', RB.EntryViewColumn.TITLE),
            ('genre', RB.EntryViewColumn.GENRE),
            ('artist', RB.EntryViewColumn.ARTIST),
            ('album', RB.EntryViewColumn.ALBUM),
            ('composer', RB.EntryViewColumn.COMPOSER),
            ('date', RB.EntryViewColumn.YEAR),
            ('duration', RB.EntryViewColumn.DURATION),
            ('bitrate', RB.EntryViewColumn.QUALITY),
            ('play-count', RB.EntryViewColumn.PLAY_COUNT),
            ('beats-per-minute', RB.EntryViewColumn.BPM),
            ('comment', RB.EntryViewColumn.COMMENT),
            ('location', RB.EntryViewColumn.LOCATION),
            ('rating', RB.EntryViewColumn.RATING),
            ('last-played', RB.EntryViewColumn.LAST_PLAYED),
            ('first-seen', RB.EntryViewColumn.FIRST_SEEN)
        ])

        for entry in self.col_map:
            visible = True if entry == 'title' else False
            self.append_column(self.col_map[entry], visible)

        # connect the visible-columns global setting to update our entryview
        gs = GSetting()
        rhythm_settings = gs.get_setting(gs.Path.RBSOURCE)
        rhythm_settings.connect('changed::visible-columns',
                                self.on_visible_columns_changed)
        self.on_visible_columns_changed(rhythm_settings, 'visible-columns')

    def on_visible_columns_changed(self, settings, key):
        print("CoverArtBrowser DEBUG - on_visible_columns_changed()")
        # reset current columns
        print("CoverArtBrowser DEBUG - end on_visible_columns_changed()")
        for entry in self.col_map:
            col = self.get_column(self.col_map[entry])
            if entry in settings[key]:
                col.set_visible(True)
            else:
                if entry != 'title':
                    col.set_visible(False)

        print("CoverArtBrowser DEBUG - end on_visible_columns_changed()")

    def define_menu(self):
        popup = Menu(self.plugin, self.shell)
        popup.load_from_file('N/A', 'ui/coverart_entryview_full_pop_rb3.ui')
        signals = {
            'ev_full_play_track_menu_item':
            self.play_track_menu_item_callback,
            'ev_full_play_next_track_menu_item':
            self.play_next_track_menu_item_callback,
            'ev_full_queue_track_menu_item':
            self.queue_track_menu_item_callback,
            'ev_full_add_to_playing_menu_item':
            self.add_to_playing_menu_item_callback,
            'ev_full_new_playlist':
            self.add_playlist_menu_item_callback,
            'ev_full_show_properties_menu_item':
            self.show_properties_menu_item_callback,
            'ev_full_similar_track_menu_item':
            self.play_similar_track_menu_item_callback,
            'ev_full_similar_artist_menu_item':
            self.play_similar_artist_menu_item_callback,
            'ev_full_similar_genre_menu_item':
            self.play_similar_genre_menu_item_callback
        }

        popup.connect_signals(signals)
        popup.connect('pre-popup', self.pre_popup_menu_callback)
        self.popup = popup

    def playlist_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - playlist_menu_item_callback")

        self.source.playlist_fillmenu(
            self.popup, 'ev_full_playlist_sub_menu_item',
            'ev_full_playlist_section', self.actiongroup,
            self.add_to_static_playlist_menu_item_callback)

    def pre_popup_menu_callback(self, *args):
        '''
        Callback when the popup menu is about to be displayed
        '''

        state, sensitive = self.shell.props.shell_player.get_playing()
        if not state:
            sensitive = False

        self.popup.set_sensitive('ev_full_add_to_playing_menu_item', sensitive)
        self.popup.set_sensitive('ev_full_play_next_track_menu_item',
                                 sensitive)

        if not self.external_plugins:
            self.external_plugins = \
                CreateExternalPluginMenu("ev_full_entryview", 6, self.popup)
            self.external_plugins.create_menu('entryview_full_popup_menu')

        self.playlist_menu_item_callback()
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
Example #26
0
class CoverArtEntryView(RB.EntryView):

    def __init__(self, shell, source):
        '''
        Initializes the entryview.
        '''
        self.shell = shell
        self.source = source
        self.plugin = self.source.props.plugin

        super(RB.EntryView, self).__init__(db=shell.props.db,
            shell_player=shell.props.shell_player, is_drag_source=True,
            visible_columns=[])

        cl = CoverLocale()
        cl.switch_locale(cl.Locale.RB)

        self.append_column(RB.EntryViewColumn.TRACK_NUMBER, False)
        self.append_column(RB.EntryViewColumn.TITLE, True)  # always shown
        self.append_column(RB.EntryViewColumn.GENRE, False)
        self.append_column(RB.EntryViewColumn.ARTIST, False)
        self.append_column(RB.EntryViewColumn.ALBUM, False)
        self.append_column(RB.EntryViewColumn.DURATION, False)
        self.append_column(RB.EntryViewColumn.COMMENT, False)
        self.append_column(RB.EntryViewColumn.RATING, False)
        self.append_column(RB.EntryViewColumn.QUALITY, False)
        self.append_column(RB.EntryViewColumn.PLAY_COUNT, False)
        self.append_column(RB.EntryViewColumn.LAST_PLAYED, False)
        self.append_column(RB.EntryViewColumn.YEAR, False)
        self.append_column(RB.EntryViewColumn.FIRST_SEEN, False)
        self.append_column(RB.EntryViewColumn.LOCATION, False)
        self.append_column(RB.EntryViewColumn.BPM, False)

        cl.switch_locale(cl.Locale.LOCALE_DOMAIN)

        # UI elements need to be imported.
        ui = Gtk.Builder()
        ui.set_translation_domain(cl.Locale.LOCALE_DOMAIN)
        ui.add_from_file(rb.find_plugin_file(self.plugin,
            'ui/coverart_entryview.ui'))
        ui.connect_signals(self)

        self.popup_menu = ui.get_object('entryview_popup_menu')

        # connect signals to the shell to know when the playing state changes
        self.shell.props.shell_player.connect('playing-song-changed',
            self.playing_song_changed)
        self.shell.props.shell_player.connect('playing-changed',
            self.playing_changed)

        self.playlist_sub_menu_item = ui.get_object('playlist_sub_menu_item')
        self.actiongroup = Gtk.ActionGroup('coverentryplaylist_submenu')
        uim = self.shell.props.ui_manager
        uim.insert_action_group(self.actiongroup)

        self.external_plugins = \
            CreateExternalPluginMenu("ca_entryview", self.shell)

        # connect the visible-columns global setting to update our entryview
        gs = GSetting()
        rhythm_settings = gs.get_setting(gs.Path.RBSOURCE)
        rhythm_settings.connect('changed::visible-columns',
            self.on_visible_columns_changed)
        self.on_visible_columns_changed(rhythm_settings, 'visible-columns')

        self.qm = RB.RhythmDBQueryModel.new_empty(self.shell.props.db)
        self.set_model(self.qm)

        # connect the sort-order to the library source sort
        library_view = self.shell.props.library_source.get_entry_view()
        library_view.connect('notify::sort-order',
            self._on_library_sorting_changed)
        self._on_library_sorting_changed(library_view,
            library_view.props.sort_order)

         # connect to the sort-order property
        self.connect('notify::sort-order', self._notify_sort_order,
            library_view)

    def __del__(self):
        uim = self.shell.props.ui_manager

        uim.remove_action_group(self.action_group)
        uim.remove_ui(self.ui_id)
        uim.ensure_update()

        del self.action_group
        del self.play_action
        del self.queue_action

    def on_visible_columns_changed(self, settings, key):
        print "CoverArtBrowser DEBUG - on_visible_columns_changed()"
        # reset current columns
        self.props.visible_columns = settings[key]
        print "CoverArtBrowser DEBUG - end on_visible_columns_changed()"

    def add_album(self, album):
        print "CoverArtBrowser DEBUG - add_album()"
        tracks = album.get_tracks()

        for track in tracks:
            self.qm.add_entry(track.entry, -1)

        (_, playing) = self.shell.props.shell_player.get_playing()
        self.playing_changed(self.shell.props.shell_player, playing)
        print "CoverArtBrowser DEBUG - add_album()"

    def clear(self):
        print "CoverArtBrowser DEBUG - clear()"
        # self.set_model(RB.RhythmDBQueryModel.new_empty(self.shell.props.db))
        for row in self.qm:
            self.qm.remove_entry(row[0])

        print "CoverArtBrowser DEBUG - clear()"

    def do_entry_activated(self, entry):
        print "CoverArtBrowser DEBUG - do_entry_activated()"
        self.select_entry(entry)
        self.play_track_menu_item_callback(entry)
        print "CoverArtBrowser DEBUG - do_entry_activated()"
        return True

    def do_show_popup(self, over_entry):
        if over_entry:
            print "CoverArtBrowser DEBUG - do_show_popup()"
            self.external_plugins.create_menu(self.popup_menu, 3)
            self.popup_menu.popup(None, None, None, None, 0,
                Gtk.get_current_event_time())

        return over_entry

    def play_track_menu_item_callback(self, _):
        print "CoverArtBrowser DEBUG - play_track_menu_item_callback()"
        
        query_model = RB.RhythmDBQueryModel.new_empty(self.shell.props.db)

        selected = self.get_selected_entries()
        entry = selected[0]
        
        if len(selected) == 1:
            query_model.copy_contents(self.qm)
        else:
            self.add_tracks_to_source(query_model)
            
        self.source.props.query_model = query_model

        # Start the music
        player = self.shell.props.shell_player
        player.play_entry(entry, self.source)

        print "CoverArtBrowser DEBUG - play_track_menu_item_callback()"

    def queue_track_menu_item_callback(self, entry):
        print "CoverArtBrowser DEBUG - queue_track_menu_item_callback()"

        self.add_tracks_to_source(self.shell.props.queue_source)

    def add_tracks_to_source(self, source):
        selected = self.get_selected_entries()
        selected.reverse()

        selected = sorted(selected,
            key=lambda song: song.get_ulong(RB.RhythmDBPropType.TRACK_NUMBER))

        for entry in selected:
            source.add_entry(entry, -1)

        print "CoverArtBrowser DEBUG - queue_track_menu_item_callback()"

    def love_track(self, rating):
        '''
        utility function to set the rating for selected tracks
        '''
        selected = self.get_selected_entries()

        for entry in selected:
            self.shell.props.db.entry_set(entry, RB.RhythmDBPropType.RATING,
                rating)

        self.shell.props.db.commit()

    def show_properties_menu_item_callback(self, entry):
        print "CoverArtBrowser DEBUG - show_properties_menu_item_callback()"

        info_dialog = RB.SongInfo(source=self.source, entry_view=self)

        info_dialog.show_all()

        print "CoverArtBrowser DEBUG - show_properties_menu_item_callback()"

    def playing_song_changed(self, shell_player, entry):
        print "CoverArtBrowser DEBUG - playing_song_changed()"

        if entry is not None and self.get_entry_contained(entry):
            self.set_state(RB.EntryViewState.PLAYING)
        else:
            self.set_state(RB.EntryViewState.NOT_PLAYING)

        print "CoverArtBrowser DEBUG - playing_song_changed()"

    def playing_changed(self, shell_player, playing):
        print "CoverArtBrowser DEBUG - playing_changed()"
        entry = shell_player.get_playing_entry()

        if entry is not None and self.get_entry_contained(entry):
            if playing:
                self.set_state(RB.EntryViewState.PLAYING)
            else:
                self.set_state(RB.EntryViewState.PAUSED)
        else:
            self.set_state(RB.EntryViewState.NOT_PLAYING)

        print "CoverArtBrowser DEBUG - playing_changed()"

    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.add_tracks_to_source(playlist)

    def playlist_menu_item_callback(self, menu_item):
        print "CoverArtBrowser DEBUG - playlist_menu_item_callback"

        self.source.playlist_fillmenu(self.playlist_sub_menu_item,
            self.actiongroup, self.add_to_static_playlist_menu_item_callback)

    def add_to_static_playlist_menu_item_callback(self, action, playlist,
        favourite):
        print "CoverArtBrowser DEBUG - " + \
            "add_to_static_playlist_menu_item_callback"
        self.add_tracks_to_source(playlist)

    def _on_library_sorting_changed(self, view, _):
        self._old_sort_order = self.props.sort_order

        self.set_sorting_type(view.props.sort_order)

    def _notify_sort_order(self, view, _, library_view):
        if self.props.sort_order != self._old_sort_order:
            self.resort_model()

            # update library source's view direction
            library_view.set_sorting_type(self.props.sort_order)
class CoverArtCompactEntryView(BaseView):
    __hash__ = GObject.__hash__

    def __init__(self, shell, source):
        '''
        Initializes the entryview.
        '''
        super(CoverArtCompactEntryView, self).__init__(shell, source)

    def display_columns(self):

        self.col_map = OrderedDict([('track-number',
                                     RB.EntryViewColumn.TRACK_NUMBER),
                                    ('title', RB.EntryViewColumn.TITLE),
                                    ('artist', RB.EntryViewColumn.ARTIST),
                                    ('rating', RB.EntryViewColumn.RATING),
                                    ('duration', RB.EntryViewColumn.DURATION)])

        for entry in self.col_map:
            visible = False if entry == 'artist' else True
            self.append_column(self.col_map[entry], visible)

    def add_album(self, album):
        super(CoverArtCompactEntryView, self).add_album(album)

        if len(self.artists) > 1:
            self.get_column(RB.EntryViewColumn.ARTIST).set_visible(True)
        else:
            self.get_column(RB.EntryViewColumn.ARTIST).set_visible(False)

    def define_menu(self):
        popup = Menu(self.plugin, self.shell)
        popup.load_from_file('N/A', 'ui/coverart_entryview_compact_pop_rb3.ui')
        signals = {
            'ev_compact_play_track_menu_item':
            self.play_track_menu_item_callback,
            'ev_compact_play_next_track_menu_item':
            self.play_next_track_menu_item_callback,
            'ev_compact_queue_track_menu_item':
            self.queue_track_menu_item_callback,
            'ev_compact_add_to_playing_menu_item':
            self.add_to_playing_menu_item_callback,
            'ev_compact_new_playlist':
            self.add_playlist_menu_item_callback,
            'ev_compact_show_properties_menu_item':
            self.show_properties_menu_item_callback,
            'ev_compact_similar_track_menu_item':
            self.play_similar_track_menu_item_callback,
            'ev_compact_similar_artist_menu_item':
            self.play_similar_artist_menu_item_callback,
            'ev_compact_similar_genre_menu_item':
            self.play_similar_genre_menu_item_callback
        }

        popup.connect_signals(signals)
        popup.connect('pre-popup', self.pre_popup_menu_callback)
        self.popup = popup

    def playlist_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - playlist_menu_item_callback")

        self.source.playlist_fillmenu(
            self.popup, 'ev_compact_playlist_sub_menu_item',
            'ev_compact_playlist_section', self.actiongroup,
            self.add_to_static_playlist_menu_item_callback)

    def pre_popup_menu_callback(self, *args):
        '''
        Callback when the popup menu is about to be displayed
        '''

        state, sensitive = self.shell.props.shell_player.get_playing()
        if not state:
            sensitive = False

        self.popup.set_sensitive('ev_compact_add_to_playing_menu_item',
                                 sensitive)
        self.popup.set_sensitive('ev_compact_play_next_track_menu_item',
                                 sensitive)

        if not self.external_plugins:
            self.external_plugins = \
                CreateExternalPluginMenu("ev_compact_entryview", 6, self.popup)
            self.external_plugins.create_menu('entryview_compact_popup_menu')

        self.playlist_menu_item_callback()
class CoverArtEntryView(BaseView):
    __hash__ = GObject.__hash__

    def __init__(self, shell, source):
        '''
        Initializes the entryview.
        '''
        super(CoverArtEntryView, self).__init__(shell, source)

    def display_columns(self):

        self.col_map = OrderedDict([
            ('track-number', RB.EntryViewColumn.TRACK_NUMBER),
            ('title', RB.EntryViewColumn.TITLE),
            ('genre', RB.EntryViewColumn.GENRE),
            ('artist', RB.EntryViewColumn.ARTIST),
            ('album', RB.EntryViewColumn.ALBUM),
            ('composer', RB.EntryViewColumn.COMPOSER),
            ('date', RB.EntryViewColumn.YEAR),
            ('duration', RB.EntryViewColumn.DURATION),
            ('bitrate', RB.EntryViewColumn.QUALITY),
            ('play-count', RB.EntryViewColumn.PLAY_COUNT),
            ('beats-per-minute', RB.EntryViewColumn.BPM),
            ('comment', RB.EntryViewColumn.COMMENT),
            ('location', RB.EntryViewColumn.LOCATION),
            ('rating', RB.EntryViewColumn.RATING),
            ('last-played', RB.EntryViewColumn.LAST_PLAYED),
            ('first-seen', RB.EntryViewColumn.FIRST_SEEN)
        ])

        for entry in self.col_map:
            visible = True if entry == 'title' else False
            self.append_column(self.col_map[entry], visible)

        # connect the visible-columns global setting to update our entryview
        gs = GSetting()
        rhythm_settings = gs.get_setting(gs.Path.RBSOURCE)
        rhythm_settings.connect('changed::visible-columns',
                                self.on_visible_columns_changed)
        self.on_visible_columns_changed(rhythm_settings, 'visible-columns')

    def on_visible_columns_changed(self, settings, key):
        print("CoverArtBrowser DEBUG - on_visible_columns_changed()")
        # reset current columns
        print("CoverArtBrowser DEBUG - end on_visible_columns_changed()")
        for entry in self.col_map:
            col = self.get_column(self.col_map[entry])
            if entry in settings[key]:
                col.set_visible(True)
            else:
                if entry != 'title':
                    col.set_visible(False)

        print("CoverArtBrowser DEBUG - end on_visible_columns_changed()")

    def define_menu(self):
        popup = Menu(self.plugin, self.shell)
        popup.load_from_file('N/A',
                             'ui/coverart_entryview_full_pop_rb3.ui')
        signals = {
            'ev_full_play_track_menu_item': self.play_track_menu_item_callback,
            'ev_full_play_next_track_menu_item': self.play_next_track_menu_item_callback,
            'ev_full_queue_track_menu_item': self.queue_track_menu_item_callback,
            'ev_full_add_to_playing_menu_item': self.add_to_playing_menu_item_callback,
            'ev_full_new_playlist': self.add_playlist_menu_item_callback,
            'ev_full_show_properties_menu_item': self.show_properties_menu_item_callback,
            'ev_full_similar_track_menu_item': self.play_similar_track_menu_item_callback,
            'ev_full_similar_artist_menu_item': self.play_similar_artist_menu_item_callback,
            'ev_full_similar_genre_menu_item': self.play_similar_genre_menu_item_callback}

        popup.connect_signals(signals)
        popup.connect('pre-popup', self.pre_popup_menu_callback)
        self.popup = popup

    def playlist_menu_item_callback(self, *args):
        print("CoverArtBrowser DEBUG - playlist_menu_item_callback")

        self.source.playlist_fillmenu(self.popup, 'ev_full_playlist_sub_menu_item', 'ev_full_playlist_section',
                                      self.actiongroup, self.add_to_static_playlist_menu_item_callback)

    def pre_popup_menu_callback(self, *args):
        '''
        Callback when the popup menu is about to be displayed
        '''

        state, sensitive = self.shell.props.shell_player.get_playing()
        if not state:
            sensitive = False

        self.popup.set_sensitive('ev_full_add_to_playing_menu_item', sensitive)
        self.popup.set_sensitive('ev_full_play_next_track_menu_item', sensitive)

        if not self.external_plugins:
            self.external_plugins = \
                CreateExternalPluginMenu("ev_full_entryview", 6, self.popup)
            self.external_plugins.create_menu('entryview_full_popup_menu')

        self.playlist_menu_item_callback()