Beispiel #1
0
 def __init__(self, parent=None):
     super(PluginsOptionsPage, self).__init__(parent)
     self.ui = Ui_PluginsOptionsPage()
     self.ui.setupUi(self)
     self.items = {}
     self.ui.plugins.itemSelectionChanged.connect(self.change_details)
     self.ui.plugins.mimeTypes = self.mimeTypes
     self.ui.plugins.dropEvent = self.dropEvent
     self.ui.plugins.dragEnterEvent = self.dragEnterEvent
     if sys.platform == "win32":
         self.loader = "file:///%s"
     else:
         self.loader = "file://%s"
     self.ui.install_plugin.clicked.connect(self.open_plugins)
     self.ui.folder_open.clicked.connect(self.open_plugin_dir)
     self.ui.reload_list_of_plugins.clicked.connect(
         self.reload_list_of_plugins)
     self.tagger.pluginmanager.plugin_installed.connect(
         self.plugin_installed)
     self.tagger.pluginmanager.plugin_updated.connect(self.plugin_updated)
     self.ui.plugins.header().setStretchLastSection(False)
     self.ui.plugins.header().setSectionResizeMode(
         0, QtWidgets.QHeaderView.Stretch)
     self.ui.plugins.header().setSectionResizeMode(
         1, QtWidgets.QHeaderView.Stretch)
     self.ui.plugins.header().resizeSection(2, 100)
     self.ui.plugins.setSortingEnabled(True)
Beispiel #2
0
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_PluginsOptionsPage()
        self.ui.setupUi(self)
        plugins = self.ui.plugins

        # fix for PICARD-1226, QT bug (https://bugreports.qt.io/browse/QTBUG-22572) workaround
        plugins.setStyleSheet('')

        plugins.itemSelectionChanged.connect(self.change_details)
        plugins.mimeTypes = self.mimeTypes
        plugins.dropEvent = self.dropEvent
        plugins.dragEnterEvent = self.dragEnterEvent

        self.ui.install_plugin.clicked.connect(self.open_plugins)
        self.ui.folder_open.clicked.connect(self.open_plugin_dir)
        self.ui.reload_list_of_plugins.clicked.connect(
            self.reload_list_of_plugins)

        self.manager = self.tagger.pluginmanager
        self.manager.plugin_installed.connect(self.plugin_installed)
        self.manager.plugin_updated.connect(self.plugin_updated)
        self.manager.plugin_removed.connect(self.plugin_removed)
        self.manager.plugin_errored.connect(self.plugin_loading_error)

        self._preserve = {}
        self._preserve_selected = None
Beispiel #3
0
 def __init__(self, parent=None):
     super().__init__(parent)
     self.ui = Ui_PluginsOptionsPage()
     self.ui.setupUi(self)
     #fix for PICARD-1226, QT bug (https://bugreports.qt.io/browse/QTBUG-22572) workaround
     self.ui.plugins.setStyleSheet('')
     self.items = {}
     self.ui.plugins.itemSelectionChanged.connect(self.change_details)
     self.ui.plugins.mimeTypes = self.mimeTypes
     self.ui.plugins.dropEvent = self.dropEvent
     self.ui.plugins.dragEnterEvent = self.dragEnterEvent
     if sys.platform == "win32":
         self.loader = "file:///%s"
     else:
         self.loader = "file://%s"
     self.ui.install_plugin.clicked.connect(self.open_plugins)
     self.ui.folder_open.clicked.connect(self.open_plugin_dir)
     self.ui.reload_list_of_plugins.clicked.connect(
         self.reload_list_of_plugins)
     self.tagger.pluginmanager.plugin_installed.connect(
         self.plugin_installed)
     self.tagger.pluginmanager.plugin_updated.connect(self.plugin_updated)
     self.ui.plugins.header().setStretchLastSection(False)
     self.ui.plugins.header().setSectionResizeMode(
         COLUMN_NAME, QtWidgets.QHeaderView.Stretch)
     self.ui.plugins.header().setSectionResizeMode(
         COLUMN_VERSION, QtWidgets.QHeaderView.Stretch)
     self.ui.plugins.header().resizeSection(COLUMN_ACTION, 100)
     self.ui.plugins.setSortingEnabled(True)
Beispiel #4
0
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_PluginsOptionsPage()
        self.ui.setupUi(self)
        plugins = self.ui.plugins

        # fix for PICARD-1226, QT bug (https://bugreports.qt.io/browse/QTBUG-22572) workaround
        plugins.setStyleSheet('')

        plugins.itemSelectionChanged.connect(self.change_details)
        plugins.mimeTypes = self.mimeTypes
        plugins.dropEvent = self.dropEvent
        plugins.dragEnterEvent = self.dragEnterEvent

        self.ui.install_plugin.clicked.connect(self.open_plugins)
        self.ui.folder_open.clicked.connect(self.open_plugin_dir)
        self.ui.reload_list_of_plugins.clicked.connect(self.reload_list_of_plugins)

        self.manager = self.tagger.pluginmanager
        self.manager.plugin_installed.connect(self.plugin_installed)
        self.manager.plugin_updated.connect(self.plugin_updated)
        self.manager.plugin_removed.connect(self.plugin_removed)
        self.manager.plugin_errored.connect(self.plugin_loading_error)

        self._preserve = {}
        self._preserve_selected = None
Beispiel #5
0
 def __init__(self, parent=None):
     super(PluginsOptionsPage, self).__init__(parent)
     self.ui = Ui_PluginsOptionsPage()
     self.ui.setupUi(self)
     self.items = {}
     self.connect(self.ui.plugins, QtCore.SIGNAL("itemSelectionChanged()"), self.change_details)
     self.ui.plugins.mimeTypes = self.mimeTypes
     self.ui.plugins.dropEvent = self.dropEvent
     self.ui.plugins.dragEnterEvent = self.dragEnterEvent
     if sys.platform == "win32":
         self.loader="file:///%s"
     else:
         self.loader="file://%s"
     self.connect(self.ui.install_plugin, QtCore.SIGNAL("clicked()"), self.open_plugins)
     self.connect(self.ui.folder_open, QtCore.SIGNAL("clicked()"), self.open_plugin_dir)
     self.connect(self.ui.plugin_download, QtCore.SIGNAL("clicked()"), self.open_plugin_site)
     self.connect(self.tagger.pluginmanager, QtCore.SIGNAL("plugin_installed"), self.plugin_installed)
Beispiel #6
0
 def __init__(self, parent=None):
     super(PluginsOptionsPage, self).__init__(parent)
     self.ui = Ui_PluginsOptionsPage()
     self.ui.setupUi(self)
     self.items = {}
     self.ui.plugins.itemSelectionChanged.connect(self.change_details)
     self.ui.plugins.mimeTypes = self.mimeTypes
     self.ui.plugins.dropEvent = self.dropEvent
     self.ui.plugins.dragEnterEvent = self.dragEnterEvent
     if sys.platform == "win32":
         self.loader = "file:///%s"
     else:
         self.loader = "file://%s"
     self.ui.install_plugin.clicked.connect(self.open_plugins)
     self.ui.folder_open.clicked.connect(self.open_plugin_dir)
     self.ui.plugin_download.clicked.connect(self.open_plugin_site)
     self.tagger.pluginmanager.plugin_installed.connect(self.plugin_installed)
Beispiel #7
0
 def __init__(self, parent=None):
     super(PluginsOptionsPage, self).__init__(parent)
     self.ui = Ui_PluginsOptionsPage()
     self.ui.setupUi(self)
     self.items = {}
     self.ui.plugins.itemSelectionChanged.connect(self.change_details)
     self.ui.plugins.mimeTypes = self.mimeTypes
     self.ui.plugins.dropEvent = self.dropEvent
     self.ui.plugins.dragEnterEvent = self.dragEnterEvent
     if sys.platform == "win32":
         self.loader = "file:///%s"
     else:
         self.loader = "file://%s"
     self.ui.install_plugin.clicked.connect(self.open_plugins)
     self.ui.folder_open.clicked.connect(self.open_plugin_dir)
     self.ui.reload_list_of_plugins.clicked.connect(self.reload_list_of_plugins)
     self.tagger.pluginmanager.plugin_installed.connect(self.plugin_installed)
     self.tagger.pluginmanager.plugin_updated.connect(self.plugin_updated)
     self.ui.plugins.header().setStretchLastSection(False)
     self.ui.plugins.header().setResizeMode(0, QtGui.QHeaderView.Stretch)
     self.ui.plugins.header().setResizeMode(1, QtGui.QHeaderView.Stretch)
     self.ui.plugins.header().resizeSection(2, 100)
     self.ui.plugins.setSortingEnabled(True)
