class AlbumManager(GObject.Object):
    '''
    Main construction that glues together the different managers, the loader
    and the model. It takes care of initializing all the system.

    :param plugin: `Peas.PluginInfo` instance.
    :param current_view: `AlbumView` where the album's cover are shown.
    '''
    # singleton instance
    instance = None

    # properties
    progress = GObject.property(type=float, default=0)

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


    def __init__(self, plugin, current_view):
        super(AlbumManager, self).__init__()

        self.current_view = current_view
        self.db = plugin.shell.props.db

        self.model = AlbumsModel()

        # initialize managers
        self.loader = AlbumLoader(self)
        self.cover_man = AlbumCoverManager(plugin, self)
        from coverart_artistview import ArtistManager

        self.artist_man = ArtistManager(plugin, self, plugin.shell)
        self.text_man = TextManager(self)
        self._show_policy = current_view.show_policy.initialise(self)

        # connect signals
        self._connect_signals()

    def _connect_signals(self):
        '''
        Connects the manager to all the needed signals for it to work.
        '''
        # connect signal to the loader so it shows the albums when it finishes
        self._load_finished_id = self.loader.connect('model-load-finished',
                                                     self._load_finished_callback)
        self.connect('sort', self._sort_album)

    def _sort_album(self, widget, param):
        toolbar_type = param

        if not toolbar_type or toolbar_type == "album":
            self.model.sort()

    def _load_finished_callback(self, *args):
        self.artist_man.loader.load_artists()
        self.cover_man.load_covers() class GSetting:
    '''
    This class manages the different settings that the plugin has to
    access to read or write.
    '''
    # storage for the instance reference
    __instance = None

    class __impl:
        """ Implementation of the singleton interface """
        # below public variables and methods that can be called for GSetting
        def __init__(self):
            '''
            Initializes the singleton interface, assigning all the constants
            used to access the plugin's settings.
            '''
            self.Path = self._enum(
                PLUGIN='org.gnome.rhythmbox.plugins.coverart_browser',
                RBSOURCE='org.gnome.rhythmbox.sources')

            self.RBSourceKey = self._enum(VISIBLE_COLS='visible-columns')

            self.PluginKey = self._enum(
                CUSTOM_STATUSBAR='custom-statusbar',
                DISPLAY_TEXT='display-text',
                DISPLAY_TEXT_POS='display-text-pos',
                RANDOM='random-queue',
                DISPLAY_TEXT_LOADING='display-text-loading',
                DISPLAY_TEXT_ELLIPSIZE='display-text-ellipsize',
                DISPLAY_TEXT_ELLIPSIZE_LENGTH='display-text-ellipsize-length',
                DISPLAY_FONT_SIZE='display-font-size',
                COVER_SIZE='cover-size',
                ADD_SHADOW='add-shadow',
                SHADOW_IMAGE='shadow-image',
                PANED_POSITION='paned-position',
                SORT_BY='sort-by',
                SORT_ORDER='sort-order',
                SORT_BY_ARTIST='sort-by-artist',
                SORT_ORDER_ARTIST='sort-order-artist',
                RATING='rating-threshold',
                AUTOSTART='autostart',
                TOOLBAR_POS='toolbar-pos',
                BUTTON_RELIEF='button-relief',
                THEME='theme',
                NEW_GENRE_ICON='new-genre-icon',
                ICON_PADDING='icon-padding',
                ICON_SPACING='icon-spacing',
                ICON_AUTOMATIC='icon-automatic',
                VIEW_NAME='view-name',
                FLOW_APPEARANCE='flow-appearance',
                FLOW_HIDE_CAPTION='flow-hide-caption',
                FLOW_SCALE='flow-scale',
                FLOW_BACKGROUND_COLOUR='flow-background-colour',
                FLOW_AUTOMATIC='flow-automatic',
                FLOW_WIDTH='flow-width',
                FLOW_MAX='flow-max-albums',
                WEBKIT='webkit-support',
                ARTIST_PANED_POSITION='artist-paned-pos',
                USE_FAVOURITES='use-favourites',
                ARTIST_INFO_PANED_POSITION='artist-info-paned-pos',
                LAST_GENRE_FOLDER='last-genre-folder',
                ENTRY_VIEW_MODE='entry-view-mode',
                FOLLOWING='following',
                ACTIVATIONS='activations',
                TEXT_ALIGNMENT='text-alignment')

            self.setting = {}

        def get_setting(self, path):
            '''
            Return an instance of Gio.Settings pointing at the selected path.
            '''
            try:
                setting = self.setting[path]
            except:
                self.setting[path] = Gio.Settings.new(path)
                setting = self.setting[path]

            return setting

        def get_value(self, path, key):
            '''
            Return the value saved on key from the settings path.
            '''
            return self.get_setting(path)[key]

        def set_value(self, path, key, value):
            '''
            Set the passed value to key in the settings path.
            '''
            self.get_setting(path)[key] = value

        def _enum(self, **enums):
            '''
            Create an enumn.
            '''
            return type('Enum', (), enums)

    def __init__(self):
        """ Create singleton instance """
        # Check whether we already have an instance
        if GSetting.__instance is None:
            # Create and remember instance
            GSetting.__instance = GSetting.__impl()

        # Store instance reference as the only member in the handle
        self.__dict__['_GSetting__instance'] = GSetting.__instance

    def __getattr__(self, attr):
        """ Delegate access to implementation """
        return getattr(self.__instance, attr)

    def __setattr__(self, attr, value):
        """ Delegate access to implementation """
        return setattr(self.__instance, attr, value) class CoverLocale:
    '''
    This class manages the locale
    '''
    # storage for the instance reference
    __instance = None

    class __impl:
        """ Implementation of the singleton interface """
        # below public variables and methods that can be called for CoverLocale
        def __init__(self):
            '''
            Initializes the singleton interface, assigning all the constants
            used to access the plugin's settings.
            '''
            self.Locale = self._enum(
                RB='rhythmbox',
                LOCALE_DOMAIN='coverart_browser')

        def switch_locale(self, locale_type):
            '''
            Change the locale
            '''
            locale.setlocale(locale.LC_ALL, '')
            locale.bindtextdomain(locale_type, RB.locale_dir())
            locale.textdomain(locale_type)
            gettext.bindtextdomain(locale_type, RB.locale_dir())
            gettext.textdomain(locale_type)
            gettext.install(locale_type)

        def get_locale(self):
            '''
            return the string representation of the users locale
            for example
            en_US
            '''
            return locale.getdefaultlocale()[0]

        def _enum(self, **enums):
            '''
            Create an enumn.
            '''
            return type('Enum', (), enums)

    def __init__(self):
        """ Create singleton instance """
        # Check whether we already have an instance
        if CoverLocale.__instance is None:
            # Create and remember instance
            CoverLocale.__instance = CoverLocale.__impl()

        # Store instance reference as the only member in the handle
        self.__dict__['_CoverLocale__instance'] = CoverLocale.__instance

    def __getattr__(self, attr):
        """ Delegate access to implementation """
        return getattr(self.__instance, attr)

    def __setattr__(self, attr, value):
        """ Delegate access to implementation """
        return setattr(self.__instance, attr, value) class Preferences(GObject.Object, PeasGtk.Configurable):
    '''
    Preferences for the CoverArt Browser Plugins. It holds the settings for
    the plugin and also is the responsible of creating the preferences dialog.
    '''
    __gtype_name__ = 'CoverArtBrowserPreferences'
    object = GObject.property(type=GObject.Object)

    GENRE_POPUP = 1
    GENRE_LIST = 2

    def __init__(self):
        '''
        Initialises the preferences, getting an instance of the settings saved
        by Gio.
        '''
        GObject.Object.__init__(self)
        gs = GSetting()
        self.settings = gs.get_setting(gs.Path.PLUGIN)

        self._first_run = True
        self._cover_size = 0
        self._cover_size_delay = 0

    def do_create_configure_widget(self):
        '''
        Creates the plugin's preferences dialog
        '''
        return self._create_display_contents(self)

    def display_preferences_dialog(self, plugin):
        print("DEBUG - display_preferences_dialog")
        if self._first_run:
            self._first_run = False

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

            self._dialog = Gtk.Dialog(modal=True, destroy_with_parent=True)
            self._dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
            self._dialog.set_title(_('Browser Preferences'))
            content_area = self._dialog.get_content_area()
            content_area.pack_start(self._create_display_contents(plugin), True, True, 0)

            helpbutton = self._dialog.add_button(Gtk.STOCK_HELP, Gtk.ResponseType.HELP)
            helpbutton.connect('clicked', self._display_help)

        self._dialog.show_all()

        print("shown")

        while True:
            response = self._dialog.run()

            print("and run")

            if response != Gtk.ResponseType.HELP:
                break

        self._dialog.hide()

        print("DEBUG - display_preferences_dialog end")

    def _display_help(self, *args):
        peas = Peas.Engine.get_default()
        uri = peas.get_plugin_info('coverart_browser').get_help_uri()

        webbrowser.open(uri)

    def _create_display_contents(self, plugin):
        print("DEBUG - create_display_contents")
        # create the ui
        self._first_run = True
        cl = CoverLocale()
        cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
        builder = Gtk.Builder()
        builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN)
        builder.add_from_file(rb.find_plugin_file(plugin,
                                                  'ui/coverart_browser_prefs.ui'))
        self.launchpad_button = builder.get_object('show_launchpad')
        self.launchpad_label = builder.get_object('launchpad_label')

        builder.connect_signals(self)

        # . TRANSLATORS: Do not translate this string.
        translators = _('translator-credits')

        if translators != "translator-credits":
            self.launchpad_label.set_text(translators)
        else:
            self.launchpad_button.set_visible(False)

        gs = GSetting()
        # bind the toggles to the settings
        toggle_statusbar = builder.get_object('custom_statusbar_checkbox')
        self.settings.bind(gs.PluginKey.CUSTOM_STATUSBAR,
                           toggle_statusbar, 'active', Gio.SettingsBindFlags.DEFAULT)

        toggle_text = builder.get_object('display_text_checkbox')
        self.settings.bind(gs.PluginKey.DISPLAY_TEXT, toggle_text, 'active',
                           Gio.SettingsBindFlags.DEFAULT)

        box_text = builder.get_object('display_text_box')
        self.settings.bind(gs.PluginKey.DISPLAY_TEXT, box_text, 'sensitive',
                           Gio.SettingsBindFlags.GET)

        self.display_text_pos = self.settings[gs.PluginKey.DISPLAY_TEXT_POS]
        self.display_text_under_radiobutton = builder.get_object('display_text_under_radiobutton')
        self.display_text_within_radiobutton = builder.get_object('display_text_within_radiobutton')

        if self.display_text_pos:
            self.display_text_under_radiobutton.set_active(True)
        else:
            self.display_text_within_radiobutton.set_active(True)

        random_scale = builder.get_object('random_adjustment')
        self.settings.bind(gs.PluginKey.RANDOM, random_scale, 'value',
                           Gio.SettingsBindFlags.DEFAULT)

        toggle_text_ellipsize = builder.get_object(
            'display_text_ellipsize_checkbox')
        self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE,
                           toggle_text_ellipsize, 'active', Gio.SettingsBindFlags.DEFAULT)

        box_text_ellipsize_length = builder.get_object(
            'display_text_ellipsize_length_box')
        self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE,
                           box_text_ellipsize_length, 'sensitive', Gio.SettingsBindFlags.GET)

        spinner_text_ellipsize_length = builder.get_object(
            'display_text_ellipsize_length_spin')
        self.settings.bind(gs.PluginKey.DISPLAY_TEXT_ELLIPSIZE_LENGTH,
                           spinner_text_ellipsize_length, 'value',
                           Gio.SettingsBindFlags.DEFAULT)

        spinner_font_size = builder.get_object(
            'display_font_spin')
        self.settings.bind(gs.PluginKey.DISPLAY_FONT_SIZE,
                           spinner_font_size, 'value',
                           Gio.SettingsBindFlags.DEFAULT)

        cover_size_scale = builder.get_object('cover_size_adjustment')

        #self.settings.bind(gs.PluginKey.COVER_SIZE, cover_size_scale, 'value',
        #                   Gio.SettingsBindFlags.DEFAULT)
        self._cover_size = self.settings[gs.PluginKey.COVER_SIZE]
        cover_size_scale.set_value(self._cover_size)
        cover_size_scale.connect('value-changed', self.on_cover_size_scale_changed)

        add_shadow = builder.get_object('add_shadow_checkbox')
        self.settings.bind(gs.PluginKey.ADD_SHADOW, add_shadow, 'active',
                           Gio.SettingsBindFlags.DEFAULT)

        rated_box = builder.get_object('rated_box')
        self.stars = ReactiveStar(size=StarSize.BIG)

        self.stars.connect('changed', self.rating_changed_callback)

        align = Gtk.Alignment.new(0.5, 0, 0, 0.1)
        align.add(self.stars)
        rated_box.add(align)

        self.stars.set_rating(self.settings[gs.PluginKey.RATING])

        autostart = builder.get_object('autostart_checkbox')
        self.settings.bind(gs.PluginKey.AUTOSTART,
                           autostart, 'active', Gio.SettingsBindFlags.DEFAULT)

        toolbar_pos_combo = builder.get_object('show_in_combobox')
        renderer = Gtk.CellRendererText()
        toolbar_pos_combo.pack_start(renderer, True)
        toolbar_pos_combo.add_attribute(renderer, 'text', 1)
        self.settings.bind(gs.PluginKey.TOOLBAR_POS, toolbar_pos_combo,
                           'active-id', Gio.SettingsBindFlags.DEFAULT)

        light_source_combo = builder.get_object('light_source_combobox')
        renderer = Gtk.CellRendererText()
        light_source_combo.pack_start(renderer, True)
        light_source_combo.add_attribute(renderer, 'text', 1)
        self.settings.bind(gs.PluginKey.SHADOW_IMAGE, light_source_combo,
                           'active-id', Gio.SettingsBindFlags.DEFAULT)

        combo_liststore = builder.get_object('combo_liststore')

        from coverart_utils import Theme

        for theme in Theme(self).themes:
            combo_liststore.append([theme, theme])

        theme_combo = builder.get_object('theme_combobox')
        renderer = Gtk.CellRendererText()
        theme_combo.pack_start(renderer, True)
        theme_combo.add_attribute(renderer, 'text', 1)
        self.settings.bind(gs.PluginKey.THEME, theme_combo,
                           'active-id', Gio.SettingsBindFlags.DEFAULT)

        button_relief = builder.get_object('button_relief_checkbox')
        self.settings.bind(gs.PluginKey.BUTTON_RELIEF, button_relief, 'active',
                           Gio.SettingsBindFlags.DEFAULT)

        # create user data files
        cachedir = RB.user_cache_dir() + "/coverart_browser/usericons"
        if not os.path.exists(cachedir):
            os.makedirs(cachedir)

        popup = cachedir + "/popups.xml"

        temp = RB.find_user_data_file('plugins/coverart_browser/img/usericons/popups.xml')

        # lets see if there is a legacy file - if necessary copy it to the cache dir
        if os.path.isfile(temp) and not os.path.isfile(popup):
            shutil.copyfile(temp, popup)

        if not os.path.isfile(popup):
            template = rb.find_plugin_file(plugin, 'template/popups.xml')
            folder = os.path.split(popup)[0]
            if not os.path.exists(folder):
                os.makedirs(folder)
            shutil.copyfile(template, popup)

        # now prepare the genre tab
        from coverart_utils import GenreConfiguredSpriteSheet
        from coverart_utils import get_stock_size

        self._sheet = GenreConfiguredSpriteSheet(plugin, "genre", get_stock_size())

        self.alt_liststore = builder.get_object('alt_liststore')
        self.alt_user_liststore = builder.get_object('alt_user_liststore')
        self._iters = {}
        for key in list(self._sheet.keys()):
            store_iter = self.alt_liststore.append([key, self._sheet[key]])
            self._iters[(key, self.GENRE_POPUP)] = store_iter

        for key, value in self._sheet.genre_alternate.items():
            if key.genre_type == GenreConfiguredSpriteSheet.GENRE_USER:
                store_iter = self.alt_user_liststore.append([key.name,
                                                             self._sheet[self._sheet.genre_alternate[key]],
                                                             self._sheet.genre_alternate[key]])
                self._iters[(key.name, self.GENRE_LIST)] = store_iter

        self.amend_mode = False
        self.blank_iter = None
        self.genre_combobox = builder.get_object('genre_combobox')
        self.genre_entry = builder.get_object('genre_entry')
        self.genre_view = builder.get_object('genre_view')
        self.save_button = builder.get_object('save_button')
        self.filechooserdialog = builder.get_object('filechooserdialog')
        last_genre_folder = self.settings[gs.PluginKey.LAST_GENRE_FOLDER]
        if last_genre_folder != "":
            self.filechooserdialog.set_current_folder(last_genre_folder)

        padding_scale = builder.get_object('padding_adjustment')
        self.settings.bind(gs.PluginKey.ICON_PADDING, padding_scale, 'value',
                           Gio.SettingsBindFlags.DEFAULT)

        spacing_scale = builder.get_object('spacing_adjustment')
        self.settings.bind(gs.PluginKey.ICON_SPACING, spacing_scale, 'value',
                           Gio.SettingsBindFlags.DEFAULT)

        icon_automatic = builder.get_object('icon_automatic_checkbox')
        self.settings.bind(gs.PluginKey.ICON_AUTOMATIC,
                           icon_automatic, 'active', Gio.SettingsBindFlags.DEFAULT)

        #flow tab
        flow_combo = builder.get_object('flow_combobox')
        renderer = Gtk.CellRendererText()
        flow_combo.pack_start(renderer, True)
        flow_combo.add_attribute(renderer, 'text', 1)
        self.settings.bind(gs.PluginKey.FLOW_APPEARANCE, flow_combo,
                           'active-id', Gio.SettingsBindFlags.DEFAULT)

        flow_hide = builder.get_object('hide_caption_checkbox')
        self.settings.bind(gs.PluginKey.FLOW_HIDE_CAPTION,
                           flow_hide, 'active', Gio.SettingsBindFlags.DEFAULT)

        flow_scale = builder.get_object('cover_scale_adjustment')
        self.settings.bind(gs.PluginKey.FLOW_SCALE, flow_scale, 'value',
                           Gio.SettingsBindFlags.DEFAULT)

        flow_width = builder.get_object('cover_width_adjustment')
        self.settings.bind(gs.PluginKey.FLOW_WIDTH, flow_width, 'value',
                           Gio.SettingsBindFlags.DEFAULT)

        flow_max = builder.get_object('flow_max_adjustment')
        self.settings.bind(gs.PluginKey.FLOW_MAX, flow_max, 'value',
                           Gio.SettingsBindFlags.DEFAULT)

        flow_automatic = builder.get_object('automatic_checkbox')
        self.settings.bind(gs.PluginKey.FLOW_AUTOMATIC,
                           flow_automatic, 'active', Gio.SettingsBindFlags.DEFAULT)

        self.background_colour = self.settings[gs.PluginKey.FLOW_BACKGROUND_COLOUR]
        self.white_radiobutton = builder.get_object('white_radiobutton')
        self.black_radiobutton = builder.get_object('black_radiobutton')

        if self.background_colour == 'W':
            self.white_radiobutton.set_active(True)
        else:
            self.black_radiobutton.set_active(True)

        self.text_alignment = self.settings[gs.PluginKey.TEXT_ALIGNMENT]
        self.text_alignment_left_radiobutton = builder.get_object('left_alignment_radiobutton')
        self.text_alignment_centre_radiobutton = builder.get_object('centre_alignment_radiobutton')
        self.text_alignment_right_radiobutton = builder.get_object('right_alignment_radiobutton')

        if self.text_alignment == 0:
            self.text_alignment_left_radiobutton.set_active(True)
        elif self.text_alignment == 1:
            self.text_alignment_centre_radiobutton.set_active(True)
        else:
            self.text_alignment_right_radiobutton.set_active(True)

        # return the dialog
        self._first_run = False
        print("end create dialog contents")
        return builder.get_object('main_notebook')

    def on_cover_size_scale_changed(self, scale):
        self._cover_size = scale.get_value()

        def delay(*args):
            print('delay')
            print(self._cover_size_delay)
            self._cover_size_delay = self._cover_size_delay + 1

            if self._cover_size_delay >= 8:
                gs = GSetting()
                self.settings[gs.PluginKey.COVER_SIZE] = self._cover_size
                self._cover_size_delay = 0
                return False

            return True

        if self._cover_size_delay == 0:
            Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 100, delay, None)
        else:
            self._cover_size_delay = 1

    def on_flow_combobox_changed(self, combobox):
        current_val = combobox.get_model()[combobox.get_active()][0]
        gs = GSetting()
        if self.settings[gs.PluginKey.FLOW_APPEARANCE] != current_val:
            if current_val == 'flow-vert':
                default_size = 150
            else:
                default_size = 600

            self.settings[gs.PluginKey.FLOW_WIDTH] = default_size

            if current_val == 'carousel':
                self.settings[gs.PluginKey.FLOW_HIDE_CAPTION] = True

    def on_background_radio_toggled(self, button):
        if button.get_active():
            gs = GSetting()
            if button == self.white_radiobutton:
                self.settings[gs.PluginKey.FLOW_BACKGROUND_COLOUR] = 'W'
            else:
                self.settings[gs.PluginKey.FLOW_BACKGROUND_COLOUR] = 'B'

    def on_display_text_pos_radio_toggled(self, button):
        if self._first_run:
            return

        if button.get_active():
            gs = GSetting()
            if button == self.display_text_under_radiobutton:
                self.settings[gs.PluginKey.DISPLAY_TEXT_POS] = True
            else:
                self.settings[gs.PluginKey.DISPLAY_TEXT_POS] = False
                self.settings[gs.PluginKey.ADD_SHADOW] = False

    def on_text_alignment_radiobutton_toggled(self, button):
        if self._first_run:
            return

        if button.get_active():
            gs = GSetting()
            if button == self.text_alignment_left_radiobutton:
                self.settings[gs.PluginKey.TEXT_ALIGNMENT] = 0
            elif button == self.text_alignment_centre_radiobutton:
                self.settings[gs.PluginKey.TEXT_ALIGNMENT] = 1
            else:
                self.settings[gs.PluginKey.TEXT_ALIGNMENT] = 2

    def on_add_shadow_checkbox_toggled(self, button):
        if button.get_active():
            # gs = GSetting()
            #self.settings[gs.PluginKey.DISPLAY_TEXT_POS] = True
            self.display_text_under_radiobutton.set_active(True)

    def rating_changed_callback(self, stars):
        print("rating_changed_callback")
        gs = GSetting()
        self.settings[gs.PluginKey.RATING] = self.stars.get_rating()

    def on_save_button_clicked(self, button):
        '''
        action when genre edit area is saved
        '''
        entry_value = self.genre_entry.get_text()
        treeiter = self.genre_combobox.get_active_iter()
        icon_value = self.alt_liststore[treeiter][0]
        # model 0 is the icon name, model 1 is the pixbuf

        if self.amend_mode:
            key = self._sheet.amend_genre_info(self.current_genre,
                                               entry_value, icon_value)

            self.alt_user_liststore[self._iters[(self.current_genre,
                                                 self.GENRE_LIST)]][1] = self._sheet[self._sheet.genre_alternate[key]]
            self.alt_user_liststore[self._iters[(self.current_genre,
                                                 self.GENRE_LIST)]][0] = key.name
            store_iter = self._iters[(self.current_genre, self.GENRE_LIST)]
            del self._iters[(self.current_genre, self.GENRE_LIST)]
            self._iters[(key.name, self.GENRE_LIST)] = store_iter

        else:
            self.amend_mode = True
            key = self._sheet.amend_genre_info('',
                                               entry_value, icon_value)
            self.current_genre = key.name

            store_iter = self.alt_user_liststore.append([key.name,
                                                         self._sheet[self._sheet.genre_alternate[key]],
                                                         self._sheet.genre_alternate[key]])
            self._iters[(key.name, self.GENRE_LIST)] = store_iter
            selection = self.genre_view.get_selection()
            selection.select_iter(store_iter)

        self.save_button.set_sensitive(False)
        self._toggle_new_genre_state()


    def on_genre_filechooserbutton_file_set(self, filechooser):
        '''
        action when genre new icon button is pressed
        '''
        key = self._sheet.add_genre_icon(self.filechooserdialog.get_filename())
        store_iter = self.alt_liststore.append([key.name, self._sheet[key.name]])
        self._iters[(key.name, self.GENRE_POPUP)] = store_iter

        gs = GSetting()
        last_genre_folder = self.filechooserdialog.get_current_folder()

        print(last_genre_folder)
        print(self.filechooserdialog.get_filename())
        if last_genre_folder:
            self.settings[gs.PluginKey.LAST_GENRE_FOLDER] = last_genre_folder

    def on_genre_view_selection_changed(self, view):
        '''
        action when user selects a row in the list of genres
        '''
        model, genre_iter = view.get_selected()
        if genre_iter:
            self.genre_entry.set_text(model[genre_iter][0])
            index = model[genre_iter][2]
            if index != '':
                self.genre_combobox.set_active_iter(self._iters[(index, self.GENRE_POPUP)])
                self.amend_mode = True
                self.current_genre = rb3compat.unicodestr(model[genre_iter][0], 'utf-8')
        else:
            self.genre_entry.set_text('')
            self.genre_combobox.set_active_iter(None)
            self.amend_mode = False

        if self.blank_iter and self.amend_mode:
            try:
                index = model[self.blank_iter][0]
                if index == '':
                    model.remove(self.blank_iter)
                    self.blank_iter = None
            except:
                self.blank_iter = None

    def on_add_button_clicked(self, button):
        '''
        action when a new genre is added to the table
        '''
        self.genre_entry.set_text('')
        self.genre_combobox.set_active(-1)
        self.amend_mode = False
        self.blank_iter = self.alt_user_liststore.append(['', None, ''])
        selection = self.genre_view.get_selection()
        selection.select_iter(self.blank_iter)


    def on_delete_button_clicked(self, button):
        '''
        action when a genre is to be deleted
        '''
        selection = self.genre_view.get_selection()

        model, genre_iter = selection.get_selected()
        if genre_iter:
            index = rb3compat.unicodestr(model[genre_iter][0], 'utf-8')
            model.remove(genre_iter)

            if index:
                del self._iters[(index, self.GENRE_LIST)]
                self._sheet.delete_genre(index)

                self._toggle_new_genre_state()

    def set_save_sensitivity(self, _):
        '''
        action to toggle the state of the save button depending
        upon the values entered in the genre edit fields
        '''
        entry_value = self.genre_entry.get_text()
        treeiter = self.genre_combobox.get_active_iter()

        entry_value = rb3compat.unicodestr(entry_value, 'utf-8')
        enable = False
        try:
            test = self._iters[(entry_value, self.GENRE_LIST)]
            if RB.search_fold(self.current_genre) == RB.search_fold(entry_value):
                # if the current entry is the same then could save
                enable = True
        except:
            # reach here if this is a brand new entry
            enable = True

        if treeiter == None or entry_value == None or entry_value == "":
            # no icon chosen, or no entry value then nothing to save
            enable = False

        self.save_button.set_sensitive(enable)

    def _toggle_new_genre_state(self):
        '''
        fire an event - uses gsettings and an object such as a
        controller connects to receive the signal that a new or amended
        genre has been made
        '''
        gs = GSetting()
        test = self.settings[gs.PluginKey.NEW_GENRE_ICON]

        if test:
            test = False
        else:
            test = True

        self.settings[gs.PluginKey.NEW_GENRE_ICON] = test

    def on_show_launchpad_toggled(self, button):
        self.launchpad_label.set_visible(button.get_active()) class PanedCollapsible(Gtk.Paned):
    __gtype_name__ = "PanedCollapsible"

    # properties
    # this two properties indicate which one of the Paned childs is collapsible
    # only one can be True at a time, the widget takes care of keeping this
    # restriction consistent.
    collapsible1 = GObject.property(type=bool, default=False)
    collapsible2 = GObject.property(type=bool, default=False)

    # values for expand method
    Paned = enum(DEFAULT=1, EXPAND=2, COLLAPSE=3)

    # this indicates the latest position for the handle before a child was
    # collapsed
    collapsible_y = GObject.property(type=int, default=0)

    # label for the Expander used on the collapsible child
    collapsible_label = GObject.property(type=str, default='')

    # signals
    __gsignals__ = {
        'expanded': (GObject.SIGNAL_RUN_LAST, None, (bool,))
    }

    Min_Paned_Size = 80

    def __init__(self, *args, **kwargs):
        super(PanedCollapsible, self).__init__(*args, **kwargs)
        self._connect_properties()
        self._from_paned_handle = 0

    def _connect_properties(self):
        self.connect('notify::collapsible1', self._on_collapsible1_changed)
        self.connect('notify::collapsible2', self._on_collapsible2_changed)
        self.connect('notify::collapsible_label',
                     self._on_collapsible_label_changed)

    def _on_collapsible1_changed(self, *args):
        if self.collapsible1 and self.collapsible2:
            # check consistency, only one collapsible at a time
            self.collapsible2 = False

        child = self.get_child1()

        self._wrap_unwrap_child(child, self.collapsible1, self.add1)

    def _on_collapsible2_changed(self, *args):
        if self.collapsible1 and self.collapsible2:
            # check consistency, only one collapsible at a time
            self.collapsible1 = False

        child = self.get_child2()

        self._wrap_unwrap_child(child, self.collapsible2, self.add2)

    def _wrap_unwrap_child(self, child, wrap, add):
        if child:
            self.remove(child)

            if not wrap:
                inner_child = child.get_child()
                child.remove(inner_child)
                child = inner_child

            add(child)

    def _on_collapsible_label_changed(self, *args):
        if self._expander:
            self._expander.set_label(self.collapsible_label)

    def _on_collapsible_expanded(self, *args):
        expand = self._expander.get_expanded()

        if not expand:
            self.collapsible_y = self.get_position()

            # move the lower pane to the bottom since it's collapsed
            self._collapse()
        else:
            # reinstate the lower pane to it's expanded size
            if not self.collapsible_y:
                # if there isn't a saved size, use half of the space
                new_y = self.get_allocated_height() / 2
                self.collapsible_y = new_y

            # if the calculated new position is less than the minimum then
            # use half the space

            current_pos = self.get_allocated_height() - \
                          self.get_handle_window().get_height()

            if ((current_pos - self.collapsible_y) < self.Min_Paned_Size):
                self.collapsible_y = self.get_allocated_height() / 2

            self.set_position(self.collapsible_y)

        self.emit('expanded', expand)

    def do_button_press_event(self, event):
        '''
        This callback allows or denies the paned handle to move depending on
        the expanded expander
        '''
        # if not self._expander or self._expander.get_expanded():
        self._from_paned_handle = 1

        if event.type == Gdk.EventType._2BUTTON_PRESS:
            self._from_paned_handle = 2

        Gtk.Paned.do_button_press_event(self, event)

    def do_button_release_event(self, *args):
        '''
        Callback when the paned handle is released from its mouse click.
        '''
        if self._from_paned_handle != 0:
            Gtk.Paned.do_button_release_event(self, *args)

        if (not self._expander or self._expander.get_expanded()) and self._from_paned_handle == 1:
            print("in an expanded situation")
            self.collapsible_y = self.get_position()

            # if the current paned handle pos is less than the minimum the force a collapse
            current_pos = self.get_allocated_height() - \
                          self.get_handle_window().get_height()

            if ((current_pos - self.collapsible_y) < self.Min_Paned_Size):
                self.expand(PanedCollapsible.Paned.COLLAPSE)

        if self._from_paned_handle == 2:
            # we are dealing with a double click situation

            if self._expander.get_expanded():
                # if we are in an expanded position - lets collapse the pane
                print("collapsing")
                self.expand(PanedCollapsible.Paned.COLLAPSE)
            else:
                # the current paned position is closed, so lets open the pane fully
                self.expand(PanedCollapsible.Paned.EXPAND)
                print("expanding")
                self.set_position(0)
        print(self.get_position())
        self._from_paned_handle = 0

    def do_remove(self, widget):
        '''
        Overwrites the super class remove method, taking care of removing the
        child even if it's wrapped inside an Expander.
        '''
        if self.collapsible1 and self.get_child1().get_child() is widget:
            expander = self.get_child1()
            expander.remove(widget)
            widget = expander
        elif self.collapsible2 and self.get_child2().get_child() is widget:
            expander = self.get_child2()
            expander.remove(widget)
            widget = expander

        self._expander = None

        Gtk.Paned.remove(self, widget)

    def do_add(self, widget):
        '''
        This method had to be overridden to allow the add and packs method to
        work with Glade.
        '''
        if not self.get_child1():
            self.do_add1(widget)
        elif not self.get_child2():
            self.do_add2(widget)
        else:
            print("GtkPaned cannot have more than 2 children")

    def do_add1(self, widget):
        '''
        Overrides the add1 superclass' method for pack1 to work correctly.
        '''
        self.do_pack1(widget, True, True)

    def do_pack1(self, widget, *args, **kwargs):
        '''
        Packs the widget into the first paned child, adding a GtkExpander
        around the packed widget if the collapsible1 property is True.
        '''
        if self.collapsible1:
            widget = self._create_expander(widget)

        Gtk.Paned.pack1(self, widget, *args, **kwargs)

    def do_add2(self, widget):
        '''
        Overrides the add2 superclass' method for pack2 to work correctly.
        '''
        self.do_pack2(widget, True, True)

    def do_pack2(self, widget, *args, **kwargs):
        '''
        Packs the widget into the second paned child, adding a GtkExpander
        around the packed widget if the collapsible2 property is True.
        '''
        if self.collapsible2:
            widget = self._create_expander(widget)

        Gtk.Paned.pack2(self, widget, *args, **kwargs)

    def _create_expander(self, widget):
        # self._expander = Gtk.Expander(label=self.collapsible_label,
        #                              visible=True)
        self._expander = HiddenExpander(label=self.collapsible_label,
                                        visible=True)

        self._expander.add(widget)

        # connect the expanded signal
        self._expander.connect('notify::expanded',
                               self._on_collapsible_expanded)

        # connect the initial collapse
        self._allocate_id = self._expander.connect('size-allocate',
                                                   self._initial_collapse)

        return self._expander

    def _initial_collapse(self, *args):
        self._collapse()
        self._expander.disconnect(self._allocate_id)
        del self._allocate_id

    def _collapse(self):
        new_y = self.get_allocated_height() - \
                self.get_handle_window().get_height()  # - \
        # self._expander.get_label_widget().get_allocated_height()

        self.set_position(new_y)

    def expand(self, force):
        '''
        Toggles the expanded property of the collapsible children.
        unless requested to force expansion
        '''
        if self._expander:
            if force == PanedCollapsible.Paned.EXPAND:
                self._expander.set_expanded(True)
            elif force == PanedCollapsible.Paned.COLLAPSE:
                self._expander.set_expanded(False)
            elif force == PanedCollapsible.Paned.DEFAULT:
                self._expander.set_expanded(not self._expander.get_expanded())

    def get_expansion_status(self):
        '''
        returns the position of the expander i.e. expanded or not
        '''
        value = PanedCollapsible.Paned.COLLAPSE
        if self._expander and self._expander.get_expanded():
            value = PanedCollapsible.Paned.EXPAND

        return value class AlbumQuickSearchController(object):
    def __init__(self, album_manager):
        self._album_manager = album_manager

    def connect_quick_search(self, quick_search):
        quick_search.connect('quick-search', self._on_quick_search)
        quick_search.connect('arrow-pressed', self._on_arrow_pressed)
        quick_search.connect('hide', self._on_hide)

    def _on_quick_search(self, quick_search, search_text, *args):
        album = self._album_manager.model.find_first_visible('album_name',
                                                             search_text)

        if album:
            path = self._album_manager.model.get_path(album)
            self._album_manager.current_view.select_and_scroll_to_path(path)

    def _on_arrow_pressed(self, quick_search, key, *args):
        current = self._album_manager.current_view.get_selected_objects()[0]
        search_text = quick_search.get_text()
        album = None

        if key == Gdk.KEY_Up:
            album = self._album_manager.model.find_first_visible(
                'album_name', search_text, current, True)
        elif key == Gdk.KEY_Down:
            album = self._album_manager.model.find_first_visible(
                'album_name', search_text, current)

        if album:
            path = self._album_manager.model.get_path(album)
            self._album_manager.current_view.select_and_scroll_to_path(path)

    def _on_hide(self, quick_search, *args):
        self._album_manager.current_view.grab_focus() class ViewController(OptionsController):
    def __init__(self, shell, viewmgr):
        super(ViewController, self).__init__()

        self._viewmgr = viewmgr

        from coverart_browser_source import Views

        views = Views(shell)

        self.values = OrderedDict()
        for view_name in views.get_view_names():
            self.values[views.get_menu_name(view_name)] = view_name
            print(view_name)

        self.options = list(self.values.keys())
        viewmgr.connect('new-view', self.on_notify_view_name)

    def on_notify_view_name(self, *args):
        for key in self.options:
            if self.values[key] == self._viewmgr.view_name:
                self.current_key = key

    def do_action(self):
        if self._viewmgr.view_name != self.values[self.current_key]:
            self._viewmgr.view_name = self.values[self.current_key] class CoverArtExport(GObject.Object):
    '''
    This class provides for various export routines
    
    '''
    TARGET_BITRATE = 128

    def __init__(self, plugin, shell, album_manager):
        self.plugin = plugin
        self.shell = shell
        self.album_manager = album_manager

        self._gstreamer_has_initialised = False

    def is_search_plugin_enabled(self):
        peas = Peas.Engine.get_default()
        loaded_plugins = peas.get_loaded_plugins()

        result = False
        if 'coverart_search_providers' in loaded_plugins:
            info = peas.get_plugin_info('coverart_search_providers')
            version = info.get_version()

            if NaturalString(version) >= "0.9":
                result = True

        return result

    def embed_albums(self, selected_albums):
        '''
        method to create the menu items for all supported plugins

        :selected_albums: `Album` - array of albums
        '''

        self._initialise_gstreamer()

        from coverart_search_tracks import CoverArtTracks

        search_tracks = CoverArtTracks()
        playlist_manager = self.shell.props.playlist_manager
        playlists_entries = playlist_manager.get_playlists()

        ui = Gtk.Builder()
        ui.add_from_file(rb.find_plugin_file(self.plugin,
                                             'ui/coverart_exportembed.ui'))
        ui.connect_signals(self)
        embeddialog = ui.get_object('exportembeddialog')
        embeddialog.set_transient_for(self.shell.props.window)
        folderchooserbutton = ui.get_object('folderchooserbutton')
        use_album_name_checkbutton = ui.get_object('use_album_name_checkbutton')
        open_filemanager_checkbutton = ui.get_object('open_filemanager_checkbutton')
        convert_checkbutton = ui.get_object('convert_checkbutton')
        bitrate_spinbutton = ui.get_object('bitrate_spinbutton')
        resize_checkbutton = ui.get_object('resize_checkbutton')
        resize_spinbutton = ui.get_object('resize_spinbutton')
        bitrate_spinbutton.set_value(self.TARGET_BITRATE)
        resize_spinbutton.set_value(128)

        downloads_dir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOWNLOAD)
        folderchooserbutton.set_current_folder(downloads_dir)

        response = embeddialog.run()

        if response != Gtk.ResponseType.OK:
            embeddialog.destroy()
            return

        # ok pressed - now fetch values from the dialog
        final_folder_store = folderchooserbutton.get_current_folder()
        use_album_name = use_album_name_checkbutton.get_active()
        open_filemanager = open_filemanager_checkbutton.get_active()
        convert = convert_checkbutton.get_active()
        bitrate = bitrate_spinbutton.get_value()
        toresize = resize_checkbutton.get_active()
        if toresize:
            resize = int(resize_spinbutton.get_value())
        else:
            resize = -1

        embeddialog.destroy()

        albums = {}
        total = 0

        for album in selected_albums:
            albums[album] = album.get_tracks()
            total = total + len(albums[album])

        self._track_count = 1

        def complete():
            self.album_manager.progress = 1

            if open_filemanager:
                #code taken from http://stackoverflow.com/questions/1795111/is-there-a-cross-platform-way-to-open-a-file-browser-in-python
                if sys.platform == 'win32':
                    import winreg

                    path = r('SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon')
                    for root in (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE):
                        try:
                            with winreg.OpenKey(root, path) as k:
                                value, regtype = winreg.QueryValueEx(k, 'Shell')
                        except WindowsError:
                            pass
                        else:
                            if regtype in (winreg.REG_SZ, winreg.REG_EXPAND_SZ):
                                shell = value
                            break
                    else:
                        shell = 'Explorer.exe'
                    subprocess.Popen([shell, final_folder_store])

                elif sys.platform == 'darwin':
                    subprocess.Popen(['open', final_folder_store])

                else:
                    subprocess.Popen(['xdg-open', final_folder_store])

        self._albumiter = iter(albums)
        self._tracknumber = 0
        self._album = next(self._albumiter)

        def idle_call(data):
            exit_idle = True

            track = albums[self._album][self._tracknumber]

            if not process_track(self._album, track):
                exit_idle = False

            self._tracknumber = self._tracknumber + 1

            if self._tracknumber >= len(albums[self._album]):
                try:
                    self._tracknumber = 0
                    self._album = next(self._albumiter)
                except StopIteration:
                    exit_idle = False

            if not exit_idle:
                complete()

            return exit_idle

        def process_track(album, track):
            self.album_manager.progress = self._track_count / total
            self._track_count = self._track_count + 1

            key = album.create_ext_db_key()
            finalPath = rb3compat.unquote(track.location)[7:]
            album_name = RB.search_fold(album.name)

            if use_album_name:
                folder_store = final_folder_store + '/' + album_name
            else:
                folder_store = final_folder_store

            try:
                if not os.path.exists(folder_store):
                    os.makedirs(folder_store)

                if convert:
                    self.convert_to_mp3(finalPath, folder_store, bitrate)
                    finalPath = self._calc_mp3_filename(finalPath, folder_store)
                    print(finalPath)
                else:
                    shutil.copy(finalPath, folder_store)
            except IOError as err:
                print(err.args[0])
                return False

            dest = os.path.join(folder_store, os.path.basename(finalPath))
            desturi = 'file://' + rb3compat.pathname2url(dest)

            return search_tracks.embed(desturi, key, resize)

        data = None

        Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, idle_call, data)

    def _initialise_gstreamer(self):

        if self._gstreamer_has_initialised:
            return

        self._gstreamer_has_initialised = True
        Gst.init(None)

        def on_new_decoded_pad(dbin, pad):
            decode = pad.get_parent()
            pipeline = decode.get_parent()
            convert = pipeline.get_by_name('convert')
            decode.link(convert)

        # we are going to mimic the following
        # gst-launch-1.0 filesrc location="02 - ABBA - Knowing Me, Knowing You.ogg" ! 
        # decodebin ! audioconvert ! audioresample ! lamemp3enc target=bitrate bitrate=128 ! 
        # xingmux ! id3v2mux ! filesink location="mytrack.mp3"

        converter = Gst.Pipeline.new('converter')

        source = Gst.ElementFactory.make('filesrc', None)

        decoder = Gst.ElementFactory.make('decodebin', 'decoder')
        convert = Gst.ElementFactory.make('audioconvert', 'convert')
        sample = Gst.ElementFactory.make('audioresample', 'sample')
        encoder = Gst.ElementFactory.make('lamemp3enc', 'encoder')
        encoder.set_property('target', 'bitrate')
        encoder.set_property('bitrate', self.TARGET_BITRATE)

        xing = Gst.ElementFactory.make('xingmux', 'xing')  # needed to make bitrate more accurate
        mux = Gst.ElementFactory.make('id3v2mux', 'mux')
        if not mux:
            # use id3mux where not available
            mux = Gst.ElementFactory.make('id3mux', 'mux')

        sink = Gst.ElementFactory.make('filesink', 'sink')

        converter.add(source)
        converter.add(decoder)
        converter.add(convert)
        converter.add(sample)
        converter.add(encoder)
        converter.add(xing)
        converter.add(mux)
        converter.add(sink)

        Gst.Element.link(source, decoder)
        #note - a decodebin cannot be linked at compile since
        #it doesnt have source-pads (http://stackoverflow.com/questions/2993777/gstreamer-of-pythons-gst-linkerror-problem)

        decoder.connect("pad-added", on_new_decoded_pad)

        Gst.Element.link(convert, sample)
        Gst.Element.link(sample, encoder)
        Gst.Element.link(encoder, xing)
        Gst.Element.link(xing, mux)
        Gst.Element.link(mux, sink)

        self.converter = converter
        self.source = source
        self.sink = sink
        self.encoder = encoder

    def _calc_mp3_filename(self, filename, save_folder):
        finalname = os.path.basename(filename)
        finalname = finalname.rsplit('.')[0] + ".mp3"
        return save_folder + "/" + finalname

    def convert_to_mp3(self, filename, save_folder, bitrate):

        self.source.set_property('location', filename)
        self.sink.set_property('location', self._calc_mp3_filename(filename, save_folder))
        print(bitrate)
        if bitrate < 32:
            bitrate = self.TARGET_BITRATE

        self.encoder.set_property('bitrate', int(bitrate))
        print(bitrate)

        # Start playing
        ret = self.converter.set_state(Gst.State.PLAYING)

        if ret == Gst.StateChangeReturn.FAILURE:
            print("Unable to set the pipeline to the playing state.", sys.stderr)
            exit(-1)

        # Wait until error or EOS
        bus = self.converter.get_bus()
        try:
            msg = bus.timed_pop_filtered(
                Gst.CLOCK_TIME_NONE, Gst.MessageType.ERROR | Gst.MessageType.EOS)
        except:
            # for some reason in ubuntu 12.04 Gst.CLOCK_TIME_NONE fails
            msg = bus.timed_pop_filtered(
                18446744073709551615, Gst.MessageType.ERROR | Gst.MessageType.EOS)

        # Parse message
        if (msg):
            if msg.type == Gst.MessageType.ERROR:
                err, debug = msg.parse_error()
                print("Error received from element %s: %s" % (
                    msg.src.get_name(), err), sys.stderr)
                print("Debugging information: %s" % debug, sys.stderr)
            elif msg.type == Gst.MessageType.EOS:
                print("End-Of-Stream reached.")
            else:
                print("Unexpected message received.", sys.stderr)

        # Free resources
        self.converter.set_state(Gst.State.NULL) class Menu(GObject.Object):
    '''
    Menu object used to create window popup menus
    '''
    __gsignals__ = {
        'pre-popup': (GObject.SIGNAL_RUN_LAST, None, ())
    }

    def __init__(self, plugin, shell):
        '''
        Initializes the menu.
        '''
        super(Menu, self).__init__()
        self.plugin = plugin
        self.shell = shell
        self._unique_num = 0

        self._rbmenu_items = {}
        self._rbmenu_objects = {}

    def add_menu_item(self, menubar, section_name, action):
        '''
        add a new menu item to the popup
        :param menubar: `str` is the name GtkMenu (or ignored for RB2.99+)
        :param section_name: `str` is the name of the section to add the item to (RB2.99+)
        :param action: `Action`  to associate with the menu item
        '''
        return self.insert_menu_item(menubar, section_name, -1, action)

    def insert_menu_item(self, menubar, section_name, position, action):
        '''
        add a new menu item to the popup
        :param menubar: `str` is the name GtkMenu (or ignored for RB2.99+)
        :param section_name: `str` is the name of the section to add the item to (RB2.99+)
        :param position: `int` position to add to GtkMenu (ignored for RB2.99+)
        :param action: `Action`  to associate with the menu item
        '''
        label = action.label

        if is_rb3(self.shell):
            app = self.shell.props.application
            item = Gio.MenuItem()
            action.associate_menuitem(item)
            item.set_label(label)

            if not section_name in self._rbmenu_items:
                self._rbmenu_items[section_name] = []
            self._rbmenu_items[section_name].append(label)

            app.add_plugin_menu_item(section_name, label, item)
        else:
            item = Gtk.MenuItem(label=label)
            action.associate_menuitem(item)
            self._rbmenu_items[label] = item
            bar = self.get_menu_object(menubar)

            if position == -1:
                bar.append(item)
            else:
                bar.insert(item, position)
            bar.show_all()
            uim = self.shell.props.ui_manager
            uim.ensure_update()

        return item

    def insert_separator(self, menubar, at_position):
        '''
        add a separator to the popup (only required for RB2.98 and earlier)
        :param menubar: `str` is the name GtkMenu (or ignored for RB2.99+)
        :param position: `int` position to add to GtkMenu (ignored for RB2.99+)
        '''
        if not is_rb3(self.shell):
            menu_item = Gtk.SeparatorMenuItem().new()
            menu_item.set_visible(True)
            self._rbmenu_items['separator' + str(self._unique_num)] = menu_item
            self._unique_num = self._unique_num + 1
            bar = self.get_menu_object(menubar)
            bar.insert(menu_item, at_position)
            bar.show_all()
            uim = self.shell.props.ui_manager
            uim.ensure_update()

    def remove_menu_items(self, menubar, section_name):
        '''
        utility function to remove all menuitems associated with the menu section
        :param menubar: `str` is the name of the GtkMenu containing the menu items (ignored for RB2.99+)
        :param section_name: `str` is the name of the section containing the menu items (for RB2.99+ only)
        '''
        if is_rb3(self.shell):
            if not section_name in self._rbmenu_items:
                return

            app = self.shell.props.application

            for menu_item in self._rbmenu_items[section_name]:
                app.remove_plugin_menu_item(section_name, menu_item)

            if self._rbmenu_items[section_name]:
                del self._rbmenu_items[section_name][:]

        else:

            if not self._rbmenu_items:
                return

            uim = self.shell.props.ui_manager
            bar = self.get_menu_object(menubar)

            for menu_item in self._rbmenu_items:
                bar.remove(self._rbmenu_items[menu_item])

            bar.show_all()
            uim.ensure_update()

    def load_from_file(self, rb2_ui_filename, rb3_ui_filename):
        '''
        utility function to load the menu structure
        :param rb2_ui_filename: `str` RB2.98 and below UI file
        :param rb3_ui_filename: `str` RB2.99 and higher UI file
        '''
        self.builder = Gtk.Builder()
        try:
            from coverart_browser_prefs import CoverLocale

            cl = CoverLocale()

            self.builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN)
        except:
            pass

        if is_rb3(self.shell):
            ui_filename = rb3_ui_filename
        else:
            ui_filename = rb2_ui_filename

        self.ui_filename = ui_filename

        self.builder.add_from_file(rb.find_plugin_file(self.plugin,
                                                       ui_filename))

    def _connect_rb3_signals(self, signals):
        def _menu_connect(action_name, func):
            action = Gio.SimpleAction(name=action_name)
            action.connect('activate', func)
            action.set_enabled(True)
            self.shell.props.window.add_action(action)

        for key, value in signals.items():
            _menu_connect(key, value)

    def _connect_rb2_signals(self, signals):
        def _menu_connect(menu_item_name, func):
            menu_item = self.get_menu_object(menu_item_name)
            menu_item.connect('activate', func)

        for key, value in signals.items():
            _menu_connect(key, value)

    def connect_signals(self, signals):
        '''
        connect all signal handlers with their menuitem counterparts
        :param signals: `dict` key is the name of the menuitem 
             and value is the function callback when the menu is activated
        '''
        if is_rb3(self.shell):
            self._connect_rb3_signals(signals)
        else:
            self._connect_rb2_signals(signals)

    def get_gtkmenu(self, source, popup_name):
        '''
        utility function to obtain the GtkMenu from the menu UI file
        :param popup_name: `str` is the name menu-id in the UI file
        '''
        if popup_name in self._rbmenu_objects:
            return self._rbmenu_objects[popup_name]
        item = self.builder.get_object(popup_name)

        if is_rb3(self.shell):
            app = self.shell.props.application
            app.link_shared_menus(item)
            popup_menu = Gtk.Menu.new_from_model(item)
            popup_menu.attach_to_widget(source, None)
        else:
            popup_menu = item

        self._rbmenu_objects[popup_name] = popup_menu

        return popup_menu

    def get_menu_object(self, menu_name_or_link):
        '''
        utility function returns the GtkMenuItem/Gio.MenuItem
        :param menu_name_or_link: `str` to search for in the UI file
        '''
        if menu_name_or_link in self._rbmenu_objects:
            return self._rbmenu_objects[menu_name_or_link]
        item = self.builder.get_object(menu_name_or_link)
        if is_rb3(self.shell):
            if item:
                popup_menu = item
            else:
                app = self.shell.props.application
                popup_menu = app.get_plugin_menu(menu_name_or_link)
        else:
            popup_menu = item
        print(menu_name_or_link)
        self._rbmenu_objects[menu_name_or_link] = popup_menu

        return popup_menu

    def set_sensitive(self, menu_or_action_item, enable):
        '''
        utility function to enable/disable a menu-item
        :param menu_or_action_item: `GtkMenuItem` or `Gio.SimpleAction`
           that is to be enabled/disabled
        :param enable: `bool` value to enable/disable
        '''

        if is_rb3(self.shell):
            item = self.shell.props.window.lookup_action(menu_or_action_item)
            item.set_enabled(enable)
        else:
            item = self.get_menu_object(menu_or_action_item)
            item.set_sensitive(enable)

    def popup(self, source, menu_name, button, time):
        '''
        utility function to show the popup menu
        '''
        self.emit('pre-popup')
        menu = self.get_gtkmenu(source, menu_name)
        menu.popup(None, None, None, None, button, time) class ActionGroup(object):
    '''
    container for all Actions used to associate with menu items
    '''

    # action_state
    STANDARD = 0
    TOGGLE = 1

    def __init__(self, shell, group_name):
        '''
        constructor
        :param shell: `RBShell`
        :param group_name: `str` unique name for the object to create
        '''
        self.group_name = group_name
        self.shell = shell

        self._actions = {}

        if is_rb3(self.shell):
            self.actiongroup = Gio.SimpleActionGroup()
        else:
            self.actiongroup = Gtk.ActionGroup(group_name)
            uim = self.shell.props.ui_manager
            uim.insert_action_group(self.actiongroup)

    @property
    def name(self):
        return self.group_name

    def remove_actions(self):
        '''
        utility function to remove all actions associated with the ActionGroup
        '''
        for action in self.actiongroup.list_actions():
            self.actiongroup.remove_action(action)

    def get_action(self, action_name):
        '''
        utility function to obtain the Action from the ActionGroup
        
        :param action_name: `str` is the Action unique name
        '''
        return self._actions[action_name]

    def add_action_with_accel(self, func, action_name, accel, **args):
        '''
        Creates an Action with an accelerator and adds it to the ActionGroup
        
        :param func: function callback used when user activates the action
        :param action_name: `str` unique name to associate with an action
        :param accel: `str` accelerator
        :param args: dict of arguments - this is passed to the function callback
        
        Notes: 
        see notes for add_action
        '''
        args['accel'] = accel
        return self.add_action(func, action_name, **args)

    def add_action(self, func, action_name, **args):
        '''
        Creates an Action and adds it to the ActionGroup
        
        :param func: function callback used when user activates the action
        :param action_name: `str` unique name to associate with an action
        :param args: dict of arguments - this is passed to the function callback
        
        Notes: 
        key value of "label" is the visual menu label to display
        key value of "action_type" is the RB2.99 Gio.Action type ("win" or "app")
           by default it assumes all actions are "win" type
        key value of "action_state" determines what action state to create
        '''
        if 'label' in args:
            label = args['label']
        else:
            label = action_name

        if 'accel' in args:
            accel = args['accel']
        else:
            accel = None

        state = ActionGroup.STANDARD
        if 'action_state' in args:
            state = args['action_state']

        if is_rb3(self.shell):
            if state == ActionGroup.TOGGLE:
                action = Gio.SimpleAction.new_stateful(action_name, None,
                                                       GLib.Variant('b', False))
            else:
                action = Gio.SimpleAction.new(action_name, None)

            action_type = 'win'
            if 'action_type' in args:
                if args['action_type'] == 'app':
                    action_type = 'app'

            app = Gio.Application.get_default()

            if action_type == 'app':
                app.add_action(action)
            else:
                self.shell.props.window.add_action(action)
                self.actiongroup.add_action(action)

            if accel:
                app.add_accelerator(accel, action_type + "." + action_name, None)
        else:
            if 'stock_id' in args:
                stock_id = args['stock_id']
            else:
                stock_id = Gtk.STOCK_CLEAR

            if state == ActionGroup.TOGGLE:
                action = Gtk.ToggleAction(label=label,
                                          name=action_name,
                                          tooltip='', stock_id=stock_id)
            else:
                action = Gtk.Action(label=label,
                                    name=action_name,
                                    tooltip='', stock_id=stock_id)

            if accel:
                self.actiongroup.add_action_with_accel(action, accel)
            else:
                self.actiongroup.add_action(action)

        act = Action(self.shell, action)
        act.connect('activate', func, args)

        act.label = label
        act.accel = accel

        self._actions[action_name] = act

        return act 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)
    display_text_pos = GObject.property(type=bool, default=False)
    name = 'coverview'
    panedposition = PanedCollapsible.Paned.COLLAPSE
    text_alignment = GObject.property(type=int, default=1)

    __gsignals__ = {
        'update-toolbar': (GObject.SIGNAL_RUN_LAST, None, ())
    }

    def __init__(self, *args, **kwargs):
        super(CoverIconView, self).__init__(cell_area=AlbumArtCellArea(), *args, **kwargs)

        self.gs = GSetting()
        # custom text renderer
        self._text_renderer = None
        self.show_policy = AlbumShowingPolicy(self)
        self.view = self
        self._has_initialised = False
        self._last_path = None
        self._calc_motion_step = 0
        self.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
        self.object_column = AlbumsModel.columns['album']

    def initialise(self, source):
        if self._has_initialised:
            return

        self._has_initialised = True

        self.view_name = "covers_view"
        super(CoverIconView, self).initialise(source)

        self.shell = source.shell
        self.album_manager = source.album_manager

        # 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.source.paned.connect("expanded", self.bottom_expander_expanded_callback)

        # lastly support drag-drop from coverart to devices/nautilus etc
        self.connect('drag-begin', self.on_drag_begin)
        self.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK,
            [], Gdk.DragAction.COPY)
        # targets = Gtk.TargetList.new([Gtk.TargetEntry.new("application/x-rhythmbox-entry", 0, 0),
        # Gtk.TargetEntry.new("text/uri-list", 0, 1) ])
        targets = Gtk.TargetList.new([Gtk.TargetEntry.new("text/uri-list", 0, 0)])
        # N.B. values taken from rhythmbox v2.97 widgets/rb_entry_view.c
        targets.add_uri_targets(1)

        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_pixbuf_column(AlbumsModel.columns['pixbuf'])
        self.set_model(self.album_manager.model.store)

        # setup view to monitor mouse movements
        self.add_events(Gdk.EventMask.POINTER_MOTION_MASK)

        self.hover_pixbufs = {
            'button_play': None,
            'button_play_hover': None,
            'button_playpause': None,
            'button_playpause_hover': None,
            'button_queue': None,
            'button_queue_hover': None,
        }

        for pixbuf_type in self.hover_pixbufs:
            filename = 'img/' + pixbuf_type + '.png'
            filename = rb.find_plugin_file(self.plugin, filename)
            self.hover_pixbufs[pixbuf_type] = GdkPixbuf.Pixbuf.new_from_file_at_size(filename,
                                                                                     PLAY_SIZE_X,
                                                                                     PLAY_SIZE_Y)

        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)

        setting.bind(self.gs.PluginKey.DISPLAY_TEXT_POS, self,
                     'display-text-pos', Gio.SettingsBindFlags.GET)

        setting.bind(self.gs.PluginKey.TEXT_ALIGNMENT, self,
                     'text-alignment', 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)
        self.connect('notify::display-text-pos',
                     self._activate_markup)
        self.connect('notify::text-alignment',
                     self._create_and_configure_renderer)
        self.connect("motion-notify-event", self.on_pointer_motion)

        self.add_events(Gdk.EventMask.SCROLL_MASK)
        self.connect("scroll-event", self.on_scroll_event)

    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 on_scroll_event(self, widget, scroll_event):
        if scroll_event.state & Gdk.ModifierType.CONTROL_MASK:
            settings = self.gs.get_setting(self.gs.Path.PLUGIN)
            cover_size = settings[self.gs.PluginKey.COVER_SIZE]
            if scroll_event.direction == Gdk.ScrollDirection.UP:
                if cover_size <= 195:
                    settings[self.gs.PluginKey.COVER_SIZE] = cover_size + 5
            elif scroll_event.direction == Gdk.ScrollDirection.DOWN:
                if cover_size >= 55:
                    settings[self.gs.PluginKey.COVER_SIZE] = cover_size - 5
            elif scroll_event.direction == Gdk.ScrollDirection.SMOOTH:
                delta = scroll_event.delta_y
                print (delta)
                if delta < 0 and cover_size <= 195: # negative delta means scroll up
                    settings[self.gs.PluginKey.COVER_SIZE] = cover_size - int(delta * 5)
                if delta > 0 and cover_size >= 55: # positive delta means scroll down
                    settings[self.gs.PluginKey.COVER_SIZE] = cover_size - int(delta * 5)

            GLib.idle_add(self.queue_draw)

            return 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)
        widget.stop_emission_by_name('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)
        widget.stop_emission_by_name('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)

        sel = data.set_uris(uris)
        # stop the propagation of the signal (deactivates superclass callback)
        widget.stop_emission_by_name('drag-data-get')

    def on_drag_begin(self, widget, context):
        '''
        Callback called when the drag-drop from coverview has started
        Changes the drag icon as appropriate
        '''
        album_number = len(widget.get_selected_objects())

        if album_number == 1:
            item = Gtk.STOCK_DND
        else:
            item = Gtk.STOCK_DND_MULTIPLE

        widget.drag_source_set_icon_stock(item)
        widget.stop_emission_by_name('drag-begin')

    def _cover_play_hotspot(self, path, in_vacinity=False):
        if path:
            valid, rect = self.get_cell_rect(path, None)  # rect of widget coords

            cursor_x, cursor_y = self.get_pointer()  # returns widget coords
            c_x = cursor_x - rect.x - (self.icon_padding / 2) - (self.icon_spacing / 2)
            c_y = cursor_y - rect.y - (self.icon_padding / 2) - (self.icon_spacing / 2)

            sizing = (rect.width / 3) if in_vacinity else 0

            full, x_offset, y_offset = self.props.cell_area.calc_play_icon_offset(0, 0)
            if full and c_y > y_offset:
                return False

            y_offset = y_offset - PLAY_SIZE_Y

            if (y_offset - PLAY_SIZE_Y) < 0:
                return False

            if c_x < (PLAY_SIZE_X + sizing + x_offset) and \
                            c_y < (PLAY_SIZE_Y + sizing + y_offset) and \
                            c_x > x_offset and \
                            c_y > (y_offset - sizing):
                return True

                # c_y 0 value at top - largest at bottom of the cover
        return False

    def on_pointer_motion(self, widget, event):
        self._current_mouse_x = event.x
        self._current_mouse_y = event.y

        if self._calc_motion_step == 0:
            self._calc_motion_step = 1
            Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 100,
                                    self._calculate_hotspot)
        else:
            path = self.get_path_at_pos(self._current_mouse_x,
                                        self._current_mouse_y)

            if not self._last_path or self._last_path != path:
                self._display_icon(None, self._last_path)

    def _display_icon(self, icon, path):
        self.props.cell_area.hover_pixbuf = icon
        if path and self.props.window:
            valid, rect = self.get_cell_rect(path, None)
            self.props.window.invalidate_rect(rect, True)

        self.queue_draw()

    def _calculate_hotspot(self, *args):

        path = self.get_path_at_pos(self._current_mouse_x,
                                    self._current_mouse_y)

        # if the current path was not the same as the last path then
        # reset the counter
        if not self._last_path or self._last_path != path:
            self._display_icon(None, self._last_path)
            self._last_path = path
            self._calc_motion_step = 0
            return False

        self._calc_motion_step = self._calc_motion_step + 1

        # if havent yet reached the requisite number of steps then
        # let the thread roll to the next increment
        if self._calc_motion_step < 8:
            return True

        if not self._cover_play_hotspot(path, in_vacinity=True):
            # we are not near the hot-spot so decrement the counter
            # hoping next time around we are near
            self._calc_motion_step = self._calc_motion_step - 1
            self._display_icon(None, self._last_path)
            return True

        # from  here on in, we are going to display a hotspot icon
        # so lets decide which one

        (_, playing) = self.shell.props.shell_player.get_playing()

        calc_path = -1
        if playing:
            entry = self.shell.props.shell_player.get_playing_entry()
            album = self.album_manager.model.get_from_dbentry(entry)
            calc_path = self.album_manager.model.get_path(album)

        if playing and calc_path == path:
            icon = 'button_playpause'
        elif playing:
            icon = 'button_queue'
        else:
            icon = 'button_play'

        # now we've got the icon - lets double check that we are
        # actually hovering exactly on the hotspot because the icon will visually change

        exact_hotspot = self._cover_play_hotspot(path)
        if exact_hotspot:
            icon = icon + '_hover'

        hover = self.hover_pixbufs[icon]

        self._display_icon(hover, path)
        self._calc_motion_step = self._calc_motion_step - 1

        return True

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

        # first test if we've clicked on the cover-play icon
        if self._cover_play_hotspot(path):
            (_, playing) = self.shell.props.shell_player.get_playing()

            # first see if anything is playing...
            if playing:
                entry = self.shell.props.shell_player.get_playing_entry()
                album = self.album_manager.model.get_from_dbentry(entry)

                # if the current playing entry corresponds to the album
                # we are hovering over then we are requesting to pause
                if self.album_manager.model.get_from_path(path) == album:
                    self._last_path = path
                    self.shell.props.shell_player.pause()
                    self.on_pointer_motion(self, event)
                    return

            # this must be a new album so we are asking just
            # to play this new album ... just need a short interval
            # for the selection event to kick in first
            def delay(*args):
                if playing:  # if we are playing then queue up the next album
                    self.source.queue_selected_album(None, self.source.favourites)
                    album = self.get_selected_objects()[0]
                    cl = CoverLocale()
                    cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
                    message = gettext.gettext('Album has added to list of playing albums')
                    self.display_notification(album.name,
                                              message,
                                              album.cover.original)
                else:  # otherwise just play it
                    self._last_path = path
                    self.source.play_selected_album(self.source.favourites)

                icon = 'button_play_hover'
                self.props.cell_area.hover_pixbuf = \
                    self.hover_pixbufs[icon]

            Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250,
                                    delay, None)

            return

        # 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(self.source.favourites)

        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, *args):
        if not self._text_renderer:
            # Add own cellrenderer
            self._text_renderer = Gtk.CellRendererText()

        self._text_renderer.props.alignment = self.text_alignment
        self._text_renderer.props.wrap_mode = Pango.WrapMode.WORD
        if self.text_alignment == 1:
            self._text_renderer.props.xalign = 0.5
        elif self.text_alignment == 0:
            self._text_renderer.props.xalign = 0
        else:
            self._text_renderer.props.xalign = 1

        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 and self.display_text_pos:
            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)

        if self.display_text_enabled:
            self.set_tooltip_column(-1)  # turnoff tooltips
        else:
            self.set_tooltip_column(AlbumsModel.columns['tooltip'])

    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)

        self.scroll_to_album(album)

    def grab_focus(self):
        super(EnhancedIconView, self).grab_focus() 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.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 initialise(self, source):
        if self._has_initialised:
            return

        self._has_initialised = True

        super(CoverFlowView, self).initialise(source)

        self.album_manager = source.album_manager
        self.ext_menu_pos = 6

        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):
        self.last_album = album
        self.popup.popup(self.source, 'popup_menu', 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(self.last_album)

    def grab_focus(self):
        self.view.grab_focus()

    def scroll_to_album(self, album):
        self.flow.scroll_to_album(album, self.view) class ArtistView(Gtk.TreeView, AbstractView):
    __gtype_name__ = "ArtistView"

    name = 'artistview'
    icon_automatic = GObject.property(type=bool, default=True)
    panedposition = PanedCollapsible.Paned.COLLAPSE

    __gsignals__ = {
        'update-toolbar': (GObject.SIGNAL_RUN_LAST, None, ())
    }


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

        self._external_plugins = None
        self.gs = GSetting()
        self.show_policy = ArtistShowingPolicy(self)
        self.view = self
        self._has_initialised = False
        self._last_row_was_artist = False

    def initialise(self, source):
        if self._has_initialised:
            return

        self._has_initialised = True

        self.view_name = "artist_view"
        super(ArtistView, self).initialise(source)
        self.album_manager = source.album_manager
        self.shell = source.shell
        self.props.has_tooltip = True

        self.set_enable_tree_lines(True)

        col = Gtk.TreeViewColumn('       ', Gtk.CellRendererText(), text=6)
        self.append_column(col)

        pixbuf = Gtk.CellRendererPixbuf()
        col = Gtk.TreeViewColumn(_('Covers'), pixbuf, pixbuf=1)

        self.append_column(col)

        col = Gtk.TreeViewColumn(_('Artist'), Gtk.CellRendererText(), markup=5)
        self._artist_col = col
        col.set_clickable(True)
        col.set_sort_column_id(0)
        col.set_sort_indicator(True)
        col.connect('clicked', self._artist_sort_clicked)
        self.append_column(col)
        col = Gtk.TreeViewColumn('', Gtk.CellRendererText(), text=4)
        self.append_column(col)  # dummy column to expand horizontally

        self.artist_manager = self.album_manager.artist_man
        self.artist_manager.model.store.set_sort_column_id(0, Gtk.SortType.ASCENDING)
        self.set_model(self.artist_manager.model.store)

        # 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)

        # lastly support drag-drop from coverart to devices/nautilus etc
        # n.b. enabling of drag-source is controlled by the selection-changed to ensure
        # we dont allow drag from artists
        self.connect('drag-begin', self.on_drag_begin)
        self._targets = Gtk.TargetList.new([Gtk.TargetEntry.new("text/uri-list", 0, 0)])

        # N.B. values taken from rhythmbox v2.97 widgets/rb_entry_view.c
        self._targets.add_uri_targets(1)
        self.connect("drag-data-get", self.on_drag_data_get)

        # define artist specific popup menu
        self.artist_popup_menu = Menu(self.plugin, self.shell)
        self.artist_popup_menu.load_from_file('ui/coverart_artist_pop_rb2.ui',
                                              'ui/coverart_artist_pop_rb3.ui')
        signals = \
            {'play_album_menu_item': self.source.play_album_menu_item_callback,
             'queue_album_menu_item': self.source.queue_album_menu_item_callback,
             'add_to_playing_menu_item': self.source.add_playlist_menu_item_callback,
             'new_playlist': self.source.add_playlist_menu_item_callback,
             'artist_cover_search_menu_item': self.cover_search_menu_item_callback
            }

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

        # connect properties and signals
        self._connect_properties()
        self._connect_signals()

    def _connect_properties(self):
        setting = self.gs.get_setting(self.gs.Path.PLUGIN)
        setting.bind(self.gs.PluginKey.ICON_AUTOMATIC, self,
                     'icon_automatic', Gio.SettingsBindFlags.GET)

    def _connect_signals(self):
        self.connect('row-activated', self._row_activated)
        self.connect('row-expanded', self._row_expanded)
        self.connect('button-press-event', self._row_click)
        self.get_selection().connect('changed', self._selection_changed)
        self.connect('query-tooltip', self._query_tooltip)

    def _artist_sort_clicked(self, *args):
        # in the absence of an apparent way to remove the unsorted default_sort_func
        # find out if we are now in an unsorted state - if we are
        # throw another clicked event so that we remain sorted.
        value, order = self.artist_manager.model.store.get_sort_column_id()

        if order == None:
            self._artist_col.emit('clicked')

    def cover_search_menu_item_callback(self, *args):
        self.artist_manager.cover_man.search_covers(self.get_selected_objects(just_artist=True),
                                                    callback=self.source.update_request_status_bar)

    def _query_tooltip(self, widget, x, y, key, tooltip):

        try:
            winx, winy = self.convert_widget_to_bin_window_coords(x, y)
            treepath, treecolumn, cellx, celly = self.get_path_at_pos(winx, winy)
            active_object = self.artist_manager.model.get_from_path(treepath)

            # active_object=self.artist_manager.model.store[treepath][self.artist_manager.model.columns['artist_album']]

            if isinstance(active_object, Artist) and \
                            treecolumn.get_title() == _('Covers') and \
                            active_object.cover.original != self.artist_manager.cover_man.unknown_cover.original:
                # we display the tooltip if the row is an artist and the column
                # is actually the artist cover itself
                pixbuf = GdkPixbuf.Pixbuf.new_from_file(active_object.cover.original)

                src_width = pixbuf.get_width()
                src_height = pixbuf.get_height()

                factor = min(float(256) / float(src_width), float(256) / float(src_height))
                new_width = int(src_width * factor + 0.5)
                new_height = int(src_height * factor + 0.5)

                pixbuf = create_pixbuf_from_file_at_size(
                    active_object.cover.original, new_width, new_height)

                tooltip.set_icon(pixbuf)
                return True
            else:
                return False

        except:
            pass

    def _row_expanded(self, treeview, treeiter, treepath):
        '''
        event called when clicking the expand icon on the treeview
        '''
        self._row_activated(treeview, treepath, _)

    def _row_activated(self, treeview, treepath, treeviewcolumn):
        '''
        event called when double clicking on the tree-view or by keyboard ENTER
        '''
        active_object = self.artist_manager.model.get_from_path(treepath)
        if isinstance(active_object, Artist):
            self.artist_manager.model.emit('update-path', treepath)
        else:
            # we need to play this album
            self.source.play_selected_album(self.source.favourites)

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

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

        # self.popup_menu.get_menu_object('add_to_playing_menu_item')
        self.artist_popup_menu.set_sensitive('add_to_playing_menu_item', sensitive)

        self.source.playlist_menu_item_callback()

    def _row_click(self, widget, event):
        '''
        event called when clicking on a row
        '''
        print('_row_click')

        try:
            treepath, treecolumn, cellx, celly = self.get_path_at_pos(event.x, event.y)
        except:
            return

        active_object = self.artist_manager.model.get_from_path(treepath)

        if not isinstance(active_object, Album):
            self.source.artist_info.emit('selected', active_object.name, None)
            if self.icon_automatic:
                # reset counter so that we get correct double click action for albums
                self.source.click_count = 0

            if treecolumn != self.get_expander_column():
                if self.row_expanded(treepath) and event.button == 1 and self._last_row_was_artist:
                    self.collapse_row(treepath)
                else:
                    self.expand_row(treepath, False)

                self._last_row_was_artist = True

                if event.button == 3:
                    # on right click
                    # display popup

                    self.artist_popup_menu.popup(self.source, 'popup_menu', 3,
                                                 Gtk.get_current_event_time())
            print('_row click artist exit')
            return

        if event.button == 1:
            # on click
            # to expand the entry view
            ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
            shift = event.state & Gdk.ModifierType.SHIFT_MASK

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

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

        elif event.button == 3:
            # on right click
            # display popup

            self.popup.popup(self.source, 'popup_menu', 3,
                             Gtk.get_current_event_time())

        self._last_row_was_artist = False

        print('_row_click album exit')
        return

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

    def _selection_changed(self, *args):
        selected = self.get_selected_objects(just_artist=True)

        print(selected)
        if len(selected) == 0:
            self.source.entry_view.clear()
            return

        if isinstance(selected[0], Artist):
            print("selected artist")
            self.unset_rows_drag_source()  # turn off drag-drop for artists

            self.source.entryviewpane.update_cover(selected[0],
                                                   self.artist_manager)
        else:
            print("selected album")
            self.source.update_with_selection()
            # now turnon drag-drop for album.
            self.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK,
                [], Gdk.DragAction.COPY)
            self.drag_source_set_target_list(self._targets)

    def switch_to_coverpane(self, cover_search_pane):
        '''
        called from the source to update the coverpane when
        it is switched from the track pane
        This overrides the base method
        '''

        selected = self.get_selected_objects(just_artist=True)

        if selected:
            manager = self.get_default_manager()
            self.source.entryviewpane.cover_search(selected[0],
                                                   manager)

    def get_selected_objects(self, just_artist=False):
        '''
        finds what has been selected

        returns an array of `Album`
        '''
        selection = self.get_selection()
        model, treeiter = selection.get_selected()
        if treeiter:
            active_object = model.get_value(treeiter, ArtistsModel.columns['artist_album'])
            if isinstance(active_object, Album):
                # have chosen an album then just return that album
                return [active_object]
            else:
                # must have chosen an artist - return all albums for the artist by default
                # or just the artist itself
                if not just_artist:
                    return self.artist_manager.model.get_albums(active_object.name)
                else:
                    return [active_object]
        return []

    def switch_to_view(self, source, album):
        self.initialise(source)
        self.show_policy.initialise(source.album_manager)

        self.scroll_to_album(album)

    def scroll_to_album(self, album):
        if album:
            print("switch to artist view")
            print(album)
            artist = self.artist_manager.model.get(album.artist)
            path = self.artist_manager.model.get_path(artist)
            print(artist)
            print(path)
            path = self.artist_manager.model.store.convert_child_path_to_path(path)
            print(path)
            if path:
                self.scroll_to_cell(path, self._artist_col)
                self.expand_row(path, False)
                self.set_cursor(path)

    def do_update_toolbar(self, *args):
        self.source.toolbar_manager.set_enabled(False, ToolbarObject.SORT_BY)
        self.source.toolbar_manager.set_enabled(False, ToolbarObject.SORT_ORDER)
        self.source.toolbar_manager.set_enabled(True, ToolbarObject.SORT_BY_ARTIST)
        self.source.toolbar_manager.set_enabled(True, ToolbarObject.SORT_ORDER_ARTIST)

    def on_drag_drop(self, widget, context, x, y, time):
        '''
        Callback called when a drag operation finishes over the 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)
        widget.stop_emission_by_name('drag-drop')

        # obtain the path of the icon over which the drag operation finished
        drop_info = self.get_dest_row_at_pos(x, y)
        path = None
        if drop_info:
            path, position = drop_info

        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)
        widget.stop_emission_by_name('drag-data-received')

        # get the artist and the info and ask the loader to update the cover
        path, position = self.get_dest_row_at_pos(x, y)
        artist_album = widget.get_model()[path][2]

        pixbuf = data.get_pixbuf()

        if isinstance(artist_album, Album):
            manager = self.album_manager
        else:
            manager = self.artist_manager

        if pixbuf:
            manager.cover_man.update_cover(artist_album, pixbuf)
        else:
            uri = data.get_text()
            manager.cover_man.update_cover(artist_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)
        widget.stop_emission_by_name('drag-data-get')

    def on_drag_begin(self, widget, context):
        '''
        Callback called when the drag-drop from coverview has started
        Changes the drag icon as appropriate
        '''
        album_number = len(widget.get_selected_objects())

        if album_number == 1:
            item = Gtk.STOCK_DND
        else:
            item = Gtk.STOCK_DND_MULTIPLE

        widget.drag_source_set_icon_stock(item)
        widget.stop_emission_by_name('drag-begin')

    def get_default_manager(self):
        '''
        the default manager for this view is the artist_manager
        '''
        return self.artist_manager class ListView(AbstractView):
    __gtype_name__ = "ListView"

    name = 'listview'
    use_plugin_window = False

    def __init__(self):
        super(ListView, self).__init__()
        self.view = self
        self._has_initialised = False
        self.show_policy = ListShowingPolicy(self)

    def initialise(self, source):
        if self._has_initialised:
            return

        self._has_initialised = True

        self.view_name = "list_view"
        super(ListView, self).initialise(source)
        # self.album_manager = source.album_manager
        self.shell = source.shell

    def switch_to_view(self, source, album):
        self.initialise(source)

        GLib.idle_add(self.shell.props.display_page_tree.select,
                      self.shell.props.library_source)

    def get_selected_objects(self):
        '''
        finds what has been selected

        returns an array of `Album`
        '''
        return [] class QueueView(AbstractView):
    __gtype_name__ = "QueueView"

    name = 'queueview'
    use_plugin_window = False

    def __init__(self):
        super(QueueView, self).__init__()
        self.view = self
        self._has_initialised = False
        self.show_policy = QueueShowingPolicy(self)

    def initialise(self, source):
        if self._has_initialised:
            return

        self._has_initialised = True

        self.view_name = "queue_view"
        super(QueueView, self).initialise(source)
        # self.album_manager = source.album_manager
        self.shell = source.shell

    def switch_to_view(self, source, album):
        self.initialise(source)

        GLib.idle_add(self.shell.props.display_page_tree.select,
                      self.shell.props.queue_source)

    def get_selected_objects(self):
        '''
        finds what has been selected

        returns an array of `Album`
        '''
        return [] class ToolbarManager(GObject.Object):
    # properties
    toolbar_pos = GObject.property(type=str, default=TopToolbar.name)

    def __init__(self, plugin, main_box, viewmgr):
        super(ToolbarManager, self).__init__()
        self.plugin = plugin
        # create the buttons controllers
        controllers = self._create_controllers(plugin, viewmgr)

        # initialize toolbars
        self._bars = {}
        self._bars[TopToolbar.name] = TopToolbar(plugin, main_box,
                                                 controllers)
        self._bars[LeftToolbar.name] = LeftToolbar(plugin, main_box,
                                                   controllers)
        self._bars[RightToolbar.name] = RightToolbar(plugin, main_box,
                                                     controllers)

        self.last_toolbar_pos = None
        # connect signal and properties
        self._connect_signals()
        self._connect_properties()

        self._controllers = controllers

        # if the alternative-toolbar is loaded then lets connect to the toolbar-visibility signal
        # to control our sources toolbar visibility

        if hasattr(self.plugin.shell, 'alternative_toolbar'):
            self.plugin.shell.alternative_toolbar.connect('toolbar-visibility', self._visibility)

    def _visibility(self, altplugin, value):
        if value:
            self._bars[self.toolbar_pos].show()
        else:
            self._bars[self.toolbar_pos].hide()

    def set_enabled(self, enabled, toolbar_object=None):
        '''
        enable or disable the toolbar object.
        
        :param enabled: `bool` value.
        :param toolbar_object: `ToolbarObject` 
           None if enabled is to apply to all objects in the toolbar
        
        '''
        if toolbar_object:
            self._controllers[toolbar_object].enabled = enabled
        else:
            for controller in self._controllers:
                self._controllers[controller].enabled = enabled

    def _connect_signals(self):
        self.connect('notify::toolbar-pos', self._on_notify_toolbar_pos)

    def _connect_properties(self):
        gs = GSetting()
        setting = gs.get_setting(gs.Path.PLUGIN)
        setting.bind(gs.PluginKey.TOOLBAR_POS, self, 'toolbar_pos',
                     Gio.SettingsBindFlags.GET)

    def _create_controllers(self, plugin, viewmgr):
        controllers = {}

        album_model = viewmgr.source.album_manager.model
        controllers[ToolbarObject.PROPERTIES] = \
            PropertiesMenuController(plugin, viewmgr.source)
        controllers[ToolbarObject.SORT_BY] = \
            SortPopupController(plugin, viewmgr)
        controllers[ToolbarObject.SORT_ORDER] = \
            SortOrderToggleController(plugin, viewmgr)
        controllers[ToolbarObject.SORT_BY_ARTIST] = \
            ArtistSortPopupController(plugin, viewmgr)
        controllers[ToolbarObject.SORT_ORDER_ARTIST] = \
            ArtistSortOrderToggleController(plugin, viewmgr)
        controllers[ToolbarObject.GENRE] = \
            GenrePopupController(plugin, album_model)
        controllers[ToolbarObject.PLAYLIST] = \
            PlaylistPopupController(plugin, album_model)
        controllers[ToolbarObject.DECADE] = \
            DecadePopupController(plugin, album_model)
        controllers[ToolbarObject.SEARCH] = \
            AlbumSearchEntryController(album_model)

        controllers[ToolbarObject.VIEW] = viewmgr.controller

        return controllers

    def _on_notify_toolbar_pos(self, *args):
        if self.last_toolbar_pos:
            self._bars[self.last_toolbar_pos].hide()

        self._bars[self.toolbar_pos].show()

        self.last_toolbar_pos = self.toolbar_pos class ArtistInfoPane(GObject.GObject):
    __gsignals__ = {
        'selected': (GObject.SIGNAL_RUN_LAST, None,
                     (GObject.TYPE_STRING, GObject.TYPE_STRING))
    }

    paned_pos = GObject.property(type=str)

    min_paned_pos = 100

    def __init__(self, button_box, stack, info_paned, source):
        GObject.GObject.__init__(self)

        self.ds = {}
        self.view = {}

        # self.buttons = button_box
        self.source = source
        self.plugin = source.plugin
        self.shell = source.shell
        self.info_paned = info_paned
        self.current_artist = None
        self.current_album_title = None
        self.current = 'artist'
        self._from_paned_handle = 0

        self.stack = stack
        self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
        stack_switcher = Gtk.StackSwitcher()
        stack_switcher.set_stack(self.stack)
        self.stack.connect('notify::visible-child-name', self.change_stack)
        button_box.pack_start(stack_switcher, False, False, 0)
        button_box.show_all()

        # cache for artist/album information: valid for a month, can be used indefinitely
        # if offline, discarded if unused for six months
        self.info_cache = rb.URLCache(name='info',
                                      path=os.path.join('coverart_browser', 'info'),
                                      refresh=30,
                                      discard=180)
        # cache for rankings (artist top tracks and top albums): valid for a week,
        # can be used for a month if offline
        self.ranking_cache = rb.URLCache(name='ranking',
                                         path=os.path.join('coverart_browser', 'ranking'),
                                         refresh=7,
                                         lifetime=30)

        self.info_cache.clean()
        self.ranking_cache.clean()

        self.ds['link'] = LinksDataSource()
        self.ds['artist'] = ArtistDataSource(self.info_cache,
                                             self.ranking_cache)

        self.view['artist'] = ArtistInfoView()
        self.view['artist'].initialise(self.source,
                                       self.shell,
                                       self.plugin,
                                       self.stack,
                                       self.ds['artist'],
                                       self.ds['link'])

        self.ds['album'] = AlbumDataSource(self.info_cache,
                                           self.ranking_cache)
        self.view['album'] = AlbumInfoView()
        self.view['album'].initialise(self.source,
                                      self.shell,
                                      self.plugin,
                                      self.stack,
                                      self.ds['album'])

        self.ds['echoartist'] = EchoArtistDataSource(
            self.info_cache,
            self.ranking_cache)
        self.view['echoartist'] = EchoArtistInfoView()
        self.view['echoartist'].initialise(self.source,
                                           self.shell,
                                           self.plugin,
                                           self.stack,
                                           self.ds['echoartist'],
                                           self.ds['link'])

        self.gs = GSetting()
        self.connect_properties()
        self.connect_signals()
        Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE,
                                50,
                                self._change_paned_pos,
                                self.source.viewmgr.view_name)
        self.view[self.current].activate()

    def connect_properties(self):
        '''
        Connects the source properties to the saved preferences.
        '''
        setting = self.gs.get_setting(self.gs.Path.PLUGIN)

        setting.bind(
            self.gs.PluginKey.ARTIST_INFO_PANED_POSITION,
            self,
            'paned-pos',
            Gio.SettingsBindFlags.DEFAULT)

    def connect_signals(self):
        self.tab_cb_ids = []

        # Listen for switch-tab signal from each tab
        '''
        for key, value in self.tab.items():
            self.tab_cb_ids.append(( key, 
                                    self.tab[key].connect ('switch-tab', 
                                                            self.change_tab)
                                    ))
        '''

        # Listen for selected signal from the views
        self.connect('selected', self.select_artist)

        # lets remember info paned click
        self.info_paned.connect('button_press_event',
                                self.paned_button_press_callback)
        self.info_paned.connect('button-release-event',
                                self.paned_button_release_callback)

        # lets also listen for changes to the view to set the paned position
        self.source.viewmgr.connect('new-view', self.on_view_changed)

    def on_view_changed(self, widget, view_name):
        self._change_paned_pos(view_name)

    def _change_paned_pos(self, view_name):
        print(self.paned_pos)
        paned_positions = eval(self.paned_pos)

        found = None
        for viewpos in paned_positions:
            if view_name in viewpos:
                found = viewpos
                break

        if not found:
            return

        values = found.split(":")
        child_width = int(values[1])

        open_type = "closed"
        if len(values) > 2:
            open_type = values[2]
        elif child_width > 0:
            open_type = "opened"

        if open_type == "closed":
            child_width = 0

        calc_pos = self.source.page.get_allocated_width() - child_width
        self.info_paned.set_position(calc_pos)
        self.info_paned.set_visible(True)

    def _get_child_width(self):
        child = self.info_paned.get_child2()
        return child.get_allocated_width()

    def paned_button_press_callback(self, widget, event):
        print('paned_button_press_callback')
        self._from_paned_handle = 1

        if event.type == Gdk.EventType._2BUTTON_PRESS:
            self._from_paned_handle = 2

    def paned_button_release_callback(self, *args):
        '''
        Callback when the artist paned handle is released from its mouse click.
        '''
        if self._from_paned_handle == 0:
            return False

        print("paned_button_release_callback")

        paned_positions = eval(self.paned_pos)

        found = None
        for viewpos in paned_positions:
            if self.source.viewmgr.view_name in viewpos:
                found = viewpos
                break

        if not found:
            print("cannot find")
            return True

        values = found.split(':')

        child_width = self.source.page.get_allocated_width() - self.info_paned.get_position()
        print(child_width)
        open_type = "closed"
        print(values)
        if len(values) > 2:
            open_type = values[2]

        if child_width <= self.min_paned_pos and \
                        self._from_paned_handle == 1 and \
                        open_type == "closed":
            # we are dealing with a situation where the pane is already closed
            # or almost closed - just shut the door
            print("we are closed")
            calc_pos = self.source.page.get_allocated_width()
            self.info_paned.set_position(calc_pos)
            return False

        open_type = "closed"
        paned_positions.remove(found)

        if self._from_paned_handle == 2:
            # we are dealing with a double click situation
            new_width = child_width
            if new_width <= self.min_paned_pos:
                if int(values[1]) == 0:
                    new_width = self.min_paned_pos + 1
                else:
                    new_width = int(values[1])

                open_type = "opened"
                child_width = new_width
            else:
                new_width = 0

            calc_pos = self.source.page.get_allocated_width() - new_width

            self.info_paned.set_position(calc_pos)

        if child_width <= self.min_paned_pos and self._from_paned_handle == 1:
            if int(values[1]) == 0:
                child_width = self.min_paned_pos + 1
                open_type = "opened"
            else:
                child_width = 0

            calc_pos = self.source.page.get_allocated_width() - child_width
            self.info_paned.set_position(calc_pos)

        if self._from_paned_handle == 1 and child_width != 0:
            open_type = "opened"

        paned_positions.append(self.source.viewmgr.view_name + \
                               ":" + \
                               str(child_width) + \
                               ":" + \
                               open_type)

        self.paned_pos = repr(paned_positions)

        self._from_paned_handle = 0
        print("End artist_info_paned_button_release_callback")

    def select_artist(self, widget, artist, album_title):
        print("artist %s title %s" % (artist, album_title))
        if self._get_child_width() > self.min_paned_pos:
            self.view[self.current].reload(artist, album_title)
        else:
            self.view[self.current].blank_view()

        self.current_album_title = album_title
        self.current_artist = artist

    def change_stack(self, widget, value):
        child_name = self.stack.get_visible_child_name()
        if child_name and self.current != child_name:
            self.view[self.current].deactivate()
            if self._get_child_width() > self.min_paned_pos:
                self.view[child_name].activate(self.current_artist, self.current_album_title)
            else:
                self.view[child_name].blank_view()

            self.current = child_name class CreateExternalPluginMenu(GObject.Object):
    '''
    This is the key class called to initialise all supported plugins
    
    :param section_name: `str` unique name of the section holding the menu items
    :param at_position: `int` position within the GtkMenu to create menu - ignored for RB2.99
    :param popup: `Menu` whole popupmenu including sub-menus
    '''

    def __init__(self, section_name, at_position, popup, **kargs):
        super(CreateExternalPluginMenu, self).__init__(**kargs)

        self.menu = popup
        self.section_name = section_name
        self.at_position = at_position

        self._actiongroup = ActionGroup(popup.shell, section_name + '_externalplugins')

        # all supported plugins will be defined in the following array by parsing
        # the plugins XML file for the definition.

        self.supported_plugins = []

        extplugins = rb.find_plugin_file(popup.plugin, 'ui/coverart_external_plugins.xml')
        root = ET.parse(open(extplugins)).getroot()

        base = 'rb3/plugin'

        for elem in root.xpath(base):
            pluginname = elem.attrib['name']

            basemenu = base + "[@name='" + pluginname + "']/menu"

            for menuelem in root.xpath(basemenu):
                ext = ExternalPlugin()
                ext.appendattribute('plugin_name', pluginname)

                label = menuelem.attrib['label']
                if label != "":
                    ext.appendattribute('new_menu_name', label)
                    baseattrib = basemenu + "[@label='" + label + "']/attribute"
                else:
                    baseattrib = basemenu + "/attribute"

                for attribelem in root.xpath(baseattrib):
                    key = attribelem.attrib['name']
                    val = attribelem.text
                    ext.appendattribute(key, val)

                self.supported_plugins.append(ext)

    def create_menu(self, menu_name, for_album=False):
        '''
        method to create the menu items for all supported plugins

        :param menu_name: `str` unique name (GtkMenu) id for the menu to create
        :for_album: `bool` - create a menu applicable for Albums
          by default a menu is assumed to be applicable to a track in an
          EntryView
        '''
        self.menu_name = menu_name

        self._actiongroup.remove_actions()
        self.menu.remove_menu_items(self.menu_name, self.section_name)

        items_added = False

        for plugin in self.supported_plugins:
            new_menu_item = plugin.create_menu_item(self.menu_name, self.section_name,
                                                    self.at_position, self._actiongroup, self.menu, for_album)

            if (not items_added) and new_menu_item:
                items_added = True

        if items_added:
            self.menu.insert_separator(self.menu_name, self.at_position) class EchoNestPlaylist(WebPlaylist):
    def __init__(self, shell, source):
        WebPlaylist.__init__(self, shell, source, "echonest_playlist")

    def search_website(self):
        # unless already cached - directly fetch from echonest similar artist information
        apikey = "N685TONJGZSHBDZMP"
        url = "http://developer.echonest.com/api/v4/playlist/basic?api_key={0}&artist={1}&format=json&results=100&type=artist-radio&limited_interactivity=true"

        artist = self.search_entry.get_string(RB.RhythmDBPropType.ARTIST)
        artist = urllib.parse.quote(artist.encode("utf8"))
        formatted_url = url.format(urllib.parse.quote(apikey),
                                   artist)

        print(formatted_url)
        cachekey = "artist:%s" % artist
        self.info_cache.fetch(cachekey, formatted_url, self.similar_info_cb, None)

    def similar_info_cb(self, data, _):

        if not data:
            print("nothing to do")
            self.display_error_message()
            self._clear_next()
            return

        similar = json.loads(data.decode('utf-8'))

        # loop through the response and find all titles for the artists returned
        self.artist = {}

        if 'songs' not in similar['response']:
            print("No matching data returned from EchoNest")
            self.display_error_message()
            self._clear_next()
            return
        for song in similar['response']['songs']:
            name = RB.search_fold(song['artist_name'])
            if name not in self.artist:
                self.artist[name] = []

            self.artist[name].append(RB.search_fold(song['title']))

        if len(self.artist) == 0:
            print("no artists returned")
            self._clear_next()
            return

        # loop through every track - see if the track contains the artist & title
        # if yes then this is a candidate similar track to remember

        query_model = self.shell.props.library_source.props.base_query_model

        self._load_albums(iter(query_model), albums={}, model=query_model,
                          total=len(query_model), progress=0.) class EntryViewPane(object):
    '''
        encapulates all of the Track Pane objects
    '''

    def __init__(self, shell, plugin, source, entry_view_grid, viewmgr):
        self.gs = GSetting()

        self.entry_view_grid = entry_view_grid
        self.shell = shell
        self.viewmgr = viewmgr
        self.plugin = plugin
        self.source = source

        # setup entry-view objects and widgets
        self.stack = Gtk.Stack()
        self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
        self.stack.set_transition_duration(750)

        # create entry views. Don't allow to reorder until the load is finished
        self.entry_view_compact = CoverArtCompactEntryView(self.shell, self.source)
        self.entry_view_full = CoverArtEntryView(self.shell, self.source)
        self.entry_view = self.entry_view_compact
        self.shell.props.library_source.get_entry_view().set_columns_clickable(
            False)

        self.entry_view_results = ResultsGrid()
        self.entry_view_results.initialise(self.entry_view_grid, source)

        self.stack.add_titled(self.entry_view_results, "notebook_tracks", _("Tracks"))
        self.entry_view_grid.attach(self.stack, 0, 0, 3, 1)

    def setup_source(self):

        colour = self.viewmgr.get_selection_colour()
        self.cover_search_pane = CoverSearchPane(self.plugin, colour)

        self.stack.add_titled(self.cover_search_pane, "notebook_covers", _("Covers"))

        # define entry-view toolbar
        self.stars = ReactiveStar()
        self.stars.set_rating(0)
        self.stars.connect('changed', self.rating_changed_callback)
        self.stars.props.valign = Gtk.Align.CENTER
        self.entry_view_grid.attach(self.stars, 1, 1, 1, 1)
        stack_switcher = Gtk.StackSwitcher()
        stack_switcher.set_stack(self.stack)

        whatsplayingbutton = PressButton()
        whatsplayingbutton.set_image(create_button_image(self.plugin, "whatsplaying.png"))
        whatsplayingbutton.connect('clicked', self.whatsplayingbutton_callback)
        whatsplayingbutton.props.halign = Gtk.Align.START

        leftgrid = Gtk.Grid()
        leftgrid.attach(whatsplayingbutton, 0, 0, 1, 1)
        leftgrid.attach(stack_switcher, 1, 0, 1, 1)
        self.entry_view_grid.attach(leftgrid, 0, 1, 1, 1)

        viewtoggle = PixbufButton()
        viewtoggle.set_image(create_button_image(self.plugin, "entryview.png"))
        self.viewtoggle_id = None

        setting = self.gs.get_setting(self.gs.Path.PLUGIN)
        viewtoggle.set_active(not setting[self.gs.PluginKey.ENTRY_VIEW_MODE])
        self.entry_view_toggled(viewtoggle, True)
        viewtoggle.connect('toggled', self.entry_view_toggled)

        smallwindowbutton = PixbufButton()
        smallwindowbutton.set_image(create_button_image(self.plugin, "view-restore.png"))
        smallwindowbutton.connect('toggled', self.smallwindowbutton_callback)

        self.smallwindowext = ExternalPlugin()
        self.smallwindowext.appendattribute('plugin_name', 'smallwindow')
        self.smallwindowext.appendattribute('action_group_name', 'small window actions')
        self.smallwindowext.appendattribute('action_name', 'SmallWindow')
        self.smallwindowext.appendattribute('action_type', 'app')

        rightgrid = Gtk.Grid()
        rightgrid.props.halign = Gtk.Align.END

        rightgrid.attach(viewtoggle, 1, 0, 1, 1)
        rightgrid.attach(smallwindowbutton, 2, 0, 1, 1)

        self.entry_view_grid.attach_next_to(rightgrid, self.stars, Gtk.PositionType.RIGHT, 1, 1)
        self.stack.set_visible_child(self.entry_view_results)
        self.stack.connect('notify::visible-child-name', self.notebook_switch_page_callback)

        self.entry_view_grid.show_all()
        smallwindowbutton.set_visible(self.smallwindowext.is_activated())

    def whatsplayingbutton_callback(self, widget):
        self.entry_view_results.emit('whats-playing')

    def smallwindowbutton_callback(self, widget):
        if widget.get_active():
            self.smallwindowext.activate(self.shell)
            widget.emit('clicked')

    def entry_view_toggled(self, widget, initialised=False):
        print("DEBUG - entry_view_toggled")
        if widget.get_active():
            next_view = self.entry_view_full
            show_coverart = False
            if self.viewtoggle_id:
                self.shell.props.window.disconnect(self.viewtoggle_id)
                self.viewtoggle_id = None
        else:
            next_view = self.entry_view_compact
            show_coverart = True
            self.viewtoggle_id = self.shell.props.window.connect('check_resize', self.entry_view_results.window_resize)

        setting = self.gs.get_setting(self.gs.Path.PLUGIN)
        setting[self.gs.PluginKey.ENTRY_VIEW_MODE] = not widget.get_active()

        self.entry_view_results.change_view(next_view, show_coverart)
        self.entry_view = next_view
        if not initialised:
            self.source.update_with_selection()

    def notebook_switch_page_callback(self, *args):
        '''
        Callback called when the notebook page gets switched. It initiates
        the cover search when the cover search pane's page is selected.
        '''
        print("CoverArtBrowser DEBUG - notebook_switch_page_callback")

        if self.stack.get_visible_child_name() == 'notebook_covers':
            self.viewmgr.current_view.switch_to_coverpane(self.cover_search_pane)
        else:
            entries = self.entry_view.get_selected_entries()
            if entries and len(entries) > 0:
                self.entry_view_results.emit('update-cover', self.source, entries[0])
            else:
                selected = self.viewmgr.current_view.get_selected_objects()
                tracks = selected[0].get_tracks()
                self.entry_view_results.emit('update-cover', self.source, tracks[0].entry)

        print("CoverArtBrowser DEBUG - end notebook_switch_page_callback")

    def rating_changed_callback(self, widget):
        '''
        Callback called when the Rating stars is changed
        '''
        print("CoverArtBrowser DEBUG - rating_changed_callback")

        rating = widget.get_rating()

        for album in self.viewmgr.current_view.get_selected_objects():
            album.rating = rating

        print("CoverArtBrowser DEBUG - end rating_changed_callback")

    def get_entry_view(self):
        return self.entry_view

    def update_cover(self, album_artist, manager):
        if not self.stack.get_visible_child_name() == "notebook_covers":
            return

        self.cover_search_pane.clear()
        self.cover_search(album_artist, manager)

    def cover_search(self, album_artist, manager):
        self.cover_search_pane.do_search(album_artist,
                                         manager.cover_man.update_cover)

    def update_selection(self, last_selected_album, click_count):
        '''
        Update the source view when an item gets selected.
        '''
        print("DEBUG - update_with_selection")
        selected = self.viewmgr.current_view.get_selected_objects()

        # clear the entry view
        self.entry_view.clear()

        cover_search_pane_visible = self.stack.get_visible_child_name() == "notebook_covers"

        if not selected:
            # clean cover tab if selected
            if cover_search_pane_visible:
                self.cover_search_pane.clear()

            self.entry_view_results.emit('update-cover', self.source, None)
            return last_selected_album, click_count
        elif len(selected) == 1:
            self.stars.set_rating(selected[0].rating)

            if selected[0] is not last_selected_album:
                # when the selection changes we've to take into account two
                # things
                if not click_count:
                    # we may be using the arrows, so if there is no mouse
                    # involved, we should change the last selected
                    last_selected_album = selected[0]
                else:
                    # we may've doing a fast change after a valid second click,
                    # so it shouldn't be considered a double click
                    click_count -= 1
        else:
            self.stars.set_rating(0)

        if len(selected) == 1:
            self.source.artist_info.emit('selected',
                                         selected[0].artist,
                                         selected[0].name)

        self.entry_view.set_sorting_order('track-number', Gtk.SortType.ASCENDING)

        player = self.shell.props.shell_player

        # add album tracks automatically to the playlist if current playsource is not ours
        # or if it is ours, then add automatically when nothing is playing
        autoplaylist = False

        if player.get_playing_source() != self.source.playlist_source and player.get_playing_source() != self.source:
            autoplaylist = True
        elif not player.get_playing_entry():
            autoplaylist = True

        if autoplaylist:
            # cleardown current playlist
            for row in self.source.source_query_model:
                self.source.source_query_model.remove_entry(row[0])

        for album in selected:
            # add the album to the entry_view
            self.entry_view.add_album(album, autoplaylist)

        if len(selected) > 0:
            if autoplaylist:
                self.source.props.query_model = self.source.source_query_model
                player.set_playing_source(self.source)

            def cover_update(*args):
                print("emitting")
                self.entry_view_results.emit('update-cover',
                                             self.source,
                                             selected[0].get_tracks()[0].entry)

            # add a short delay to give the entry-pane time to expand etc.
            Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250, cover_update, None)

        # update the cover search pane with the first selected album
        if cover_search_pane_visible:
            self.cover_search_pane.do_search(selected[0],
                                             self.source.album_manager.cover_man.update_cover)

        return last_selected_album, click_count class CoverArtPlaySource(RB.Source):
    '''
    Source utilized by the plugin to show all it's ui.
    '''

    def __init__(self, **kwargs):
        '''
        Initializes the source.
        '''
        super(CoverArtPlaySource, self).__init__()
        self.external_plugins = None
        self.hasActivated = False

    def initialise(self, plugin, shell, source):
        self.plugin = plugin
        self.shell = shell
        self.source = source
        player = self.shell.props.shell_player
        player.set_playing_source(self)
        player.set_selected_source(self)

    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 shown.
        '''
        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 connect_library_signals(self):
        pass

    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 necessary signals for it
        correct behavior.
        '''
        print('do_impl_activate')
        self.hasActivated = True

        self.entryview = CoverArtPlayEntryView(self.shell, self.source)
        self.entryview.props.hexpand = True
        self.entryview.props.vexpand = True
        grid = Gtk.Grid()
        grid.attach(self.entryview, 0, 1, 3, 1)

        self.entryview.set_model(self.source.source_query_model)

        # enable sorting on the entryview
        # entryview.set_columns_clickable(True)
        self.shell.props.library_source.get_entry_view().set_columns_clickable(
            True)

        cl = CoverLocale()
        cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
        location = rb.find_plugin_file(self.plugin, 'ui/playsource-toolbar.ui')
        ui = Gtk.Builder()
        ui.set_translation_domain(cl.Locale.RB)
        ui.add_from_file(location)
        toolbar_menu = ui.get_object('playsource-toolbar')
        app = self.shell.props.application
        app.link_shared_menus(toolbar_menu)
        self.toolbar = RB.ButtonBar.new(toolbar_menu, toolbar_menu)
        self.toolbar.props.hexpand_set = False
        grid.attach(self.toolbar, 1, 0, 1, 1)

        self.coverartbutton = PressButton()
        self.coverartbutton.props.halign = Gtk.Align.START
        self.coverartbutton.set_image(create_button_image(self.plugin, "covermgr_std.png"))
        self.coverartbutton.connect('clicked', self.coverartbutton_callback)
        grid.attach(self.coverartbutton, 0, 0, 1, 1)

        grid.show_all()
        self.pack_start(grid, True, True, 0)

        appshell = ApplicationShell(self.shell)
        action_group = ActionGroup(self.shell, 'PlaySourceActions')
        action_group.add_action(func=self.clear_playsource,
                                action_name='playsource-clear', action_state=ActionGroup.STANDARD,
                                action_type='app')
        action_group.add_action(func=self.shuffle_playsource,
                                action_name='playsource-shuffle', action_state=ActionGroup.STANDARD,
                                action_type='app')
        appshell.insert_action_group(action_group)

        # if the alternative-toolbar is loaded then lets connect to the toolbar-visibility signal
        # to control our sources toolbar visibility

        if hasattr(self.plugin.shell, 'alternative_toolbar'):
            self.plugin.shell.alternative_toolbar.connect('toolbar-visibility', self._visibility)

    def _visibility(self, altplugin, value):
        if value:
            self.toolbar.show()
            self.coverartbutton.show()
        else:
            self.toolbar.hide()
            self.coverartbutton.hide()


    def coverartbutton_callback(self, *args):
        GLib.idle_add( self.source.shell.props.display_page_tree.select, self.source)


    def clear_playsource(self, *args):
        for row in self.entryview.props.model:
            self.entryview.props.model.remove_entry(row[0])

    def shuffle_playsource(self, *args):
        self.entryview.props.model.shuffle_entries()
import random
import unicodedata
import re
import rb
import coverart_rb3compat as rb3compat
from collections import OrderedDict
from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Gio
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import RB
from coverart_album import AlbumManager
from coverart_browser_prefs import GSetting
from coverart_browser_prefs import CoverLocale
from coverart_browser_prefs import Preferences
from coverart_widgets import PanedCollapsible
from coverart_controllers import AlbumQuickSearchController
from coverart_controllers import ViewController
from coverart_export import CoverArtExport
from coverart_rb3compat import Menu
from coverart_rb3compat import ActionGroup
from coverart_covericonview import CoverIconView
from coverart_coverflowview import CoverFlowView
from coverart_artistview import ArtistView
from coverart_listview import ListView
from coverart_queueview import QueueView
from coverart_toolbar import ToolbarManager
from coverart_artistinfo import ArtistInfoPane
from coverart_external_plugins import CreateExternalPluginMenu
from coverart_playlists import EchoNestPlaylist
from coverart_entryview import EntryViewPane
from coverart_play_source import CoverArtPlaySource
                    from gi.repository import Peas
            from coverart_covericonview import CoverIconView
            from coverart_coverflowview import CoverFlowView
            from coverart_artistview import ArtistView
            from coverart_listview import ListView
            from coverart_queueview import QueueView
            from coverart_browser_prefs import webkit_support
        self.request_cancel_button.set_sensitive(False)
        self._cover_search_manager.cover_man.cancel_cover_request()

        print("CoverArtBrowser DEBUG - end cancel_request_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
        '''
        print('show_hide_pane')
        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

        print('show_hide_pane end')

    def update_with_selection(self):
        self.last_selected_album, self.click_count = \
            self.entryviewpane.update_selection(self.last_selected_album,
                                                self.click_count)

        self.statusbar.emit('display-status', self.viewmgr.current_view)

    def propertiesbutton_callback(self, choice):
        print("properties chosen: %s" % 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 == 'follow':
            self.follow_song = not self.follow_song
        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:

                    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 Statusbar(GObject.Object):
    # signals
    __gsignals__ = {
        'display-status': (GObject.SIGNAL_RUN_LAST, None, (object,))
    }

    custom_statusbar_enabled = GObject.property(type=bool, default=False)

    def __init__(self, source):
        super(Statusbar, self).__init__()

        self.status = ''

        self._source_statusbar = SourceStatusBar(source)
        self._custom_statusbar = CustomStatusBar(source.status_label)
        self.current_statusbar = self._source_statusbar

        self._connect_signals(source)
        self._connect_properties()

    def _connect_properties(self):

        gs = GSetting()