From 15773086e5d2e1017b344f50c3f9345c35d350d4 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Tue, 1 Aug 2023 18:02:46 +0200 Subject: [PATCH] qt: initial trustedcoin wizard pages --- electrum/gui/qt/__init__.py | 3 + electrum/gui/qt/wizard/server_connect.py | 7 +- electrum/gui/qt/wizard/wallet.py | 19 ++-- electrum/gui/qt/wizard/wizard.py | 23 ++-- electrum/plugins/trustedcoin/qt.py | 128 ++++++++++++++++++++++- 5 files changed, 162 insertions(+), 18 deletions(-) diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index c84721f64..cd2118f24 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -148,6 +148,9 @@ class ElectrumGui(BaseElectrumGui, Logger): self._default_qtstylesheet = self.app.styleSheet() self.reload_app_stylesheet() + # always load 2fa + self.plugins.load_plugin('trustedcoin') + run_hook('init_qt', self) def _init_tray(self): diff --git a/electrum/gui/qt/wizard/server_connect.py b/electrum/gui/qt/wizard/server_connect.py index 947fca536..030f7a6ca 100644 --- a/electrum/gui/qt/wizard/server_connect.py +++ b/electrum/gui/qt/wizard/server_connect.py @@ -10,13 +10,16 @@ from ..util import ChoicesLayout if TYPE_CHECKING: from electrum.simple_config import SimpleConfig + from electrum.plugin import Plugins + from electrum.daemon import Daemon + from electrum.gui.qt import QElectrumApplication class QEServerConnectWizard(ServerConnectWizard, QEAbstractWizard): - def __init__(self, config: 'SimpleConfig', app: QApplication, daemon, parent=None): + def __init__(self, config: 'SimpleConfig', app: 'QElectrumApplication', plugins: 'Plugins', daemon: 'Daemon', parent=None): ServerConnectWizard.__init__(self, daemon) - QEAbstractWizard.__init__(self, config, app, parent) + QEAbstractWizard.__init__(self, config, app, plugins, daemon) self._daemon = daemon # attach view names diff --git a/electrum/gui/qt/wizard/wallet.py b/electrum/gui/qt/wizard/wallet.py index 235c17d9f..9b20051b7 100644 --- a/electrum/gui/qt/wizard/wallet.py +++ b/electrum/gui/qt/wizard/wallet.py @@ -10,6 +10,7 @@ from electrum.bip32 import is_bip32_derivation, BIP32Node, normalize_bip32_deriv from electrum.daemon import Daemon from electrum.i18n import _ from electrum.keystore import bip44_derivation, bip39_to_seed, purpose48_derivation +from electrum.plugin import run_hook from electrum.storage import StorageReadWriteError from electrum.util import WalletFileException, get_new_wallet_name from electrum.wallet import wallet_types @@ -24,6 +25,9 @@ from ..util import ChoicesLayout, PasswordLineEdit, char_width_in_lineedit, WWLa if TYPE_CHECKING: from electrum.simple_config import SimpleConfig + from electrum.plugin import Plugins + from electrum.daemon import Daemon + from electrum.gui.qt import QElectrumApplication WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\n\n' + _('A few examples') + ':\n' + @@ -35,10 +39,10 @@ WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard): _logger = get_logger(__name__) - def __init__(self, config: 'SimpleConfig', app: QApplication, daemon: Daemon, path, parent=None): + def __init__(self, config: 'SimpleConfig', app: 'QElectrumApplication', plugins: 'Plugins', daemon: Daemon, path, parent=None): NewWalletWizard.__init__(self, daemon) - QEAbstractWizard.__init__(self, config, app, parent) - self._daemon = daemon + QEAbstractWizard.__init__(self, config, app, plugins, daemon) + self._daemon = daemon # TODO: dedupe self._path = path # attach gui classes to views @@ -63,15 +67,15 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard): # modify default flow, insert seed extension entry/confirm as separate views self.navmap_merge({ 'create_seed': { - 'next': lambda d: 'create_ext' if d['seed_extend'] else 'confirm_seed' + 'next': lambda d: 'create_ext' if self.wants_ext(d) else 'confirm_seed' }, 'create_ext': { 'next': 'confirm_seed', 'gui': WCEnterExt }, 'confirm_seed': { - 'next': lambda d: 'confirm_ext' if d['seed_extend'] else self.on_have_or_confirm_seed(d), - 'accept': lambda d: None if d['seed_extend'] else self.maybe_master_pubkey(d) + 'next': lambda d: 'confirm_ext' if self.wants_ext(d) else self.on_have_or_confirm_seed(d), + 'accept': lambda d: None if self.wants_ext(d) else self.maybe_master_pubkey(d) }, 'confirm_ext': { 'next': self.on_have_or_confirm_seed, @@ -100,6 +104,9 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard): } }) + run_hook('init_wallet_wizard', self) + + @property def path(self): return self._path diff --git a/electrum/gui/qt/wizard/wizard.py b/electrum/gui/qt/wizard/wizard.py index eea968a65..62e356611 100644 --- a/electrum/gui/qt/wizard/wizard.py +++ b/electrum/gui/qt/wizard/wizard.py @@ -12,16 +12,20 @@ from electrum.logging import get_logger if TYPE_CHECKING: from electrum.simple_config import SimpleConfig + from electrum.plugin import Plugins + from electrum.daemon import Daemon + from electrum.gui.qt import QElectrumApplication class QEAbstractWizard(QDialog): _logger = get_logger(__name__) # def __init__(self, config: 'SimpleConfig', app: QApplication, plugins: 'Plugins', *, gui_object: 'ElectrumGui'): - def __init__(self, config: 'SimpleConfig', app: QApplication, daemon): + def __init__(self, config: 'SimpleConfig', app: 'QElectrumApplication', plugins: 'Plugins', daemon: 'Daemon'): QDialog.__init__(self, None) self.app = app self.config = config + self.plugins = plugins # self.gui_thread = gui_object.gui_thread self.setMinimumSize(600, 400) @@ -91,10 +95,12 @@ class QEAbstractWizard(QDialog): view = self.start_wizard() self.load_next_component(view) - def load_next_component(self, view, wdata=None): + def load_next_component(self, view, wdata=None, params=None): if wdata is None: wdata = {} - + if params is None: + params = {} + comp = self.view_to_component(view) try: page = comp(self.main_widget, self) @@ -102,6 +108,7 @@ class QEAbstractWizard(QDialog): self._logger.error(f'not a class: {comp!r}') raise e page.wizard_data = wdata + page.params = params page.updated.connect(self.on_page_updated) self._logger.debug(f'{page!r}') @@ -134,6 +141,9 @@ class QEAbstractWizard(QDialog): self.next_button.setEnabled(page.valid) self.main_widget.setVisible(not page.busy) self.please_wait.setVisible(page.busy) + icon = page.params.get('icon', icon_path('electrum.png')) + if icon != self.icon_filename: + self.set_icon(icon) def on_back_button_clicked(self): if self.can_go_back(): @@ -153,7 +163,7 @@ class QEAbstractWizard(QDialog): self.accept() else: next = self.submit(wd) - self.load_next_component(next['view'], next['wizard_data']) + self.load_next_component(next.view, next.wizard_data, next.params) def start_wizard(self) -> str: self.start() @@ -165,10 +175,7 @@ class QEAbstractWizard(QDialog): def submit(self, wizard_data) -> dict: wdata = wizard_data.copy() view = self.resolve_next(self._current.view, wdata) - return { - 'view': view.view, - 'wizard_data': view.wizard_data - } + return view def prev(self) -> dict: viewstate = self.resolve_prev() diff --git a/electrum/plugins/trustedcoin/qt.py b/electrum/plugins/trustedcoin/qt.py index 037e30f1e..4ba05b2c1 100644 --- a/electrum/plugins/trustedcoin/qt.py +++ b/electrum/plugins/trustedcoin/qt.py @@ -35,7 +35,7 @@ from PyQt5.QtWidgets import (QTextEdit, QVBoxLayout, QLabel, QGridLayout, QHBoxL QRadioButton, QCheckBox, QLineEdit) from electrum.gui.qt.util import (read_QIcon, WindowModalDialog, WaitingDialog, OkButton, - CancelButton, Buttons, icon_path, WWLabel, CloseButton) + CancelButton, Buttons, icon_path, WWLabel, CloseButton, ChoicesLayout) from electrum.gui.qt.qrcodewidget import QRCodeWidget from electrum.gui.qt.amountedit import AmountEdit from electrum.gui.qt.main_window import StatusBarButton @@ -46,7 +46,9 @@ from electrum.util import is_valid_email from electrum.logging import Logger from electrum.base_wizard import GoBack, UserCancelled -from .trustedcoin import TrustedCoinPlugin, server +from .trustedcoin import TrustedCoinPlugin, server, DISCLAIMER +from ...gui.qt.wizard.wallet import WCCreateSeed, WCConfirmSeed, WCHaveSeed, WCEnterExt, WCConfirmExt +from ...gui.qt.wizard.wizard import WizardComponent if TYPE_CHECKING: from electrum.gui.qt.main_window import ElectrumWindow @@ -327,3 +329,125 @@ class Plugin(TrustedCoinPlugin): cb_lost.toggled.connect(set_enabled) window.exec_layout(vbox, next_enabled=False, raise_on_cancel=False) self.check_otp(window, short_id, otp_secret, xpub3, pw.get_amount(), cb_lost.isChecked()) + + @hook + def init_qt(self, gui: 'ElectrumGui'): + pass + + @hook + def init_wallet_wizard(self, wizard: 'QEWalletWizard'): + self.extend_wizard(wizard) + + def extend_wizard(self, wizard): + # wizard = self._app.daemon.newWalletWizard + # self.logger.debug(repr(wizard)) + # TODO: move non-gui parts to base plugin + views = { + 'trustedcoin_start': { + 'gui': WCDisclaimer, + 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'next': 'trustedcoin_choose_seed' + }, + 'trustedcoin_choose_seed': { + 'gui': WCChooseSeed, + 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'next': lambda d: 'trustedcoin_create_seed' if d['keystore_type'] == 'createseed' + else 'trustedcoin_have_seed' + }, + 'trustedcoin_create_seed': { + 'gui': WCCreateSeed, + 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'next': 'trustedcoin_confirm_seed' + }, + 'trustedcoin_confirm_seed': { + 'gui': WCConfirmSeed, + 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'next': 'trustedcoin_tos_email' + }, + 'trustedcoin_have_seed': { + 'gui': WCHaveSeed, + 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'next': 'trustedcoin_keep_disable' + }, + # 'trustedcoin_keep_disable': { + # 'gui': '../../../../plugins/trustedcoin/qml/KeepDisable', + # 'next': lambda d: 'trustedcoin_tos_email' if d['trustedcoin_keepordisable'] != 'disable' + # else 'wallet_password', + # 'accept': self.recovery_disable, + # 'last': lambda d: wizard.is_single_password() and d['trustedcoin_keepordisable'] == 'disable' + # }, + # 'trustedcoin_tos_email': { + # 'gui': '../../../../plugins/trustedcoin/qml/Terms', + # 'next': 'trustedcoin_show_confirm_otp' + # }, + # 'trustedcoin_show_confirm_otp': { + # 'gui': '../../../../plugins/trustedcoin/qml/ShowConfirmOTP', + # 'accept': self.on_accept_otp_secret, + # 'next': 'wallet_password', + # 'last': lambda d: wizard.is_single_password() + # } + } + wizard.navmap_merge(views) + + # modify default flow, insert seed extension entry/confirm as separate views + ext = { + 'trustedcoin_create_seed': { + 'next': lambda d: 'trustedcoin_create_ext' if wizard.wants_ext(d) else 'trustedcoin_confirm_seed' + }, + 'trustedcoin_create_ext': { + 'gui': WCEnterExt, + 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'next': 'trustedcoin_confirm_seed', + }, + 'trustedcoin_confirm_seed': { + 'next': lambda d: 'trustedcoin_confirm_ext' if wizard.wants_ext(d) else 'trustedcoin_tos_email' + }, + 'trustedcoin_confirm_ext': { + 'gui': WCConfirmExt, + 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'next': 'trustedcoin_tos_email', + }, + 'trustedcoin_have_seed': { + 'next': lambda d: 'trustedcoin_have_ext' if wizard.wants_ext(d) else 'trustedcoin_keep_disable' + }, + 'trustedcoin_have_ext': { + 'gui': WCEnterExt, + 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'next': 'trustedcoin_keep_disable', + }, + } + wizard.navmap_merge(ext) + + +class WCDisclaimer(WizardComponent): + def __init__(self, parent, wizard): + WizardComponent.__init__(self, parent, wizard, title=_('Disclaimer')) + + self.layout().addWidget(WWLabel('\n\n'.join(DISCLAIMER))) + self.layout().addStretch(1) + + self._valid = True + + def apply(self): + pass + + +class WCChooseSeed(WizardComponent): + def __init__(self, parent, wizard): + WizardComponent.__init__(self, parent, wizard, title=_('Create or restore')) + message = _('Do you want to create a new seed, or restore a wallet using an existing seed?') + choices = [ + ('createseed', _('Create a new seed')), + ('haveseed', _('I already have a seed')), + ] + + self.c_values = [x[0] for x in choices] + c_titles = [x[1] for x in choices] + self.clayout = ChoicesLayout(message, c_titles) + self.layout().addLayout(self.clayout.layout()) + self.layout().addStretch(1) + + self._valid = True + + def apply(self): + self.wizard_data['keystore_type'] = self.c_values[self.clayout.selected_index()]