Beispiel #8
0
class PluginsOptionsPage(OptionsPage):

    NAME = "plugins"
    TITLE = N_("Plugins")
    PARENT = None
    SORT_ORDER = 70
    ACTIVE = True

    options = [
        config.ListOption("setting", "enabled_plugins", []),
        config.Option("persist", "plugins_list_state", QtCore.QByteArray()),
        config.Option("persist", "plugins_list_sort_section", 0),
        config.Option("persist", "plugins_list_sort_order",
                      QtCore.Qt.AscendingOrder),
    ]

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_PluginsOptionsPage()
        self.ui.setupUi(self)
        plugins = self.ui.plugins

        # fix for PICARD-1226, QT bug (https://bugreports.qt.io/browse/QTBUG-22572) workaround
        plugins.setStyleSheet('')

        plugins.itemSelectionChanged.connect(self.change_details)
        plugins.mimeTypes = self.mimeTypes
        plugins.dropEvent = self.dropEvent
        plugins.dragEnterEvent = self.dragEnterEvent

        self.ui.install_plugin.clicked.connect(self.open_plugins)
        self.ui.folder_open.clicked.connect(self.open_plugin_dir)
        self.ui.reload_list_of_plugins.clicked.connect(self.reload_list_of_plugins)

        self.manager = self.tagger.pluginmanager
        self.manager.plugin_installed.connect(self.plugin_installed)
        self.manager.plugin_updated.connect(self.plugin_updated)
        self.manager.plugin_removed.connect(self.plugin_removed)
        self.manager.plugin_errored.connect(self.plugin_loading_error)

        self._preserve = {}
        self._preserve_selected = None

    def items(self):
        iterator = QTreeWidgetItemIterator(self.ui.plugins, QTreeWidgetItemIterator.All)
        while iterator.value():
            item = iterator.value()
            iterator += 1
            yield item

    def find_item_by_plugin_name(self, plugin_name):
        for item in self.items():
            if plugin_name == item.plugin.module_name:
                return item
        return None

    def selected_item(self):
        try:
            return self.ui.plugins.selectedItems()[COLUMN_NAME]
        except IndexError:
            return None

    def save_state(self):
        header = self.ui.plugins.header()
        config.persist["plugins_list_state"] = header.saveState()
        config.persist["plugins_list_sort_section"] = header.sortIndicatorSection()
        config.persist["plugins_list_sort_order"] = header.sortIndicatorOrder()

    def set_current_item(self, item, scroll=False):
        if scroll:
            self.ui.plugins.scrollToItem(item)
        self.ui.plugins.setCurrentItem(item)
        self.refresh_details(item)

    def restore_state(self):
        header = self.ui.plugins.header()
        header.restoreState(config.persist["plugins_list_state"])
        idx = config.persist["plugins_list_sort_section"]
        order = config.persist["plugins_list_sort_order"]
        header.setSortIndicator(idx, order)
        self.ui.plugins.sortByColumn(idx, order)

    @staticmethod
    def is_plugin_enabled(plugin):
        return bool(plugin.module_name in config.setting["enabled_plugins"])

    def available_plugins_name_version(self):
        return dict([(p.module_name, p.version) for p in self.manager.available_plugins])

    def installable_plugins(self):
        if self.manager.available_plugins is not None:
            installed_plugins = [plugin.module_name for plugin in
                                 self.installed_plugins()]
            for plugin in sorted(self.manager.available_plugins,
                                 key=attrgetter('name')):
                if plugin.module_name not in installed_plugins:
                    yield plugin

    def installed_plugins(self):
        return sorted(self.manager.plugins, key=attrgetter('name'))

    def enabled_plugins(self):
        return [item.plugin.module_name for item in self.items() if item.is_enabled]

    def _populate(self):
        self._user_interaction(False)
        if self.manager.available_plugins is None:
            available_plugins = {}
            self.manager.query_available_plugins(self._reload)
        else:
            available_plugins = self.available_plugins_name_version()

        self.ui.details.setText("")

        self.ui.plugins.setSortingEnabled(False)
        for plugin in self.installed_plugins():
            new_version = None
            if plugin.module_name in available_plugins:
                latest = available_plugins[plugin.module_name]
                if latest.split('.') > plugin.version.split('.'):
                    new_version = latest
            self.update_plugin_item(None, plugin,
                                    enabled=self.is_plugin_enabled(plugin),
                                    new_version=new_version,
                                    is_installed=True
                                    )

        for plugin in self.installable_plugins():
            self.update_plugin_item(None, plugin, enabled=False,
                                    is_installed=False)

        self.ui.plugins.setSortingEnabled(True)
        self._user_interaction(True)
        header = self.ui.plugins.header()
        header.setStretchLastSection(False)
        header.setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
        header.setSectionResizeMode(COLUMN_NAME, QtWidgets.QHeaderView.Stretch)
        header.setSectionResizeMode(COLUMN_VERSION, QtWidgets.QHeaderView.ResizeToContents)
        header.setSectionResizeMode(COLUMN_ACTIONS, QtWidgets.QHeaderView.ResizeToContents)

    def _remove_all(self):
        for item in self.items():
            idx = self.ui.plugins.indexOfTopLevelItem(item)
            self.ui.plugins.takeTopLevelItem(idx)

    def restore_defaults(self):
        self._user_interaction(False)
        self._remove_all()
        super().restore_defaults()
        self.set_current_item(self.ui.plugins.topLevelItem(0), scroll=True)

    def load(self):
        self._populate()
        self.restore_state()

    def _preserve_plugins_states(self):
        self._preserve = {item.plugin.module_name: item.save_state() for item in self.items()}
        item = self.selected_item()
        if item:
            self._preserve_selected = item.plugin.module_name
        else:
            self._preserve_selected = None

    def _restore_plugins_states(self):
        for item in self.items():
            plugin = item.plugin
            if plugin.module_name in self._preserve:
                item.restore_state(self._preserve[plugin.module_name])
                if self._preserve_selected == plugin.module_name:
                    self.set_current_item(item, scroll=True)

    def _reload(self):
        self._remove_all()
        self._populate()
        self._restore_plugins_states()

    def _user_interaction(self, enabled):
        self.ui.plugins.blockSignals(not enabled)
        self.ui.plugins_container.setEnabled(enabled)

    def reload_list_of_plugins(self):
        self.ui.details.setText(_("Reloading list of available plugins..."))
        self._user_interaction(False)
        self._preserve_plugins_states()
        self.manager.query_available_plugins(callback=self._reload)

    def plugin_loading_error(self, plugin_name, error):
        QtWidgets.QMessageBox.critical(
            self,
            _("Plugin '%s'") % plugin_name,
            _("An error occured while loading the plugin '%s':\n\n%s") % (plugin_name, error)
        )

    def plugin_installed(self, plugin):
        log.debug("Plugin %r installed", plugin.name)
        if not plugin.compatible:
            QtWidgets.QMessageBox.warning(
                self,
                _("Plugin '%s'") % plugin.name,
                _("The plugin '%s' is not compatible with this version of Picard.") % plugin.name
            )
            return
        item = self.find_item_by_plugin_name(plugin.module_name)
        if item:
            self.update_plugin_item(item, plugin, make_current=True,
                                    enabled=True, is_installed=True)
        else:
            self._reload()
            item = self.find_item_by_plugin_name(plugin.module_name)
            if item:
                self.set_current_item(item, scroll=True)

    def plugin_updated(self, plugin_name):
        log.debug("Plugin %r updated", plugin_name)
        item = self.find_item_by_plugin_name(plugin_name)
        if item:
            plugin = item.plugin
            QtWidgets.QMessageBox.information(
                self,
                _("Plugin '%s'") % plugin_name,
                _("The plugin '%s' will be upgraded to version %s on next run of Picard.")
                % (plugin.name, item.new_version)
            )

            item.upgrade_to_version = item.new_version
            self.update_plugin_item(item, plugin, make_current=True)

    def plugin_removed(self, plugin_name):
        log.debug("Plugin %r removed", plugin_name)
        item = self.find_item_by_plugin_name(plugin_name)
        if item:
            if self.manager.is_available(plugin_name):
                self.update_plugin_item(item, None, make_current=True,
                                        is_installed=False)
            else:  # Remove local plugin
                self.ui.plugins.invisibleRootItem().removeChild(item)

    def uninstall_plugin(self, item):
        plugin = item.plugin
        buttonReply = QtWidgets.QMessageBox.question(
            self,
            _("Uninstall plugin '%s'?") % plugin.name,
            _("Do you really want to uninstall the plugin '%s' ?") % plugin.name,
            QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
            QtWidgets.QMessageBox.No
        )
        if buttonReply == QtWidgets.QMessageBox.Yes:
            self.manager.remove_plugin(plugin.module_name, with_update=True)

    def update_plugin_item(self, item, plugin,
                           make_current=False,
                           enabled=None,
                           new_version=None,
                           is_installed=None
                           ):
        if item is None:
            item = PluginTreeWidgetItem(self.ui.plugins)
        if plugin is not None:
            item.setData(COLUMN_NAME, QtCore.Qt.UserRole, plugin)
        else:
            plugin = item.plugin
        if new_version is not None:
            item.new_version = new_version
        if is_installed is not None:
            item.is_installed = is_installed
        if enabled is None:
            enabled = item.is_enabled

        def update_text():
            if item.new_version is not None:
                version = "%s → %s" % (plugin.version, item.new_version)
            else:
                version = plugin.version

            if item.installed_font is None:
                item.installed_font = item.font(COLUMN_NAME)
            if item.enabled_font is None:
                item.enabled_font = QtGui.QFont(item.installed_font)
                item.enabled_font.setBold(True)
            if item.available_font is None:
                item.available_font = QtGui.QFont(item.installed_font)

            if item.is_enabled:
                item.setFont(COLUMN_NAME, item.enabled_font)
            else:
                if item.is_installed:
                    item.setFont(COLUMN_NAME, item.installed_font)
                else:
                    item.setFont(COLUMN_NAME, item.available_font)

            item.setText(COLUMN_NAME, plugin.name)
            item.setText(COLUMN_VERSION, version)

        def toggle_enable():
            item.enable(not item.is_enabled, greyout=not item.is_installed)
            log.debug("Plugin %r enabled: %r", item.plugin.name, item.is_enabled)
            update_text()

        reconnect(item.buttons['enable'].clicked, toggle_enable)

        install_enabled = not item.is_installed or bool(item.new_version)
        if item.upgrade_to_version:
            if item.upgrade_to_version != item.new_version:
                # case when a new version is known after a plugin was marked for update
                install_enabled = True
            else:
                install_enabled = False

        if install_enabled:
            if item.new_version is not None:
                def download_and_update():
                    self.download_plugin(item, update=True)

                reconnect(item.buttons['update'].clicked, download_and_update)
                item.buttons['install'].mode('hide')
                item.buttons['update'].mode('show')
            else:
                def download_and_install():
                    self.download_plugin(item)

                reconnect(item.buttons['install'].clicked, download_and_install)
                item.buttons['install'].mode('show')
                item.buttons['update'].mode('hide')

        if item.is_installed:
            item.buttons['install'].mode('hide')
            item.buttons['uninstall'].mode('show')
            item.enable(enabled, greyout=False)

            def uninstall_processor():
                self.uninstall_plugin(item)

            reconnect(item.buttons['uninstall'].clicked, uninstall_processor)
        else:
            item.buttons['uninstall'].mode('hide')
            item.enable(False)
            item.buttons['enable'].mode('hide')

        update_text()

        if make_current:
            self.set_current_item(item)

        actions_sort_score = 2
        if item.is_installed:
            if item.is_enabled:
                actions_sort_score = 0
            else:
                actions_sort_score = 1

        item.setSortData(COLUMN_ACTIONS, actions_sort_score)
        item.setSortData(COLUMN_NAME, plugin.name.lower())

        def v2int(elem):
            try:
                return int(elem)
            except ValueError:
                return 0
        item.setSortData(COLUMN_VERSION, tuple(v2int(e) for e in plugin.version.split('.')))

        return item

    def save(self):
        config.setting["enabled_plugins"] = self.enabled_plugins()
        self.save_state()

    def refresh_details(self, item):
        plugin = item.plugin
        text = []
        if item.new_version is not None:
            if item.upgrade_to_version:
                label = _("Restart Picard to upgrade to new version")
            else:
                label = _("New version available")
            text.append("<b>" + label + ": " + item.new_version + "</b>")
        if plugin.description:
            text.append(plugin.description + "<hr width='90%'/>")
        if plugin.name:
            text.append("<b>" + _("Name") + "</b>: " + plugin.name)
        if plugin.author:
            text.append("<b>" + _("Authors") + "</b>: " + plugin.author)
        if plugin.license:
            text.append("<b>" + _("License") + "</b>: " + plugin.license)
        text.append("<b>" + _("Files") + "</b>: " + plugin.files_list)
        self.ui.details.setText("<p>%s</p>" % "<br/>\n".join(text))

    def change_details(self):
        item = self.selected_item()
        if item:
            self.refresh_details(item)

    def open_plugins(self):
        files, _filter = QtWidgets.QFileDialog.getOpenFileNames(
            self,
            "",
            QtCore.QDir.homePath(),
            "Picard plugin (*.py *.pyc *.zip)"
        )
        if files:
            for path in files:
                self.manager.install_plugin(path)

    def download_plugin(self, item, update=False):
        plugin = item.plugin

        self.tagger.webservice.get(
            PLUGINS_API['host'],
            PLUGINS_API['port'],
            PLUGINS_API['endpoint']['download'],
            partial(self.download_handler, update, plugin=plugin),
            parse_response_type=None,
            priority=True,
            important=True,
            queryargs={"id": plugin.module_name}
        )

    def download_handler(self, update, response, reply, error, plugin):
        if error:
            msgbox = QtWidgets.QMessageBox(self)
            msgbox.setText(_("The plugin '%s' could not be downloaded.") % plugin.module_name)
            msgbox.setInformativeText(_("Please try again later."))
            msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok)
            msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
            msgbox.exec_()
            log.error("Error occurred while trying to download the plugin: '%s'" % plugin.module_name)
            return

        self.manager.install_plugin(
            None,
            update=update,
            plugin_name=plugin.module_name,
            plugin_data=response,
        )

    @staticmethod
    def open_plugin_dir():
        if sys.platform == 'win32':
            url = 'file:///' + USER_PLUGIN_DIR
        else:
            url = 'file://' + USER_PLUGIN_DIR
        QtGui.QDesktopServices.openUrl(QtCore.QUrl(url, QtCore.QUrl.TolerantMode))

    def mimeTypes(self):
        return ["text/uri-list"]

    def dragEnterEvent(self, event):
        event.setDropAction(QtCore.Qt.CopyAction)
        event.accept()

    def dropEvent(self, event):
        for path in [os.path.normpath(u.toLocalFile()) for u in event.mimeData().urls()]:
            self.manager.install_plugin(path)
