From 7c6ff6757c9d5055de9d47bb047eda71a2db8948 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 2 Oct 2024 11:16:59 +0200 Subject: [PATCH] plugins dialog: show description and enable buttons in the same dialog --- electrum/gui/qt/plugins_dialog.py | 158 +++++++++++++++++++----------- electrum/plugin.py | 4 + 2 files changed, 107 insertions(+), 55 deletions(-) diff --git a/electrum/gui/qt/plugins_dialog.py b/electrum/gui/qt/plugins_dialog.py index a88b8b1cf..5ac1b04dc 100644 --- a/electrum/gui/qt/plugins_dialog.py +++ b/electrum/gui/qt/plugins_dialog.py @@ -1,20 +1,87 @@ from typing import TYPE_CHECKING, Optional from functools import partial -from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton, QComboBox, QLineEdit, QSpacerItem, QWidget, QHBoxLayout, QScrollArea, QCheckBox +from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton, QComboBox, QLineEdit, QSpacerItem, QWidget, QHBoxLayout, QScrollArea, QCheckBox, QFormLayout from electrum.i18n import _ from electrum.gui import messages from electrum.plugin import run_hook, BasePlugin from . import util -from .util import WindowModalDialog, Buttons, CloseButton, HelpButton +from .util import WindowModalDialog, Buttons, CloseButton, HelpButton, WWLabel if TYPE_CHECKING: from .main_window import ElectrumWindow +class PluginDialog(WindowModalDialog): + + def __init__(self, name, metadata, cb: 'QCheckBox', window: 'ElectrumWindow', index:int): + display_name = metadata.get('display_name', '') + author = metadata.get('author', '') + description = metadata.get('description', '') + requires = metadata.get('requires') + version = metadata.get('version', 'n/a') + + WindowModalDialog.__init__(self, window, 'Plugin') + self.setMinimumSize(400,250) + self.index = index + self.window = window + self.metadata = metadata + self.plugins = self.window.plugins + self.name = name + self.cb = cb + p = self.plugins.get(name) # is installed + vbox = QVBoxLayout(self) + form = QFormLayout(None) + form.addRow(QLabel(_('Name') + ':'), QLabel(display_name)) + form.addRow(QLabel(_('Author') + ':'), QLabel(author)) + form.addRow(QLabel(_('Description') + ':'), WWLabel(description)) + form.addRow(QLabel(_('Version') + ':'), QLabel(version)) + if requires: + msg = '\n'.join(map(lambda x: x[1], requires)) + form.addRow(QLabel(_('Requires') + ':'), WWLabel(msg)) + vbox.addLayout(form) + if name in self.plugins.internal_plugin_metadata: + text = _('Disable') if p else _('Enable') + else: + text = _('Remove') if p else _('Install') + toggle_button = QPushButton(text) + toggle_button.clicked.connect(partial(self.do_toggle, toggle_button, name)) + close_button = CloseButton(self) + close_button.setText(_('Cancel')) + buttons = [toggle_button, close_button] + vbox.addLayout(Buttons(*buttons)) + + def do_toggle(self, button, name): + button.setEnabled(False) + if name in self.plugins.internal_plugin_metadata: + p = self.plugins.toggle(name) + self.cb.setChecked(bool(p)) + else: + p = self.plugins.get(name) + if not p: + #if not self.window.window.question("Install plugin '%s'?"%name): + # return + coro = self.plugins.download_external_plugin(name) + def on_success(x): + self.plugins.enable(name) + p = self.plugins.get(name) + self.cb.setChecked(bool(p)) + self.window.window.run_coroutine_from_thread(coro, "Downloading '%s' "%name, on_result=on_success) + else: + #if not self.window.window.question("Remove plugin '%s'?"%name): + # return + self.plugins.disable(name) + self.cb.setChecked(False) + self.plugins.remove_external_plugin(name) + + self.close() + self.window.enable_settings_widget(name, self.index) + # note: all enabled plugins will receive this hook: + run_hook('init_qt', self.window.window.gui_object) + class PluginsDialog(WindowModalDialog): @@ -23,27 +90,24 @@ class PluginsDialog(WindowModalDialog): self.window = window self.wallet = self.window.wallet self.config = window.config - self.plugins = self.window.gui_object.plugins + self.settings_widgets = {} vbox = QVBoxLayout(self) - - # plugins scroll = QScrollArea() scroll.setEnabled(True) scroll.setWidgetResizable(True) scroll.setMinimumSize(400,250) - self.scroll_w = QWidget() - scroll.setWidget(self.scroll_w) - vbox.addWidget(scroll) - - vbox.addLayout(Buttons(CloseButton(self))) - self.settings_widgets = {} + scroll_w = QWidget() + scroll.setWidget(scroll_w) self.grid = QGridLayout() self.grid.setColumnStretch(0,1) - self.scroll_w.setLayout(self.grid) + scroll_w.setLayout(self.grid) + vbox.addWidget(scroll) + vbox.addLayout(Buttons(CloseButton(self))) self.show_list() - def enable_settings_widget(self, p: Optional['BasePlugin'], name: str, i: int): + def enable_settings_widget(self, name: str, i: int): + p = self.plugins.get(name) widget = self.settings_widgets.get(name) # type: Optional[QWidget] if widget and not p: # plugin got disabled, rm widget @@ -55,50 +119,34 @@ class PluginsDialog(WindowModalDialog): widget = self.settings_widgets[name] = p.settings_widget(self) self.grid.addWidget(widget, i, 1) - def do_toggle(self, cb, name, i): - if self.plugins.requires_download(name): - cb.setChecked(False) - self.download_plugin_dialog(cb, name, i) - return - p = self.plugins.toggle(name) - cb.setChecked(bool(p)) - self.enable_settings_widget(p, name, i) - # note: all enabled plugins will receive this hook: - run_hook('init_qt', self.window.gui_object) - - def download_plugin_dialog(self, cb, name, i): - import asyncio - if not self.window.question("Download plugin '%s'?"%name): - return - coro = self.plugins.download_external_plugin(name) - def on_success(x): - self.do_toggle(cb, name, i) - self.window.run_coroutine_dialog(coro, "Downloading '%s' "%name, on_result=on_success, on_cancelled=None) - def show_list(self): - descriptions = sorted(self.plugins.descriptions.items()) + descriptions = self.plugins.descriptions + descriptions = sorted(descriptions.items()) + grid = self.grid i = 0 - for name, descr in descriptions: + for name, metadata in descriptions: i += 1 p = self.plugins.get(name) - if descr.get('registers_keystore'): + if metadata.get('registers_keystore'): continue - try: - cb = QCheckBox(descr['display_name']) - plugin_is_loaded = p is not None - cb_enabled = (not plugin_is_loaded and self.plugins.is_available(name, self.wallet) - or plugin_is_loaded and p.can_user_disable()) - cb.setEnabled(cb_enabled) - cb.setChecked(plugin_is_loaded and p.is_enabled()) - self.grid.addWidget(cb, i, 0) - self.enable_settings_widget(p, name, i) - cb.clicked.connect(partial(self.do_toggle, cb, name, i)) - msg = descr['description'] - if descr.get('requires'): - msg += '\n\n' + _('Requires') + ':\n' + '\n'.join(map(lambda x: x[1], descr.get('requires'))) - self.grid.addWidget(HelpButton(msg), i, 2) - except Exception: - self.window.logger.exception(f"cannot display plugin {name}") - - self.grid.setRowStretch(len(descriptions), 1) - + display_name = metadata.get('display_name') + if not display_name: + continue + #try: + cb = QCheckBox(display_name) + plugin_is_loaded = p is not None + cb_enabled = (not plugin_is_loaded and self.plugins.is_available(name, self.wallet) + or plugin_is_loaded and p.can_user_disable()) + cb.setEnabled(cb_enabled) + cb.setChecked(plugin_is_loaded and p.is_enabled()) + grid.addWidget(cb, i, 0) + self.enable_settings_widget(name, i) + cb.clicked.connect(partial(self.show_plugin_dialog, name, metadata, cb, i)) + + #grid.setRowStretch(len(descriptions), 1) + + def show_plugin_dialog(self, name, metadata, cb, i): + p = self.plugins.get(name) + cb.setChecked(p is not None and p.is_enabled()) + d = PluginDialog(name, metadata, cb, self, i) + d.exec() diff --git a/electrum/plugin.py b/electrum/plugin.py index ab5b1d62d..817242664 100644 --- a/electrum/plugin.py +++ b/electrum/plugin.py @@ -178,6 +178,10 @@ class Plugins(DaemonThread): os.unlink(filename) raise Exception(f"wrong plugin hash {name}") + def remove_external_plugin(self, name): + filename = self.external_plugin_path(name) + os.unlink(filename) + def load_external_plugin(self, name): if name in self.plugins: return self.plugins[name]