mirror of
https://github.com/fergalmoran/picard.git
synced 2025-12-22 09:18:18 +00:00
733 lines
26 KiB
Python
733 lines
26 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Picard, the next-generation MusicBrainz tagger
|
|
#
|
|
# Copyright (C) 2007 Lukáš Lalinský
|
|
# Copyright (C) 2009 Carlin Mangar
|
|
# Copyright (C) 2009, 2018-2023 Philipp Wolfer
|
|
# Copyright (C) 2011-2013 Michael Wiencek
|
|
# Copyright (C) 2013, 2015, 2018-2024 Laurent Monin
|
|
# Copyright (C) 2013, 2017 Sophist-UK
|
|
# Copyright (C) 2014 Shadab Zafar
|
|
# Copyright (C) 2015, 2017 Wieland Hoffmann
|
|
# Copyright (C) 2016-2018 Sambhav Kothari
|
|
# Copyright (C) 2017 Suhas
|
|
# Copyright (C) 2018 Vishal Choudhary
|
|
# Copyright (C) 2018 yagyanshbhatia
|
|
# Copyright (C) 2023 tuspar
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
|
from functools import partial
|
|
from html import escape
|
|
from operator import attrgetter
|
|
import os.path
|
|
import re
|
|
|
|
from PyQt6 import (
|
|
QtCore,
|
|
QtGui,
|
|
QtWidgets,
|
|
)
|
|
from PyQt6.QtWidgets import QTreeWidgetItemIterator
|
|
|
|
from picard import log
|
|
from picard.config import get_config
|
|
from picard.const import (
|
|
PLUGINS_API,
|
|
USER_PLUGIN_DIR,
|
|
)
|
|
from picard.extension_points.options_pages import register_options_page
|
|
from picard.i18n import (
|
|
N_,
|
|
gettext as _,
|
|
)
|
|
from picard.util import (
|
|
icontheme,
|
|
open_local_path,
|
|
reconnect,
|
|
)
|
|
|
|
from picard.ui import HashableTreeWidgetItem
|
|
from picard.ui.forms.ui_options_plugins import Ui_PluginsOptionsPage
|
|
from picard.ui.options import OptionsPage
|
|
from picard.ui.theme import theme
|
|
|
|
|
|
COLUMN_NAME, COLUMN_VERSION, COLUMN_ACTIONS = range(3)
|
|
|
|
|
|
class PluginActionButton(QtWidgets.QToolButton):
|
|
|
|
def __init__(self, icon=None, tooltip=None, retain_space=False,
|
|
switch_method=None, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if tooltip is not None:
|
|
self.setToolTip(tooltip)
|
|
|
|
if icon is not None:
|
|
self.setIcon(self, icon)
|
|
|
|
if retain_space is True:
|
|
sp_retain = self.sizePolicy()
|
|
sp_retain.setRetainSizeWhenHidden(True)
|
|
self.setSizePolicy(sp_retain)
|
|
if switch_method is not None:
|
|
self.switch_method = switch_method
|
|
|
|
def mode(self, mode):
|
|
if self.switch_method is not None:
|
|
self.switch_method(self, mode)
|
|
|
|
def setIcon(self, icon):
|
|
super().setIcon(icon)
|
|
# Workaround for Qt sometimes not updating the icon.
|
|
# See https://tickets.metabrainz.org/browse/PICARD-1647
|
|
self.repaint()
|
|
|
|
|
|
class PluginTreeWidgetItem(HashableTreeWidgetItem):
|
|
|
|
def __init__(self, icons, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self._icons = icons
|
|
self._sortData = {}
|
|
self.upgrade_to_version = None
|
|
self.new_version = None
|
|
self.is_enabled = False
|
|
self.is_installed = False
|
|
self.installed_font = None
|
|
self.enabled_font = None
|
|
self.available_font = None
|
|
|
|
self.buttons = {}
|
|
self.buttons_widget = QtWidgets.QWidget()
|
|
|
|
layout = QtWidgets.QHBoxLayout()
|
|
layout.setContentsMargins(0, 2, 5, 2)
|
|
layout.addStretch(1)
|
|
self.buttons_widget.setLayout(layout)
|
|
|
|
def add_button(name, method):
|
|
button = PluginActionButton(switch_method=method)
|
|
layout.addWidget(button)
|
|
self.buttons[name] = button
|
|
button.mode('hide')
|
|
|
|
add_button('update', self.show_update)
|
|
add_button('uninstall', self.show_uninstall)
|
|
add_button('enable', self.show_enable)
|
|
add_button('install', self.show_install)
|
|
|
|
self.treeWidget().setItemWidget(self, COLUMN_ACTIONS,
|
|
self.buttons_widget)
|
|
|
|
def show_install(self, button, mode):
|
|
if mode == 'hide':
|
|
button.hide()
|
|
else:
|
|
button.show()
|
|
button.setToolTip(_("Download and install plugin"))
|
|
button.setIcon(self._icons['download'])
|
|
|
|
def show_update(self, button, mode):
|
|
if mode == 'hide':
|
|
button.hide()
|
|
else:
|
|
button.show()
|
|
button.setToolTip(_("Download and upgrade plugin to version %s") % self.new_version.short_str())
|
|
button.setIcon(self._icons['update'])
|
|
|
|
def show_enable(self, button, mode):
|
|
if mode == 'enabled':
|
|
button.show()
|
|
button.setToolTip(_("Enabled"))
|
|
button.setIcon(self._icons['enabled'])
|
|
elif mode == 'disabled':
|
|
button.show()
|
|
button.setToolTip(_("Disabled"))
|
|
button.setIcon(self._icons['disabled'])
|
|
else:
|
|
button.hide()
|
|
|
|
def show_uninstall(self, button, mode):
|
|
if mode == 'hide':
|
|
button.hide()
|
|
else:
|
|
button.show()
|
|
button.setToolTip(_("Uninstall plugin"))
|
|
button.setIcon(self._icons['uninstall'])
|
|
|
|
def save_state(self):
|
|
return {
|
|
'is_enabled': self.is_enabled,
|
|
'upgrade_to_version': self.upgrade_to_version,
|
|
'new_version': self.new_version,
|
|
'is_installed': self.is_installed,
|
|
}
|
|
|
|
def restore_state(self, states):
|
|
self.upgrade_to_version = states['upgrade_to_version']
|
|
self.new_version = states['new_version']
|
|
self.is_enabled = states['is_enabled']
|
|
self.is_installed = states['is_installed']
|
|
|
|
def __lt__(self, other):
|
|
if not isinstance(other, PluginTreeWidgetItem):
|
|
return super().__lt__(other)
|
|
|
|
tree = self.treeWidget()
|
|
if not tree:
|
|
column = 0
|
|
else:
|
|
column = tree.sortColumn()
|
|
|
|
return self.sortData(column) < other.sortData(column)
|
|
|
|
def sortData(self, column):
|
|
return self._sortData.get(column, self.text(column))
|
|
|
|
def setSortData(self, column, data):
|
|
self._sortData[column] = data
|
|
|
|
@property
|
|
def plugin(self):
|
|
return self.data(COLUMN_NAME, QtCore.Qt.ItemDataRole.UserRole)
|
|
|
|
def enable(self, boolean, greyout=None):
|
|
if boolean is not None:
|
|
self.is_enabled = boolean
|
|
if self.is_enabled:
|
|
self.buttons['enable'].mode('enabled')
|
|
else:
|
|
self.buttons['enable'].mode('disabled')
|
|
if greyout is not None:
|
|
self.buttons['enable'].setEnabled(not greyout)
|
|
|
|
|
|
class PluginsOptionsPage(OptionsPage):
|
|
|
|
NAME = 'plugins'
|
|
TITLE = N_("Plugins")
|
|
PARENT = None
|
|
SORT_ORDER = 70
|
|
ACTIVE = True
|
|
HELP_URL = "/config/options_plugins.html"
|
|
|
|
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
|
|
plugins.dragMoveEvent = self.dragMoveEvent
|
|
|
|
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
|
|
|
|
self.icons = {
|
|
'download': self.create_icon('plugin-download'),
|
|
'update': self.create_icon('plugin-update'),
|
|
'uninstall': self.create_icon('plugin-uninstall'),
|
|
'enabled': self.create_icon('plugin-enabled'),
|
|
'disabled': self.create_icon('plugin-disabled'),
|
|
}
|
|
|
|
def create_icon(self, icon_name):
|
|
if theme.is_dark_theme:
|
|
icon_name += '-dark'
|
|
return icontheme.lookup(icon_name, icontheme.ICON_SIZE_MENU)
|
|
|
|
def items(self):
|
|
iterator = QTreeWidgetItemIterator(self.ui.plugins, QTreeWidgetItemIterator.IteratorFlag.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 = get_config()
|
|
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()
|
|
config = get_config()
|
|
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):
|
|
config = get_config()
|
|
return bool(plugin.module_name in config.setting['enabled_plugins'])
|
|
|
|
def available_plugins_name_version(self):
|
|
return {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 > plugin.version:
|
|
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.ResizeMode.Fixed)
|
|
header.setSectionResizeMode(COLUMN_NAME, QtWidgets.QHeaderView.ResizeMode.Stretch)
|
|
header.setSectionResizeMode(COLUMN_VERSION, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
|
|
header.setSectionResizeMode(COLUMN_ACTIONS, QtWidgets.QHeaderView.ResizeMode.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):
|
|
if self.deleted:
|
|
return
|
|
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 "%(plugin)s"') % {'plugin': plugin_name},
|
|
_('An error occurred while loading the plugin "%(plugin)s":\n\n%(error)s') % {
|
|
'plugin': plugin_name,
|
|
'error': error,
|
|
})
|
|
|
|
def plugin_installed(self, plugin):
|
|
log.debug("Plugin %r installed", plugin.name)
|
|
if not plugin.compatible:
|
|
params = {'plugin': plugin.name}
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
_('Plugin "%(plugin)s"') % params,
|
|
_('The plugin "%(plugin)s" is not compatible with this version of Picard.') % params
|
|
)
|
|
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 "%(plugin)s"') % {'plugin': plugin_name},
|
|
_('The plugin "%(plugin)s" will be upgraded to version %(version)s on next run of Picard.') % {
|
|
'plugin': plugin.name,
|
|
'version': item.new_version.short_str(),
|
|
})
|
|
|
|
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
|
|
params = {'plugin': plugin.name}
|
|
buttonReply = QtWidgets.QMessageBox.question(
|
|
self,
|
|
_('Uninstall plugin "%(plugin)s"?') % params,
|
|
_('Do you really want to uninstall the plugin "%(plugin)s"?') % params,
|
|
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No,
|
|
QtWidgets.QMessageBox.StandardButton.No
|
|
)
|
|
if buttonReply == QtWidgets.QMessageBox.StandardButton.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.icons, self.ui.plugins)
|
|
if plugin is not None:
|
|
item.setData(COLUMN_NAME, QtCore.Qt.ItemDataRole.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.short_str(),
|
|
item.new_version.short_str())
|
|
else:
|
|
version = plugin.version.short_str()
|
|
|
|
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' if plugin.is_user_installed else 'hide')
|
|
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, plugin.version)
|
|
|
|
return item
|
|
|
|
def save(self):
|
|
config = get_config()
|
|
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")
|
|
version_str = item.new_version.short_str()
|
|
text.append("<b>{0}: {1}</b>".format(label, version_str))
|
|
if plugin.description:
|
|
text.append(plugin.description + "<hr width='90%'/>")
|
|
infos = [
|
|
(_("Name"), escape(plugin.name)),
|
|
(_("Authors"), self.link_authors(plugin.author)),
|
|
(_("License"), plugin.license),
|
|
(_("Files"), escape(plugin.files_list)),
|
|
(_("User Guide"), self.link_user_guide(plugin.user_guide_url)),
|
|
]
|
|
for label, value in infos:
|
|
if value:
|
|
text.append("<b>{0}:</b> {1}".format(label, value))
|
|
self.ui.details.setText("<p>{0}</p>".format("<br/>\n".join(text)))
|
|
|
|
@staticmethod
|
|
def link_authors(authors):
|
|
formatted_authors = []
|
|
re_author = re.compile(r"(?P<author>.*?)\s*<(?P<email>.*?@.*?)>")
|
|
for author in authors.split(','):
|
|
author = author.strip()
|
|
match = re_author.fullmatch(author)
|
|
if match:
|
|
author_str = '<a href="mailto:{email}">{author}</a>'.format(
|
|
email=escape(match['email']),
|
|
author=escape(match['author']),
|
|
)
|
|
formatted_authors.append(author_str)
|
|
else:
|
|
formatted_authors.append(escape(author))
|
|
return ', '.join(formatted_authors)
|
|
|
|
@staticmethod
|
|
def link_user_guide(user_guide):
|
|
if user_guide:
|
|
user_guide = '<a href="{url}">{url}</a>'.format(
|
|
url=escape(user_guide)
|
|
)
|
|
return user_guide
|
|
|
|
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_url(
|
|
url=PLUGINS_API['urls']['download'],
|
|
handler=partial(self.download_handler, update, plugin=plugin),
|
|
parse_response_type=None,
|
|
priority=True,
|
|
important=True,
|
|
unencoded_queryargs={'id': plugin.module_name, 'version': plugin.version.short_str()},
|
|
)
|
|
|
|
def download_handler(self, update, response, reply, error, plugin):
|
|
if self.deleted:
|
|
return
|
|
if error:
|
|
params = {'plugin': plugin.module_name}
|
|
msgbox = QtWidgets.QMessageBox(self)
|
|
msgbox.setText(_('The plugin "%(plugin)s" could not be downloaded.') % params)
|
|
msgbox.setInformativeText(_("Please try again later."))
|
|
msgbox.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok)
|
|
msgbox.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Ok)
|
|
msgbox.exec()
|
|
log.error('Error occurred while trying to download the plugin: "%(plugin)s"', params)
|
|
return
|
|
|
|
self.manager.install_plugin(
|
|
None,
|
|
update=update,
|
|
plugin_name=plugin.module_name,
|
|
plugin_data=response,
|
|
)
|
|
|
|
@staticmethod
|
|
def open_plugin_dir():
|
|
open_local_path(USER_PLUGIN_DIR)
|
|
|
|
def mimeTypes(self):
|
|
return ['text/uri-list']
|
|
|
|
def dragEnterEvent(self, event):
|
|
event.setDropAction(QtCore.Qt.DropAction.CopyAction)
|
|
event.accept()
|
|
|
|
def dragMoveEvent(self, event):
|
|
event.setDropAction(QtCore.Qt.DropAction.CopyAction)
|
|
event.accept()
|
|
|
|
def dropEvent(self, event):
|
|
if event.proposedAction() == QtCore.Qt.DropAction.IgnoreAction:
|
|
event.acceptProposedAction()
|
|
return
|
|
|
|
for path in (os.path.normpath(u.toLocalFile()) for u in event.mimeData().urls()):
|
|
self.manager.install_plugin(path)
|
|
|
|
event.setDropAction(QtCore.Qt.DropAction.CopyAction)
|
|
event.accept()
|
|
|
|
|
|
register_options_page(PluginsOptionsPage)
|