Beispiel #9
0
class PluginsOptionsPage(OptionsPage):

    NAME = "plugins"
    TITLE = N_("Plugins")
    PARENT = None
    SORT_ORDER = 70
    ACTIVE = True

    options = [
        config.ListOption("setting", "enabled_plugins", []),
        config.Option("persist", "plugins_list_state", QtCore.QByteArray()),
        config.Option("persist", "plugins_list_sort_section", 0),
        config.Option("persist", "plugins_list_sort_order",
                      QtCore.Qt.AscendingOrder),
    ]

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_PluginsOptionsPage()
        self.ui.setupUi(self)
        plugins = self.ui.plugins

        # fix for PICARD-1226, QT bug (https://bugreports.qt.io/browse/QTBUG-22572) workaround
        plugins.setStyleSheet('')

        plugins.itemSelectionChanged.connect(self.change_details)
        plugins.mimeTypes = self.mimeTypes
        plugins.dropEvent = self.dropEvent
        plugins.dragEnterEvent = self.dragEnterEvent

        self.ui.install_plugin.clicked.connect(self.open_plugins)
        self.ui.folder_open.clicked.connect(self.open_plugin_dir)
        self.ui.reload_list_of_plugins.clicked.connect(
            self.reload_list_of_plugins)

        self.manager = self.tagger.pluginmanager
        self.manager.plugin_installed.connect(self.plugin_installed)
        self.manager.plugin_updated.connect(self.plugin_updated)
        self.manager.plugin_removed.connect(self.plugin_removed)
        self.manager.plugin_errored.connect(self.plugin_loading_error)

        self._preserve = {}
        self._preserve_selected = None

    def items(self):
        iterator = QTreeWidgetItemIterator(self.ui.plugins,
                                           QTreeWidgetItemIterator.All)
        while iterator.value():
            item = iterator.value()
            iterator += 1
            yield item

    def find_item_by_plugin_name(self, plugin_name):
        for item in self.items():
            if plugin_name == item.plugin.module_name:
                return item
        return None

    def selected_item(self):
        try:
            return self.ui.plugins.selectedItems()[COLUMN_NAME]
        except IndexError:
            return None

    def save_state(self):
        header = self.ui.plugins.header()
        config.persist["plugins_list_state"] = header.saveState()
        config.persist[
            "plugins_list_sort_section"] = header.sortIndicatorSection()
        config.persist["plugins_list_sort_order"] = header.sortIndicatorOrder()

    def set_current_item(self, item, scroll=False):
        if scroll:
            self.ui.plugins.scrollToItem(item)
        self.ui.plugins.setCurrentItem(item)
        self.refresh_details(item)

    def restore_state(self):
        header = self.ui.plugins.header()
        header.restoreState(config.persist["plugins_list_state"])
        idx = config.persist["plugins_list_sort_section"]
        order = config.persist["plugins_list_sort_order"]
        header.setSortIndicator(idx, order)
        self.ui.plugins.sortByColumn(idx, order)

    @staticmethod
    def is_plugin_enabled(plugin):
        return bool(plugin.module_name in config.setting["enabled_plugins"])

    def available_plugins_name_version(self):
        return dict([(p.module_name, p.version)
                     for p in self.manager.available_plugins])

    def installable_plugins(self):
        if self.manager.available_plugins is not None:
            installed_plugins = [
                plugin.module_name for plugin in self.installed_plugins()
            ]
            for plugin in sorted(self.manager.available_plugins,
                                 key=attrgetter('name')):
                if plugin.module_name not in installed_plugins:
                    yield plugin

    def installed_plugins(self):
        return sorted(self.manager.plugins, key=attrgetter('name'))

    def enabled_plugins(self):
        return [
            item.plugin.module_name for item in self.items() if item.is_enabled
        ]

    def _populate(self):
        self._user_interaction(False)
        if self.manager.available_plugins is None:
            available_plugins = {}
            self.manager.query_available_plugins(self._reload)
        else:
            available_plugins = self.available_plugins_name_version()

        self.ui.details.setText("")

        self.ui.plugins.setSortingEnabled(False)
        for plugin in self.installed_plugins():
            new_version = None
            if plugin.module_name in available_plugins:
                latest = available_plugins[plugin.module_name]
                if latest.split('.') > plugin.version.split('.'):
                    new_version = latest
            self.update_plugin_item(None,
                                    plugin,
                                    enabled=self.is_plugin_enabled(plugin),
                                    new_version=new_version,
                                    is_installed=True)

        for plugin in self.installable_plugins():
            self.update_plugin_item(None,
                                    plugin,
                                    enabled=False,
                                    is_installed=False)

        self.ui.plugins.setSortingEnabled(True)
        self._user_interaction(True)
        header = self.ui.plugins.header()
        header.setStretchLastSection(False)
        header.setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
        header.setSectionResizeMode(COLUMN_NAME, QtWidgets.QHeaderView.Stretch)
        header.setSectionResizeMode(COLUMN_VERSION,
                                    QtWidgets.QHeaderView.ResizeToContents)
        header.setSectionResizeMode(COLUMN_ACTIONS,
                                    QtWidgets.QHeaderView.ResizeToContents)

    def _remove_all(self):
        for item in self.items():
            idx = self.ui.plugins.indexOfTopLevelItem(item)
            self.ui.plugins.takeTopLevelItem(idx)

    def restore_defaults(self):
        self._user_interaction(False)
        self._remove_all()
        super().restore_defaults()
        self.set_current_item(self.ui.plugins.topLevelItem(0), scroll=True)

    def load(self):
        self._populate()
        self.restore_state()

    def _preserve_plugins_states(self):
        self._preserve = {
            item.plugin.module_name: item.save_state()
            for item in self.items()
        }
        item = self.selected_item()
        if item:
            self._preserve_selected = item.plugin.module_name
        else:
            self._preserve_selected = None

    def _restore_plugins_states(self):
        for item in self.items():
            plugin = item.plugin
            if plugin.module_name in self._preserve:
                item.restore_state(self._preserve[plugin.module_name])
                if self._preserve_selected == plugin.module_name:
                    self.set_current_item(item, scroll=True)

    def _reload(self):
        self._remove_all()
        self._populate()
        self._restore_plugins_states()

    def _user_interaction(self, enabled):
        self.ui.plugins.blockSignals(not enabled)
        self.ui.plugins_container.setEnabled(enabled)

    def reload_list_of_plugins(self):
        self.ui.details.setText(_("Reloading list of available plugins..."))
        self._user_interaction(False)
        self._preserve_plugins_states()
        self.manager.query_available_plugins(callback=self._reload)

    def plugin_loading_error(self, plugin_name, error):
        QtWidgets.QMessageBox.critical(
            self,
            _("Plugin '%s'") % plugin_name,
            _("An error occured while loading the plugin '%s':\n\n%s") %
            (plugin_name, error))

    def plugin_installed(self, plugin):
        log.debug("Plugin %r installed", plugin.name)
        if not plugin.compatible:
            QtWidgets.QMessageBox.warning(
                self,
                _("Plugin '%s'") % plugin.name,
                _("The plugin '%s' is not compatible with this version of Picard."
                  ) % plugin.name)
            return
        item = self.find_item_by_plugin_name(plugin.module_name)
        if item:
            self.update_plugin_item(item,
                                    plugin,
                                    make_current=True,
                                    enabled=True,
                                    is_installed=True)

    def plugin_updated(self, plugin_name):
        log.debug("Plugin %r updated", plugin_name)
        item = self.find_item_by_plugin_name(plugin_name)
        if item:
            plugin = item.plugin
            QtWidgets.QMessageBox.information(
                self,
                _("Plugin '%s'") % plugin_name,
                _("The plugin '%s' will be upgraded to version %s on next run of Picard."
                  ) % (plugin.name, item.new_version))

            item.upgrade_to_version = item.new_version
            self.update_plugin_item(item, plugin, make_current=True)

    def plugin_removed(self, plugin_name):
        log.debug("Plugin %r removed", plugin_name)
        item = self.find_item_by_plugin_name(plugin_name)
        if item:
            self.update_plugin_item(item,
                                    None,
                                    make_current=True,
                                    is_installed=False)

    def uninstall_plugin(self, item):
        plugin = item.plugin
        buttonReply = QtWidgets.QMessageBox.question(
            self,
            _("Uninstall plugin '%s'?") % plugin.name,
            _("Do you really want to uninstall the plugin '%s' ?") %
            plugin.name, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
            QtWidgets.QMessageBox.No)
        if buttonReply == QtWidgets.QMessageBox.Yes:
            self.manager.remove_plugin(plugin.module_name, with_update=True)

    def update_plugin_item(self,
                           item,
                           plugin,
                           make_current=False,
                           enabled=None,
                           new_version=None,
                           is_installed=None):
        if item is None:
            item = PluginTreeWidgetItem(self.ui.plugins)
        if plugin is not None:
            item.setData(COLUMN_NAME, QtCore.Qt.UserRole, plugin)
        else:
            plugin = item.plugin
        if new_version is not None:
            item.new_version = new_version
        if is_installed is not None:
            item.is_installed = is_installed
        if enabled is None:
            enabled = item.is_enabled

        def update_text():
            if item.new_version is not None:
                version = "%s → %s" % (plugin.version, item.new_version)
            else:
                version = plugin.version

            if item.installed_font is None:
                item.installed_font = item.font(COLUMN_NAME)
            if item.enabled_font is None:
                item.enabled_font = QtGui.QFont(item.installed_font)
                item.enabled_font.setBold(True)
            if item.available_font is None:
                item.available_font = QtGui.QFont(item.installed_font)

            if item.is_enabled:
                item.setFont(COLUMN_NAME, item.enabled_font)
            else:
                if item.is_installed:
                    item.setFont(COLUMN_NAME, item.installed_font)
                else:
                    item.setFont(COLUMN_NAME, item.available_font)

            item.setText(COLUMN_NAME, plugin.name)
            item.setText(COLUMN_VERSION, version)

        def toggle_enable():
            item.enable(not item.is_enabled, greyout=not item.is_installed)
            log.debug("Plugin %r enabled: %r", item.plugin.name,
                      item.is_enabled)
            update_text()

        reconnect(item.buttons['enable'].clicked, toggle_enable)

        install_enabled = not item.is_installed or bool(item.new_version)
        if item.upgrade_to_version:
            if item.upgrade_to_version != item.new_version:
                # case when a new version is known after a plugin was marked for update
                install_enabled = True
            else:
                install_enabled = False

        if install_enabled:
            if item.new_version is not None:

                def download_and_update():
                    self.download_plugin(item, update=True)

                reconnect(item.buttons['update'].clicked, download_and_update)
                item.buttons['install'].mode('hide')
                item.buttons['update'].mode('show')
            else:

                def download_and_install():
                    self.download_plugin(item)

                reconnect(item.buttons['install'].clicked,
                          download_and_install)
                item.buttons['install'].mode('show')
                item.buttons['update'].mode('hide')

        if item.is_installed:
            item.buttons['install'].mode('hide')
            item.buttons['uninstall'].mode('show')
            item.enable(enabled, greyout=False)

            def uninstall_processor():
                self.uninstall_plugin(item)

            reconnect(item.buttons['uninstall'].clicked, uninstall_processor)
        else:
            item.buttons['uninstall'].mode('hide')
            item.buttons['enable'].mode('hide')

        update_text()

        if make_current:
            self.set_current_item(item)

        actions_sort_score = 2
        if item.is_installed:
            if item.is_enabled:
                actions_sort_score = 0
            else:
                actions_sort_score = 1

        item.setSortData(COLUMN_ACTIONS, actions_sort_score)
        item.setSortData(COLUMN_NAME, plugin.name.lower())

        def v2int(elem):
            try:
                return int(elem)
            except ValueError:
                return 0

        item.setSortData(COLUMN_VERSION,
                         tuple(v2int(e) for e in plugin.version.split('.')))

        return item

    def save(self):
        config.setting["enabled_plugins"] = self.enabled_plugins()
        self.save_state()

    def refresh_details(self, item):
        plugin = item.plugin
        text = []
        if item.new_version is not None:
            if item.upgrade_to_version:
                label = _("Restart Picard to upgrade to new version")
            else:
                label = _("New version available")
            text.append("<b>" + label + ": " + item.new_version + "</b>")
        if plugin.description:
            text.append(plugin.description + "<hr width='90%'/>")
        if plugin.name:
            text.append("<b>" + _("Name") + "</b>: " + plugin.name)
        if plugin.author:
            text.append("<b>" + _("Authors") + "</b>: " + plugin.author)
        if plugin.license:
            text.append("<b>" + _("License") + "</b>: " + plugin.license)
        text.append("<b>" + _("Files") + "</b>: " + plugin.files_list)
        self.ui.details.setText("<p>%s</p>" % "<br/>\n".join(text))

    def change_details(self):
        item = self.selected_item()
        if item:
            self.refresh_details(item)

    def open_plugins(self):
        files, _filter = QtWidgets.QFileDialog.getOpenFileNames(
            self, "", QtCore.QDir.homePath(),
            "Picard plugin (*.py *.pyc *.zip)")
        if files:
            for path in files:
                self.manager.install_plugin(path)

    def download_plugin(self, item, update=False):
        plugin = item.plugin

        self.tagger.webservice.get(PLUGINS_API['host'],
                                   PLUGINS_API['port'],
                                   PLUGINS_API['endpoint']['download'],
                                   partial(self.download_handler,
                                           update,
                                           plugin=plugin),
                                   parse_response_type=None,
                                   priority=True,
                                   important=True,
                                   queryargs={"id": plugin.module_name})

    def download_handler(self, update, response, reply, error, plugin):
        if error:
            msgbox = QtWidgets.QMessageBox(self)
            msgbox.setText(
                _("The plugin '%s' could not be downloaded.") %
                plugin.module_name)
            msgbox.setInformativeText(_("Please try again later."))
            msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok)
            msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
            msgbox.exec_()
            log.error(
                "Error occurred while trying to download the plugin: '%s'" %
                plugin.module_name)
            return

        self.manager.install_plugin(
            None,
            update=update,
            plugin_name=plugin.module_name,
            plugin_data=response,
        )

    @staticmethod
    def open_plugin_dir():
        if sys.platform == 'win32':
            url = 'file:///' + USER_PLUGIN_DIR
        else:
            url = 'file://' + USER_PLUGIN_DIR
        QtGui.QDesktopServices.openUrl(
            QtCore.QUrl(url, QtCore.QUrl.TolerantMode))

    def mimeTypes(self):
        return ["text/uri-list"]

    def dragEnterEvent(self, event):
        event.setDropAction(QtCore.Qt.CopyAction)
        event.accept()

    def dropEvent(self, event):
        for path in [
                os.path.normpath(u.toLocalFile())
                for u in event.mimeData().urls()
        ]:
            self.manager.install_plugin(path)
