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