Beispiel #10
0
class PluginsOptionsPage(OptionsPage):

    NAME = "plugins"
    TITLE = N_("Plugins")
    PARENT = None
    SORT_ORDER = 70
    ACTIVE = True

    options = [
        config.ListOption("setting", "enabled_plugins", []),
    ]

    def __init__(self, parent=None):
        super(PluginsOptionsPage, self).__init__(parent)
        self.ui = Ui_PluginsOptionsPage()
        self.ui.setupUi(self)
        self.items = {}
        self.ui.plugins.itemSelectionChanged.connect(self.change_details)
        self.ui.plugins.mimeTypes = self.mimeTypes
        self.ui.plugins.dropEvent = self.dropEvent
        self.ui.plugins.dragEnterEvent = self.dragEnterEvent
        if sys.platform == "win32":
            self.loader = "file:///%s"
        else:
            self.loader = "file://%s"
        self.ui.install_plugin.clicked.connect(self.open_plugins)
        self.ui.folder_open.clicked.connect(self.open_plugin_dir)
        self.ui.plugin_download.clicked.connect(self.open_plugin_site)
        self.tagger.pluginmanager.plugin_installed.connect(self.plugin_installed)

    def load(self):
        plugins = sorted(self.tagger.pluginmanager.plugins, cmp=cmp_plugins)
        enabled_plugins = config.setting["enabled_plugins"]
        firstitem = None
        for plugin in plugins:
            enabled = plugin.module_name in enabled_plugins
            item = self.add_plugin_item(plugin, enabled=enabled)
            if not firstitem:
                firstitem = item
        self.ui.plugins.setCurrentItem(firstitem)

    def plugin_installed(self, plugin):
        if not plugin.compatible:
            msgbox = QtGui.QMessageBox(self)
            msgbox.setText(u"The plugin ‘%s’ is not compatible with this version of Picard." % plugin.name)
            msgbox.setStandardButtons(QtGui.QMessageBox.Ok)
            msgbox.setDefaultButton(QtGui.QMessageBox.Ok)
            msgbox.exec_()
            return
        for i, p in self.items.items():
            if plugin.module_name == p.module_name:
                enabled = i.checkState(0) == QtCore.Qt.Checked
                self.add_plugin_item(plugin, enabled=enabled, item=i)
                break
        else:
            self.add_plugin_item(plugin)

    def add_plugin_item(self, plugin, enabled=False, item=None):
        if item is None:
            item = QtGui.QTreeWidgetItem(self.ui.plugins)
        item.setText(0, plugin.name)
        if enabled:
            item.setCheckState(0, QtCore.Qt.Checked)
        else:
            item.setCheckState(0, QtCore.Qt.Unchecked)
        item.setText(1, plugin.version)
        item.setText(2, plugin.author)
        self.ui.plugins.header().resizeSections(QtGui.QHeaderView.ResizeToContents)
        self.items[item] = plugin
        return item

    def save(self):
        enabled_plugins = []
        for item, plugin in self.items.iteritems():
            if item.checkState(0) == QtCore.Qt.Checked:
                enabled_plugins.append(plugin.module_name)
        config.setting["enabled_plugins"] = enabled_plugins

    def change_details(self):
        plugin = self.items[self.ui.plugins.selectedItems()[0]]
        text = []
        name = plugin.name
        descr = plugin.description
        if descr:
            text.append(descr + "<br/>")
            text.append('______________________________')
        if name:
            text.append("<b>" + _("Name") + "</b>: " + name)
        author = plugin.author
        if author:
            text.append("<b>" + _("Author") + "</b>: " + author)
        text.append("<b>" + _("File") + "</b>: " + plugin.file[len(plugin.dir)+1:])
        self.ui.details.setText("<p>%s</p>" % "<br/>\n".join(text))

    def open_plugins(self):
        files = QtGui.QFileDialog.getOpenFileNames(self, "",
                                                   QtCore.QDir.homePath(),
                                                   "Picard plugin (*.py *.pyc)")
        if files:
            files = map(unicode, files)
            for path in files:
                self.install_plugin(path)

    def install_plugin(self, path):
        path = encode_filename(path)
        file = os.path.basename(path)
        dest = os.path.join(USER_PLUGIN_DIR, file)
        if os.path.exists(dest):
            msgbox = QtGui.QMessageBox(self)
            msgbox.setText("A plugin named %s is already installed." % file)
            msgbox.setInformativeText("Do you want to overwrite the existing plugin?")
            msgbox.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
            msgbox.setDefaultButton(QtGui.QMessageBox.No)
            if msgbox.exec_() == QtGui.QMessageBox.No:
                return
        self.tagger.pluginmanager.install_plugin(path, dest)

    def open_plugin_dir(self):
        QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.loader % USER_PLUGIN_DIR, QtCore.QUrl.TolerantMode))

    def open_plugin_site(self):
        webbrowser2.goto('plugins')

    def mimeTypes(self):
        return ["text/uri-list"]

    def dragEnterEvent(self, event):
        event.setDropAction(QtCore.Qt.CopyAction)
        event.accept()

    def dropEvent(self, event):
        for path in [os.path.normpath(unicode(u.toLocalFile())) for u in event.mimeData().urls()]:
            self.install_plugin(path)
Beispiel #11
0
class PluginsOptionsPage(OptionsPage):

    NAME = "plugins"
    TITLE = N_("Plugins")
    PARENT = None
    SORT_ORDER = 70
    ACTIVE = True

    options = [
        config.TextOption("setting", "enabled_plugins", ""),
    ]

    def __init__(self, parent=None):
        super(PluginsOptionsPage, self).__init__(parent)
        self.ui = Ui_PluginsOptionsPage()
        self.ui.setupUi(self)
        self.items = {}
        self.ui.plugins.itemSelectionChanged.connect(self.change_details)
        self.ui.plugins.mimeTypes = self.mimeTypes
        self.ui.plugins.dropEvent = self.dropEvent
        self.ui.plugins.dragEnterEvent = self.dragEnterEvent
        if sys.platform == "win32":
            self.loader = "file:///%s"
        else:
            self.loader = "file://%s"
        self.ui.install_plugin.clicked.connect(self.open_plugins)
        self.ui.folder_open.clicked.connect(self.open_plugin_dir)
        self.ui.plugin_download.clicked.connect(self.open_plugin_site)
        self.tagger.pluginmanager.plugin_installed.connect(
            self.plugin_installed)

    def load(self):
        plugins = sorted(self.tagger.pluginmanager.plugins, cmp=cmp_plugins)
        enabled_plugins = config.setting["enabled_plugins"].split()
        firstitem = None
        for plugin in plugins:
            enabled = plugin.module_name in enabled_plugins
            item = self.add_plugin_item(plugin, enabled=enabled)
            if not firstitem:
                firstitem = item
        self.ui.plugins.setCurrentItem(firstitem)

    def plugin_installed(self, plugin):
        if not plugin.compatible:
            msgbox = QtGui.QMessageBox(self)
            msgbox.setText(
                u"The plugin ‘%s’ is not compatible with this version of Picard."
                % plugin.name)
            msgbox.setStandardButtons(QtGui.QMessageBox.Ok)
            msgbox.setDefaultButton(QtGui.QMessageBox.Ok)
            msgbox.exec_()
            return
        for i, p in self.items.items():
            if plugin.module_name == p.module_name:
                enabled = i.checkState(0) == QtCore.Qt.Checked
                self.add_plugin_item(plugin, enabled=enabled, item=i)
                break
        else:
            self.add_plugin_item(plugin)

    def add_plugin_item(self, plugin, enabled=False, item=None):
        if item is None:
            item = QtGui.QTreeWidgetItem(self.ui.plugins)
        item.setText(0, plugin.name)
        if enabled:
            item.setCheckState(0, QtCore.Qt.Checked)
        else:
            item.setCheckState(0, QtCore.Qt.Unchecked)
        item.setText(1, plugin.version)
        item.setText(2, plugin.author)
        self.ui.plugins.header().resizeSections(
            QtGui.QHeaderView.ResizeToContents)
        self.items[item] = plugin
        return item

    def save(self):
        enabled_plugins = []
        for item, plugin in self.items.iteritems():
            if item.checkState(0) == QtCore.Qt.Checked:
                enabled_plugins.append(plugin.module_name)
        config.setting["enabled_plugins"] = " ".join(enabled_plugins)

    def change_details(self):
        plugin = self.items[self.ui.plugins.selectedItems()[0]]
        text = []
        name = plugin.name
        descr = plugin.description
        if descr:
            text.append(descr + "<br/>")
            text.append('______________________________')
        if name:
            text.append("<b>" + _("Name") + "</b>: " + name)
        author = plugin.author
        if author:
            text.append("<b>" + _("Author") + "</b>: " + author)
        text.append("<b>" + _("File") + "</b>: " +
                    plugin.file[len(plugin.dir) + 1:])
        self.ui.details.setText("<p>%s</p>" % "<br/>\n".join(text))

    def open_plugins(self):
        files = QtGui.QFileDialog.getOpenFileNames(
            self, "", "/", "Picard plugin (*.py *.pyc)")
        if files:
            files = map(unicode, files)
            for path in files:
                self.install_plugin(path)

    def install_plugin(self, path):
        path = encode_filename(path)
        file = os.path.basename(path)
        dest = os.path.join(USER_PLUGIN_DIR, file)
        if os.path.exists(dest):
            msgbox = QtGui.QMessageBox(self)
            msgbox.setText("A plugin named %s is already installed." % file)
            msgbox.setInformativeText(
                "Do you want to overwrite the existing plugin?")
            msgbox.setStandardButtons(QtGui.QMessageBox.Yes
                                      | QtGui.QMessageBox.No)
            msgbox.setDefaultButton(QtGui.QMessageBox.No)
            if msgbox.exec_() == QtGui.QMessageBox.No:
                return
        self.tagger.pluginmanager.install_plugin(path, dest)

    def open_plugin_dir(self):
        QtGui.QDesktopServices.openUrl(
            QtCore.QUrl(self.loader % USER_PLUGIN_DIR,
                        QtCore.QUrl.TolerantMode))

    def open_plugin_site(self):
        QtGui.QDesktopServices.openUrl(
            QtCore.QUrl("http://musicbrainz.org/doc/Picard_Plugins",
                        QtCore.QUrl.TolerantMode))

    def mimeTypes(self):
        return ["text/uri-list"]

    def dragEnterEvent(self, event):
        event.setDropAction(QtCore.Qt.CopyAction)
        event.accept()

    def dropEvent(self, event):
        for path in [
                os.path.normpath(unicode(u.toLocalFile()))
                for u in event.mimeData().urls()
        ]:
            self.install_plugin(path)
Beispiel #12
0
class PluginsOptionsPage(OptionsPage):

    NAME = "plugins"
    TITLE = N_("Plugins")
    PARENT = None
    SORT_ORDER = 70
    ACTIVE = True

    options = [
        config.ListOption("setting", "enabled_plugins", []),
        config.Option("persist", "plugins_list_state", QtCore.QByteArray()),
        config.Option("persist", "plugins_list_sort_section", 0),
        config.Option("persist", "plugins_list_sort_order",
                      QtCore.Qt.AscendingOrder),
        config.Option("persist", "plugins_list_selected", ""),
    ]

    def __init__(self, parent=None):
        super(PluginsOptionsPage, self).__init__(parent)
        self.ui = Ui_PluginsOptionsPage()
        self.ui.setupUi(self)
        self.items = {}
        self.ui.plugins.itemSelectionChanged.connect(self.change_details)
        self.ui.plugins.mimeTypes = self.mimeTypes
        self.ui.plugins.dropEvent = self.dropEvent
        self.ui.plugins.dragEnterEvent = self.dragEnterEvent
        if sys.platform == "win32":
            self.loader = "file:///%s"
        else:
            self.loader = "file://%s"
        self.ui.install_plugin.clicked.connect(self.open_plugins)
        self.ui.folder_open.clicked.connect(self.open_plugin_dir)
        self.ui.reload_list_of_plugins.clicked.connect(
            self.reload_list_of_plugins)
        self.tagger.pluginmanager.plugin_installed.connect(
            self.plugin_installed)
        self.tagger.pluginmanager.plugin_updated.connect(self.plugin_updated)
        self.ui.plugins.header().setStretchLastSection(False)
        self.ui.plugins.header().setSectionResizeMode(
            0, QtWidgets.QHeaderView.Stretch)
        self.ui.plugins.header().setSectionResizeMode(
            1, QtWidgets.QHeaderView.Stretch)
        self.ui.plugins.header().resizeSection(2, 100)
        self.ui.plugins.setSortingEnabled(True)

    def save_state(self):
        header = self.ui.plugins.header()
        config.persist["plugins_list_state"] = header.saveState()
        config.persist[
            "plugins_list_sort_section"] = header.sortIndicatorSection()
        config.persist["plugins_list_sort_order"] = header.sortIndicatorOrder()
        try:
            selected = self.items[self.ui.plugins.selectedItems()
                                  [0]].module_name
        except IndexError:
            selected = ""
        config.persist["plugins_list_selected"] = selected

    def restore_state(self, restore_selection=False):
        header = self.ui.plugins.header()
        header.restoreState(config.persist["plugins_list_state"])
        idx = config.persist["plugins_list_sort_section"]
        order = config.persist["plugins_list_sort_order"]
        header.setSortIndicator(idx, order)
        self.ui.plugins.sortByColumn(idx, order)
        selected = restore_selection and config.persist["plugins_list_selected"]
        if selected:
            for i, p in self.items.items():
                if selected == p.module_name:
                    self.ui.plugins.setCurrentItem(i)
                    self.ui.plugins.scrollToItem(i)
                    break
        else:
            self.ui.plugins.setCurrentItem(self.ui.plugins.topLevelItem(0))

    def _populate(self):
        self.ui.details.setText("<b>" + _("No plugins installed.") + "</b>")
        self._user_interaction(False)
        plugins = sorted(self.tagger.pluginmanager.plugins,
                         key=attrgetter('name'))
        enabled_plugins = config.setting["enabled_plugins"]
        available_plugins = dict([
            (p.module_name, p.version)
            for p in self.tagger.pluginmanager.available_plugins
        ])
        installed = []
        for plugin in plugins:
            if plugin.module_name in enabled_plugins:
                plugin.enabled = True
            if plugin.module_name in available_plugins.keys():
                latest = available_plugins[plugin.module_name]
                if latest.split('.') > plugin.version.split('.'):
                    plugin.new_version = latest
                    plugin.can_be_updated = True
            self.add_plugin_item(plugin)
            installed.append(plugin.module_name)

        for plugin in sorted(self.tagger.pluginmanager.available_plugins,
                             key=attrgetter('name')):
            if plugin.module_name not in installed:
                plugin.can_be_downloaded = True
                self.add_plugin_item(plugin)

        self._user_interaction(True)

    def _remove_all(self):
        for i, p in self.items.items():
            idx = self.ui.plugins.indexOfTopLevelItem(i)
            self.ui.plugins.takeTopLevelItem(idx)
        self.items = {}

    def restore_defaults(self):
        # Plugin manager has to be updated
        for plugin in self.tagger.pluginmanager.plugins:
            plugin.enabled = False
        # Remove previous entries
        self._user_interaction(False)
        self._remove_all()
        super(PluginsOptionsPage, self).restore_defaults()

    def load(self):
        self._populate()
        self.restore_state()

    def _reload(self):
        self._populate()
        self.restore_state(restore_selection=True)

    def _user_interaction(self, enabled):
        self.ui.plugins.blockSignals(not enabled)
        self.ui.plugins_container.setEnabled(enabled)

    def reload_list_of_plugins(self):
        self.ui.details.setText("")
        self._user_interaction(False)
        self.save_state()
        self._remove_all()
        self.tagger.pluginmanager.query_available_plugins(
            callback=self._reload)

    def plugin_installed(self, plugin):
        if not plugin.compatible:
            msgbox = QtWidgets.QMessageBox(self)
            msgbox.setText(
                _("The plugin '%s' is not compatible with this version of Picard."
                  ) % plugin.name)
            msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok)
            msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
            msgbox.exec_()
            return
        plugin.new_version = ""
        plugin.enabled = False
        plugin.can_be_updated = False
        plugin.can_be_downloaded = False
        for i, p in self.items.items():
            if plugin.module_name == p.module_name:
                if i.checkState(0) == QtCore.Qt.Checked:
                    plugin.enabled = True
                self.add_plugin_item(plugin, item=i)
                self.ui.plugins.setCurrentItem(i)
                self.change_details()
                break
        else:
            self.add_plugin_item(plugin)

    def plugin_updated(self, plugin_name):
        for i, p in self.items.items():
            if plugin_name == p.module_name:
                p.can_be_updated = False
                p.can_be_downloaded = False
                p.marked_for_update = True
                msgbox = QtWidgets.QMessageBox(self)
                msgbox.setText(
                    _("The plugin '%s' will be upgraded to version %s on next run of Picard."
                      ) % (p.name, p.new_version))
                msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                msgbox.exec_()
                self.add_plugin_item(p, item=i)
                self.ui.plugins.setCurrentItem(i)
                self.change_details()
                break

    def add_plugin_item(self, plugin, item=None):
        if item is None:
            item = PluginTreeWidgetItem(self.ui.plugins)
        item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
        item.setText(0, plugin.name)
        item.setSortData(0, plugin.name.lower())
        if plugin.enabled:
            item.setCheckState(0, QtCore.Qt.Checked)
        else:
            item.setCheckState(0, QtCore.Qt.Unchecked)

        if plugin.marked_for_update:
            item.setText(1, plugin.new_version)
        else:
            item.setText(1, plugin.version)

        label = None
        if plugin.can_be_updated:
            label = _("Update")
        elif plugin.can_be_downloaded:
            label = _("Install")
            item.setFlags(item.flags() ^ QtCore.Qt.ItemIsUserCheckable)

        if label is not None:
            button = QtWidgets.QPushButton(label)
            button.setMaximumHeight(
                button.fontMetrics().boundingRect(label).height() + 7)
            self.ui.plugins.setItemWidget(item, 2, button)

            def download_button_process():
                self.ui.plugins.setCurrentItem(item)
                self.download_plugin()

            button.released.connect(download_button_process)
        else:
            # Note: setText() don't work after it was set to a button
            if plugin.marked_for_update:
                label = _("Updated")
            else:
                label = _("Installed")
            self.ui.plugins.setItemWidget(item, 2, QtWidgets.QLabel(label))
        item.setSortData(2, label)

        self.ui.plugins.header().resizeSections(
            QtWidgets.QHeaderView.ResizeToContents)
        self.items[item] = plugin
        return item

    def save(self):
        enabled_plugins = []
        for item, plugin in self.items.items():
            if item.checkState(0) == QtCore.Qt.Checked:
                enabled_plugins.append(plugin.module_name)
        config.setting["enabled_plugins"] = enabled_plugins
        self.save_state()

    def change_details(self):
        try:
            plugin = self.items[self.ui.plugins.selectedItems()[0]]
        except IndexError:
            return
        text = []
        if plugin.new_version:
            if plugin.marked_for_update:
                text.append("<b>" +
                            _("Restart Picard to upgrade to new version") +
                            ": " + plugin.new_version + "</b>")
            else:
                text.append("<b>" + _("New version available") + ": " +
                            plugin.new_version + "</b>")
        if plugin.description:
            text.append(plugin.description + "<hr width='90%'/>")
        if plugin.name:
            text.append("<b>" + _("Name") + "</b>: " + plugin.name)
        if plugin.author:
            text.append("<b>" + _("Authors") + "</b>: " + plugin.author)
        if plugin.license:
            text.append("<b>" + _("License") + "</b>: " + plugin.license)
        text.append("<b>" + _("Files") + "</b>: " + plugin.files_list)
        self.ui.details.setText("<p>%s</p>" % "<br/>\n".join(text))

    def open_plugins(self):
        files, _filter = QtWidgets.QFileDialog.getOpenFileNames(
            self, "", QtCore.QDir.homePath(),
            "Picard plugin (*.py *.pyc *.zip)")
        if files:
            for path in files:
                self.tagger.pluginmanager.install_plugin(path)

    def download_plugin(self):
        selected = self.ui.plugins.selectedItems()[0]
        plugin = self.items[selected]

        self.tagger.webservice.get(PLUGINS_API['host'],
                                   PLUGINS_API['port'],
                                   PLUGINS_API['endpoint']['download'],
                                   partial(self.download_handler,
                                           plugin=plugin),
                                   parse_response_type=None,
                                   priority=True,
                                   important=True,
                                   queryargs={"id": plugin.module_name})

    def download_handler(self, response, reply, error, plugin):
        if error:
            msgbox = QtWidgets.QMessageBox(self)
            msgbox.setText(
                _("The plugin '%s' could not be downloaded.") %
                plugin.module_name)
            msgbox.setInformativeText(_("Please try again later."))
            msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok)
            msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
            msgbox.exec_()
            log.error(
                "Error occurred while trying to download the plugin: '%s'" %
                plugin.module_name)
            return

        self.tagger.pluginmanager.install_plugin(
            None, plugin_name=plugin.module_name, plugin_data=response)

    def open_plugin_dir(self):
        QtGui.QDesktopServices.openUrl(
            QtCore.QUrl(self.loader % USER_PLUGIN_DIR,
                        QtCore.QUrl.TolerantMode))

    def mimeTypes(self):
        return ["text/uri-list"]

    def dragEnterEvent(self, event):
        event.setDropAction(QtCore.Qt.CopyAction)
        event.accept()

    def dropEvent(self, event):
        for path in [
                os.path.normpath(u.toLocalFile())
                for u in event.mimeData().urls()
        ]:
            self.tagger.pluginmanager.install_plugin(path)
Beispiel #13
0
class PluginsOptionsPage(OptionsPage):

    NAME = "plugins"
    TITLE = N_("Plugins")
    PARENT = None
    SORT_ORDER = 70
    ACTIVE = True

    options = [
        config.ListOption("setting", "enabled_plugins", []),
        config.Option("persist", "plugins_list_state", QtCore.QByteArray()),
        config.Option("persist", "plugins_list_sort_section", 0),
        config.Option("persist", "plugins_list_sort_order",
                      QtCore.Qt.AscendingOrder),
        config.Option("persist", "plugins_list_selected", ""),
    ]

    def __init__(self, parent=None):
        super(PluginsOptionsPage, self).__init__(parent)
        self.ui = Ui_PluginsOptionsPage()
        self.ui.setupUi(self)
        self.items = {}
        self.ui.plugins.itemSelectionChanged.connect(self.change_details)
        self.ui.plugins.mimeTypes = self.mimeTypes
        self.ui.plugins.dropEvent = self.dropEvent
        self.ui.plugins.dragEnterEvent = self.dragEnterEvent
        if sys.platform == "win32":
            self.loader = "file:///%s"
        else:
            self.loader = "file://%s"
        self.ui.install_plugin.clicked.connect(self.open_plugins)
        self.ui.folder_open.clicked.connect(self.open_plugin_dir)
        self.ui.reload_list_of_plugins.clicked.connect(self.reload_list_of_plugins)
        self.tagger.pluginmanager.plugin_installed.connect(self.plugin_installed)
        self.tagger.pluginmanager.plugin_updated.connect(self.plugin_updated)
        self.ui.plugins.header().setStretchLastSection(False)
        self.ui.plugins.header().setResizeMode(0, QtGui.QHeaderView.Stretch)
        self.ui.plugins.header().setResizeMode(1, QtGui.QHeaderView.Stretch)
        self.ui.plugins.header().resizeSection(2, 100)
        self.ui.plugins.setSortingEnabled(True)

    def save_state(self):
        header = self.ui.plugins.header()
        config.persist["plugins_list_state"] = header.saveState()
        config.persist["plugins_list_sort_section"] = header.sortIndicatorSection()
        config.persist["plugins_list_sort_order"] = header.sortIndicatorOrder()
        try:
            selected = self.items[self.ui.plugins.selectedItems()[0]].module_name
        except IndexError:
            selected = ""
        config.persist["plugins_list_selected"] = selected

    def restore_state(self, restore_selection=False):
        header = self.ui.plugins.header()
        header.restoreState(config.persist["plugins_list_state"])
        idx = config.persist["plugins_list_sort_section"]
        order = config.persist["plugins_list_sort_order"]
        header.setSortIndicator(idx, order)
        self.ui.plugins.sortByColumn(idx, order)
        selected = restore_selection and config.persist["plugins_list_selected"]
        if selected:
            for i, p in self.items.items():
                if selected == p.module_name:
                    self.ui.plugins.setCurrentItem(i)
                    self.ui.plugins.scrollToItem(i)
                    break
        else:
            self.ui.plugins.setCurrentItem(self.ui.plugins.topLevelItem(0))


    def _populate(self):
        self.ui.details.setText("<b>" + _("No plugins installed.") + "</b>")
        self._user_interaction(False)
        plugins = sorted(self.tagger.pluginmanager.plugins, cmp=cmp_plugins)
        enabled_plugins = config.setting["enabled_plugins"]
        available_plugins = dict([(p.module_name, p.version) for p in
                                  self.tagger.pluginmanager.available_plugins])
        installed = []
        for plugin in plugins:
            if plugin.module_name in enabled_plugins:
                plugin.enabled = True
            if plugin.module_name in available_plugins.keys():
                latest = available_plugins[plugin.module_name]
                if latest.split('.') > plugin.version.split('.'):
                    plugin.new_version = latest
                    plugin.can_be_updated = True
            item = self.add_plugin_item(plugin)
            installed.append(plugin.module_name)

        for plugin in sorted(self.tagger.pluginmanager.available_plugins, cmp=cmp_plugins):
            if plugin.module_name not in installed:
                plugin.can_be_downloaded = True
                item = self.add_plugin_item(plugin)

        self._user_interaction(True)

    def load(self):
        self._populate()
        self.restore_state()

    def _reload(self):
        self._populate()
        self.restore_state(restore_selection=True)

    def _user_interaction(self, enabled):
        self.ui.plugins.blockSignals(not enabled)
        self.ui.plugins_container.setEnabled(enabled)

    def reload_list_of_plugins(self):
        self.ui.details.setText("")
        self._user_interaction(False)
        self.save_state()
        for i, p in self.items.items():
            idx = self.ui.plugins.indexOfTopLevelItem(i)
            item = self.ui.plugins.takeTopLevelItem(idx)
            del item
        self.items = {}
        self.tagger.pluginmanager.query_available_plugins(callback=self._reload)

    def plugin_installed(self, plugin):
        if not plugin.compatible:
            msgbox = QtGui.QMessageBox(self)
            msgbox.setText(_(u"The plugin '%s' is not compatible with this version of Picard.") % plugin.name)
            msgbox.setStandardButtons(QtGui.QMessageBox.Ok)
            msgbox.setDefaultButton(QtGui.QMessageBox.Ok)
            msgbox.exec_()
            return
        plugin.new_version = False
        plugin.enabled = False
        plugin.can_be_updated = False
        plugin.can_be_downloaded = False
        for i, p in self.items.items():
            if plugin.module_name == p.module_name:
                if i.checkState(0) == QtCore.Qt.Checked:
                    plugin.enabled = True
                self.add_plugin_item(plugin, item=i)
                self.ui.plugins.setCurrentItem(i)
                self.change_details()
                break
        else:
            self.add_plugin_item(plugin)

    def plugin_updated(self, plugin_name):
        for i, p in self.items.items():
            if plugin_name == p.module_name:
                p.can_be_updated = False
                p.can_be_downloaded = False
                p.marked_for_update = True
                msgbox = QtGui.QMessageBox(self)
                msgbox.setText(
                    _(u"The plugin '%s' will be upgraded to version %s on next run of Picard.")
                    % (p.name, p.new_version))
                msgbox.setStandardButtons(QtGui.QMessageBox.Ok)
                msgbox.setDefaultButton(QtGui.QMessageBox.Ok)
                msgbox.exec_()
                self.add_plugin_item(p, item=i)
                self.ui.plugins.setCurrentItem(i)
                self.change_details()
                break

    def add_plugin_item(self, plugin, item=None):
        if item is None:
            item = PluginTreeWidgetItem(self.ui.plugins)
        item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
        item.setText(0, plugin.name)
        item.setSortData(0, plugin.name.lower())
        if plugin.enabled:
            item.setCheckState(0, QtCore.Qt.Checked)
        else:
            item.setCheckState(0, QtCore.Qt.Unchecked)

        if plugin.marked_for_update:
            item.setText(1, plugin.new_version)
        else:
            item.setText(1, plugin.version)

        label = None
        if plugin.can_be_updated:
            label = _("Update")
        elif plugin.can_be_downloaded:
            label = _("Install")
            item.setFlags(item.flags() ^ QtCore.Qt.ItemIsUserCheckable)

        if label is not None:
            button = QtGui.QPushButton(label)
            button.setMaximumHeight(button.fontMetrics().boundingRect(label).height() + 7)
            self.ui.plugins.setItemWidget(item, 2, button)

            def download_button_process():
                self.ui.plugins.setCurrentItem(item)
                self.download_plugin()
            button.released.connect(download_button_process)
        else:
            # Note: setText() don't work after it was set to a button
            if plugin.marked_for_update:
                label = _("Updated")
            else:
                label = _("Installed")
            self.ui.plugins.setItemWidget(item, 2, QtGui.QLabel(label))
        item.setSortData(2, label)

        self.ui.plugins.header().resizeSections(QtGui.QHeaderView.ResizeToContents)
        self.items[item] = plugin
        return item

    def save(self):
        enabled_plugins = []
        for item, plugin in self.items.iteritems():
            if item.checkState(0) == QtCore.Qt.Checked:
                enabled_plugins.append(plugin.module_name)
        config.setting["enabled_plugins"] = enabled_plugins
        self.save_state()

    def change_details(self):
        try:
            plugin = self.items[self.ui.plugins.selectedItems()[0]]
        except IndexError:
            return
        text = []
        if plugin.new_version:
            if plugin.marked_for_update:
                text.append("<b>" + _("Restart Picard to upgrade to new version") + ": " + plugin.new_version + "</b>")
            else:
                text.append("<b>" + _("New version available") + ": " + plugin.new_version + "</b>")
        if plugin.description:
            text.append(plugin.description + "<hr width='90%'/>")
        if plugin.name:
            text.append("<b>" + _("Name") + "</b>: " + plugin.name)
        if plugin.author:
            text.append("<b>" + _("Authors") + "</b>: " + plugin.author)
        if plugin.license:
            text.append("<b>" + _("License") + "</b>: " + plugin.license)
        text.append("<b>" + _("Files") + "</b>: " + plugin.files_list)
        self.ui.details.setText("<p>%s</p>" % "<br/>\n".join(text))

    def open_plugins(self):
        files = QtGui.QFileDialog.getOpenFileNames(self, "",
                                                   QtCore.QDir.homePath(),
                                                   "Picard plugin (*.py *.pyc *.zip)")
        if files:
            files = map(unicode, files)
            for path in files:
                self.tagger.pluginmanager.install_plugin(path)

    def download_plugin(self):
        selected = self.ui.plugins.selectedItems()[0]
        plugin = self.items[selected]

        self.tagger.xmlws.get(
            PLUGINS_API['host'],
            PLUGINS_API['port'],
            PLUGINS_API['endpoint']['download'],
            partial(self.download_handler, plugin=plugin),
            xml=False,
            priority=True,
            important=True,
            queryargs={"id": plugin.module_name}
        )

    def download_handler(self, response, reply, error, plugin):
        if error:
            msgbox = QtGui.QMessageBox(self)
            msgbox.setText(_(u"The plugin '%s' could not be downloaded.") % plugin.module_name)
            msgbox.setInformativeText(_("Please try again later."))
            msgbox.setStandardButtons(QtGui.QMessageBox.Ok)
            msgbox.setDefaultButton(QtGui.QMessageBox.Ok)
            msgbox.exec_()
            log.error("Error occurred while trying to download the plugin: '%s'" % plugin.module_name)
            return

        self.tagger.pluginmanager.install_plugin(
            None,
            plugin_name=plugin.module_name,
            plugin_data=response
        )

    def open_plugin_dir(self):
        QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.loader % USER_PLUGIN_DIR, QtCore.QUrl.TolerantMode))

    def mimeTypes(self):
        return ["text/uri-list"]

    def dragEnterEvent(self, event):
        event.setDropAction(QtCore.Qt.CopyAction)
        event.accept()

    def dropEvent(self, event):
        for path in [os.path.normpath(unicode(u.toLocalFile())) for u in event.mimeData().urls()]:
            self.tagger.pluginmanager.install_plugin(path)