Browse Source

Merge pull request #9245 from accumulator/qt_refactor_layouts_to_widgets

qt: refactor SeedLayout/KeysLayout to SeedWidget/KeysWidget, remove t…
master
accumulator 1 year ago committed by GitHub
parent
commit
d19f1f4790
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 214
      electrum/gui/qt/seed_dialog.py
  2. 144
      electrum/gui/qt/wizard/wallet.py

214
electrum/gui/qt/seed_dialog.py

@ -69,66 +69,10 @@ def seed_warning_msg(seed):
]).format(len(seed.split()))
class SeedLayout(QVBoxLayout):
class SeedWidget(QWidget):
updated = pyqtSignal()
def seed_options(self):
dialog = QDialog()
dialog.setWindowTitle(_("Seed Options"))
vbox = QVBoxLayout(dialog)
seed_types = [
(value, title) for value, title in (
('electrum', _('Electrum')),
('bip39', _('BIP39 seed')),
('slip39', _('SLIP39 seed')),
)
if value in self.options or value == 'electrum'
]
if 'ext' in self.options:
cb_ext = QCheckBox(_('Extend this seed with custom words'))
cb_ext.setChecked(self.is_ext)
vbox.addWidget(cb_ext)
if len(seed_types) >= 2:
def on_selected(idx):
self.seed_type = seed_type_choice.selected_key
self.is_seed = (lambda x: bool(x)) if self.seed_type != 'electrum' else self.saved_is_seed
self.slip39_current_mnemonic_invalid = None
self.seed_status.setText('')
self.on_edit()
if self.seed_type == 'bip39':
msg = ' '.join([
'<b>' + _('Warning') + ':</b> ',
_('BIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'),
_('However, we do not generate BIP39 seeds, because they do not meet our safety standard.'),
_('BIP39 seeds do not include a version number, which compromises compatibility with future software.'),
_('We do not guarantee that BIP39 imports will always be supported in Electrum.'),
])
elif self.seed_type == 'slip39':
msg = ' '.join([
'<b>' + _('Warning') + ':</b> ',
_('SLIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'),
_('However, we do not generate SLIP39 seeds.'),
])
else:
msg = ''
self.update_share_buttons()
self.initialize_completer()
self.seed_warning.setText(msg)
seed_type_choice = ChoiceWidget(message=_('Seed type'), choices=seed_types, selected=self.seed_type)
seed_type_choice.itemSelected.connect(on_selected)
vbox.addWidget(seed_type_choice)
vbox.addLayout(Buttons(OkButton(dialog)))
if not dialog.exec():
return None
self.is_ext = cb_ext.isChecked() if 'ext' in self.options else False
self.seed_type = seed_type_choice.selected_key if len(seed_types) >= 2 else 'electrum'
self.updated.emit()
validChanged = pyqtSignal([bool], arguments=['valid'])
def __init__(
self,
@ -137,20 +81,38 @@ class SeedLayout(QVBoxLayout):
icon=True,
msg=None,
options=None,
is_seed=None,
is_seed=None, # only used for electrum seeds
passphrase=None,
parent=None,
for_seed_words=True,
*,
config: 'SimpleConfig',
):
QVBoxLayout.__init__(self)
self.parent = parent
QWidget.__init__(self, parent)
vbox = QVBoxLayout()
self.setLayout(vbox)
self.options = options
self.config = config
self.seed_type = 'electrum'
if options:
self.seed_types = [
(value, title) for value, title in (
('electrum', _('Electrum')),
('bip39', _('BIP39 seed')),
('slip39', _('SLIP39 seed')),
)
if value in self.options
]
assert len(self.seed_types)
self.seed_type = self.seed_types[0][0]
else:
self.seed_type = 'electrum'
self.is_seed = is_seed
if title:
self.addWidget(WWLabel(title))
vbox.addWidget(WWLabel(title))
if seed: # "read only", we already have the text
if for_seed_words:
self.seed_e = ButtonsTextEdit()
@ -162,8 +124,6 @@ class SeedLayout(QVBoxLayout):
assert for_seed_words
self.seed_e = CompletionTextEdit()
self.seed_e.setTabChangesFocus(False) # so that tab auto-completes
self.is_seed = is_seed
self.saved_is_seed = self.is_seed
self.seed_e.textChanged.connect(self.on_edit)
self.initialize_completer()
@ -176,7 +136,7 @@ class SeedLayout(QVBoxLayout):
logo.setMaximumWidth(60)
hbox.addWidget(logo)
hbox.addWidget(self.seed_e)
self.addLayout(hbox)
vbox.addLayout(hbox)
hbox = QHBoxLayout()
hbox.addStretch(1)
self.seed_type_label = QLabel('')
@ -187,7 +147,7 @@ class SeedLayout(QVBoxLayout):
if options:
opt_button = EnterButton(_('Options'), self.seed_options)
hbox.addWidget(opt_button)
self.addLayout(hbox)
vbox.addLayout(hbox)
if passphrase:
hbox = QHBoxLayout()
passphrase_e = QLineEdit()
@ -195,7 +155,7 @@ class SeedLayout(QVBoxLayout):
passphrase_e.setReadOnly(True)
hbox.addWidget(QLabel(_("Your seed extension is") + ':'))
hbox.addWidget(passphrase_e)
self.addLayout(hbox)
vbox.addLayout(hbox)
# slip39 shares
self.slip39_mnemonic_index = 0
@ -211,15 +171,75 @@ class SeedLayout(QVBoxLayout):
self.next_share_btn.clicked.connect(self.on_next_share)
hbox.addWidget(self.next_share_btn)
self.update_share_buttons()
self.addLayout(hbox)
vbox.addLayout(hbox)
self.addStretch(1)
vbox.addStretch(1)
self.seed_status = WWLabel('')
self.addWidget(self.seed_status)
vbox.addWidget(self.seed_status)
self.seed_warning = WWLabel('')
if msg:
self.seed_warning.setText(seed_warning_msg(seed))
self.addWidget(self.seed_warning)
else:
self.update_seed_warning()
vbox.addWidget(self.seed_warning)
def seed_options(self):
dialog = QDialog()
dialog.setWindowTitle(_("Seed Options"))
vbox = QVBoxLayout(dialog)
if 'ext' in self.options:
cb_ext = QCheckBox(_('Extend this seed with custom words'))
cb_ext.setChecked(self.is_ext)
vbox.addWidget(cb_ext)
def on_selected(idx):
self.seed_type = seed_type_choice.selected_key
self.slip39_current_mnemonic_invalid = None
self.seed_status.setText('')
self.update_seed_warning()
self.on_edit()
self.update_share_buttons()
self.initialize_completer()
if len(self.seed_types) > 1:
seed_type_choice = ChoiceWidget(message=_('Seed type'), choices=self.seed_types, selected=self.seed_type)
seed_type_choice.itemSelected.connect(on_selected)
vbox.addWidget(seed_type_choice)
vbox.addLayout(Buttons(OkButton(dialog)))
if not dialog.exec():
return None
if 'ext' in self.options:
self.is_ext = cb_ext.isChecked()
if len(self.seed_types) > 1:
self.seed_type = seed_type_choice.selected_key
self.update_seed_warning()
self.updated.emit()
def update_seed_warning(self):
if self.seed_type == 'bip39':
msg = ' '.join([
'<b>' + _('Warning') + ':</b> ',
_('BIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'),
_('However, we do not generate BIP39 seeds, because they do not meet our safety standard.'),
_('BIP39 seeds do not include a version number, which compromises compatibility with future software.'),
_('We do not guarantee that BIP39 imports will always be supported in Electrum.'),
])
elif self.seed_type == 'slip39':
msg = ' '.join([
'<b>' + _('Warning') + ':</b> ',
_('SLIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'),
_('However, we do not generate SLIP39 seeds.'),
])
else:
msg = ''
self.seed_warning.setText(msg)
def initialize_completer(self):
if self.seed_type != 'slip39':
@ -261,12 +281,12 @@ class SeedLayout(QVBoxLayout):
def on_edit(self):
s = ' '.join(self.get_seed_words())
b = self.is_seed(s)
if self.seed_type == 'bip39':
from electrum.keystore import bip39_is_checksum_valid
is_checksum, is_wordlist = bip39_is_checksum_valid(s)
label = ''
if bool(s):
valid = bool(s)
if valid:
label = ('' if is_checksum else _('BIP39 checksum failed')) if is_wordlist else _('Unknown BIP39 wordlist')
elif self.seed_type == 'slip39':
self.slip39_mnemonics[self.slip39_mnemonic_index] = s
@ -287,15 +307,13 @@ class SeedLayout(QVBoxLayout):
self.seed_status.setText(seed_status)
self.slip39_current_mnemonic_invalid = current_mnemonic_invalid
b = self.slip39_seed is not None
valid = self.slip39_seed is not None
self.update_share_buttons()
else:
valid = self.is_seed(s)
t = calc_seed_type(s)
label = _('Seed Type') + ': ' + t if t else ''
if t and not b: # electrum seed, but does not conform to dialog rules
# FIXME we should just accept any electrum seed and "redirect" the wizard automatically.
# i.e. if user selected wallet_type=="standard" but entered a 2fa seed, accept and redirect
# if user selected wallet_type=="2fa" but entered a std electrum seed, accept and redirect
if t and not valid: # electrum seed, but does not conform to dialog rules
wiztype_fullname = _('Wallet with two-factor authentication') if is_any_2fa_seed_type(t) else _("Standard wallet")
msg = ' '.join([
'<b>' + _('Warning') + ':</b> ',
@ -307,7 +325,7 @@ class SeedLayout(QVBoxLayout):
self.seed_warning.setText("")
self.seed_type_label.setText(label)
self.parent.next_button.setEnabled(b)
self.validChanged.emit(valid)
# disable suggestions if user already typed an unknown word
for word in self.get_seed_words()[:-1]:
@ -354,7 +372,10 @@ class SeedLayout(QVBoxLayout):
self.slip39_current_mnemonic_invalid = None
class KeysLayout(QVBoxLayout):
class KeysWidget(QWidget):
validChanged = pyqtSignal([bool], arguments=['valid'])
def __init__(
self,
parent=None,
@ -364,29 +385,28 @@ class KeysLayout(QVBoxLayout):
*,
config: 'SimpleConfig',
):
QVBoxLayout.__init__(self)
self.parent = parent
QWidget.__init__(self, parent)
vbox = QVBoxLayout()
self.setLayout(vbox)
self.is_valid = is_valid
self.text_e = ScanQRTextEdit(allow_multi=allow_multi, config=config)
self.text_e.textChanged.connect(self.on_edit)
if isinstance(header_layout, str):
self.addWidget(WWLabel(header_layout))
vbox.addWidget(WWLabel(header_layout))
else:
self.addLayout(header_layout)
self.addWidget(self.text_e)
vbox.addLayout(header_layout)
vbox.addWidget(self.text_e)
def get_text(self):
return self.text_e.text()
def on_edit(self):
valid = False
try:
valid = self.is_valid(self.get_text())
except Exception as e:
self.parent.next_button.setToolTip(f'{_("Error")}: {str(e)}')
else:
self.parent.next_button.setToolTip('')
self.parent.next_button.setEnabled(valid)
valid = False
self.validChanged.emit(valid)
class SeedDialog(WindowModalDialog):
@ -395,13 +415,7 @@ class SeedDialog(WindowModalDialog):
WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
self.setMinimumWidth(400)
vbox = QVBoxLayout(self)
title = _("Your wallet generation seed is:")
slayout = SeedLayout(
title=title,
seed=seed,
msg=True,
passphrase=passphrase,
config=config,
)
vbox.addLayout(slayout)
title = _("Your wallet generation seed is:")
seed_widget = SeedWidget(title=title, seed=seed, msg=True, passphrase=passphrase, config=config)
vbox.addWidget(seed_widget)
vbox.addLayout(Buttons(CloseButton(self)))

144
electrum/gui/qt/wizard/wallet.py

@ -27,10 +27,9 @@ from electrum.wizard import NewWalletWizard
from electrum.gui.qt.bip39_recovery_dialog import Bip39RecoveryDialog
from electrum.gui.qt.password_dialog import PasswordLayout, PW_NEW, MSG_ENTER_PASSWORD, PasswordLayoutForHW
from electrum.gui.qt.seed_dialog import SeedLayout, MSG_PASSPHRASE_WARN_ISSUE4566, KeysLayout
from electrum.gui.qt.seed_dialog import SeedWidget, MSG_PASSPHRASE_WARN_ISSUE4566, KeysWidget
from electrum.gui.qt.util import (PasswordLineEdit, char_width_in_lineedit, WWLabel, InfoButton, font_height,
ChoiceWidget, MessageBoxMixin, WindowModalDialog, CancelButton,
Buttons, OkButton, icon_path)
ChoiceWidget, MessageBoxMixin, icon_path)
if TYPE_CHECKING:
from electrum.simple_config import SimpleConfig
@ -437,7 +436,7 @@ class WCCreateSeed(WalletWizardComponent):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Wallet Seed'))
self._busy = True
self.seed_type = 'standard' if self.wizard.config.WIZARD_DONT_CREATE_SEGWIT else 'segwit'
self.slayout = None
self.seed_widget = None
self.seed = None
def on_ready(self):
@ -446,10 +445,10 @@ class WCCreateSeed(WalletWizardComponent):
QTimer.singleShot(1, self.create_seed)
def apply(self):
if self.slayout:
if self.seed_widget:
self.wizard_data['seed'] = self.seed
self.wizard_data['seed_type'] = self.seed_type
self.wizard_data['seed_extend'] = self.slayout.is_ext
self.wizard_data['seed_extend'] = self.seed_widget.is_ext
self.wizard_data['seed_variant'] = 'electrum'
self.wizard_data['seed_extra_words'] = '' # empty default
@ -457,15 +456,15 @@ class WCCreateSeed(WalletWizardComponent):
self.busy = True
self.seed = mnemonic.Mnemonic('en').make_seed(seed_type=self.seed_type)
self.slayout = SeedLayout(
self.seed_widget = SeedWidget(
title=_('Your wallet generation seed is:'),
seed=self.seed,
options=['ext'],
options=['ext', 'electrum'],
msg=True,
parent=self,
config=self.wizard.config,
)
self.layout().addLayout(self.slayout)
self.layout().addWidget(self.seed_widget)
self.layout().addStretch(1)
self.busy = False
self.valid = True
@ -482,19 +481,16 @@ class WCConfirmSeed(WalletWizardComponent):
self.layout().addWidget(WWLabel(message))
# TODO: SeedLayout assumes too much in parent, refactor SeedLayout
# for now, fake parent.next_button.setEnabled
class Hack:
def setEnabled(self2, b):
self.valid = b
self.next_button = Hack()
self.slayout = SeedLayout(
self.seed_widget = SeedWidget(
is_seed=lambda x: x == self.wizard_data['seed'],
parent=self,
config=self.wizard.config,
)
self.layout().addLayout(self.slayout)
def seed_valid_changed(valid):
self.valid = valid
self.seed_widget.validChanged.connect(seed_valid_changed)
self.layout().addWidget(self.seed_widget)
wizard.app.clipboard().clear()
@ -583,37 +579,39 @@ class WCHaveSeed(WalletWizardComponent, Logger):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Enter Seed'))
Logger.__init__(self)
self.slayout = None
self.layout().addWidget(WWLabel(_('Please enter your seed phrase in order to restore your wallet.')))
# TODO: SeedLayout assumes too much in parent, refactor SeedLayout
# for now, fake parent.next_button.setEnabled
class Hack:
def setEnabled(self2, b):
if not b:
self.valid = b
else:
self.validate()
self.next_button = Hack()
self.seed_widget = None
self.can_passphrase = True
def on_ready(self):
options = ['ext'] if self.wizard_data['wallet_type'] == '2fa' else ['ext', 'bip39', 'slip39']
self.slayout = SeedLayout(
options = ['ext', 'electrum', 'bip39', 'slip39']
if self.wizard_data['wallet_type'] == '2fa':
options = ['ext', 'electrum']
else:
if self.params and 'seed_options' in self.params:
options = self.params['seed_options']
self.seed_widget = SeedWidget(
is_seed=self.is_seed,
options=options,
parent=self,
config=self.wizard.config,
)
self.slayout.updated.connect(self.validate)
self.layout().addLayout(self.slayout)
def seed_valid_changed(valid):
if not valid:
self.valid = valid
else:
self.validate()
self.seed_widget.validChanged.connect(seed_valid_changed)
self.seed_widget.updated.connect(self.validate)
self.layout().addWidget(self.seed_widget)
self.layout().addStretch(1)
def is_seed(self, x):
# really only used for electrum seeds. bip39 and slip39 are validated in SeedWidget
t = mnemonic.calc_seed_type(x)
if self.wizard_data['wallet_type'] == 'standard':
return mnemonic.is_seed(x) and not mnemonic.is_any_2fa_seed_type(t)
@ -624,9 +622,9 @@ class WCHaveSeed(WalletWizardComponent, Logger):
return t in ['standard', 'segwit']
def validate(self):
# precond: only call when SeedLayout deems seed a valid seed
seed = self.slayout.get_seed()
seed_variant = self.slayout.seed_type
# precond: only call when SeedWidget deems seed a valid seed
seed = self.seed_widget.get_seed()
seed_variant = self.seed_widget.seed_type
wallet_type = self.wizard_data['wallet_type']
seed_valid, seed_type, validation_message, self.can_passphrase = self.wizard.validate_seed(seed, seed_variant, wallet_type)
@ -646,13 +644,13 @@ class WCHaveSeed(WalletWizardComponent, Logger):
def apply(self):
cosigner_data = self.wizard.current_cosigner(self.wizard_data)
cosigner_data['seed'] = self.slayout.get_seed()
cosigner_data['seed_variant'] = self.slayout.seed_type
if self.slayout.seed_type == 'electrum':
cosigner_data['seed_type'] = mnemonic.calc_seed_type(self.slayout.get_seed())
cosigner_data['seed'] = self.seed_widget.get_seed()
cosigner_data['seed_variant'] = self.seed_widget.seed_type
if self.seed_widget.seed_type == 'electrum':
cosigner_data['seed_type'] = mnemonic.calc_seed_type(self.seed_widget.get_seed())
else:
cosigner_data['seed_type'] = self.slayout.seed_type
cosigner_data['seed_extend'] = self.slayout.is_ext if self.can_passphrase else False
cosigner_data['seed_type'] = self.seed_widget.seed_type
cosigner_data['seed_extend'] = self.seed_widget.is_ext if self.can_passphrase else False
cosigner_data['seed_extra_words'] = '' # empty default
@ -790,13 +788,13 @@ class WCCosignerKeystore(WalletWizardComponent):
# different from old wizard: master public key for sharing is now shown on this page
self.layout().addSpacing(20)
self.layout().addWidget(WWLabel(_('Below is your master public key. Please share it with your cosigners')))
slayout = SeedLayout(
seed_widget = SeedWidget(
self.wizard_data['multisig_master_pubkey'],
icon=False,
for_seed_words=False,
config=self.wizard.config,
)
self.layout().addLayout(slayout)
self.layout().addWidget(seed_widget)
self.layout().addStretch(1)
def apply(self):
@ -811,7 +809,7 @@ class WCHaveMasterKey(WalletWizardComponent):
def __init__(self, parent, wizard):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Create keystore from a master key'))
self.slayout = None
self.keys_widget = None
self.message_create = ' '.join([
_("To create a watching-only wallet, please enter your master public key (xpub/ypub/zpub)."),
@ -827,16 +825,6 @@ class WCHaveMasterKey(WalletWizardComponent):
self.label.setMinimumWidth(400)
self.header_layout.addWidget(self.label)
# TODO: KeysLayout assumes too much in parent, refactor KeysLayout
# for now, fake parent.next_button.setEnabled
class Hack:
def setEnabled(self2, b):
self.valid = b
def setToolTip(self2, b):
pass
self.next_button = Hack()
def on_ready(self):
if self.wizard_data['wallet_type'] == 'standard':
self.label.setText(self.message_create)
@ -860,12 +848,19 @@ class WCHaveMasterKey(WalletWizardComponent):
return True
else:
raise Exception(f"unexpected wallet type: {self.wizard_data['wallet_type']}")
self.slayout = KeysLayout(parent=self, header_layout=self.header_layout, is_valid=is_valid,
allow_multi=False, config=self.wizard.config)
self.layout().addLayout(self.slayout)
self.keys_widget = KeysWidget(parent=self, header_layout=self.header_layout, is_valid=is_valid,
allow_multi=False, config=self.wizard.config)
def key_valid_changed(valid):
self.valid = valid
self.keys_widget.validChanged.connect(key_valid_changed)
self.layout().addWidget(self.keys_widget)
def apply(self):
text = self.slayout.get_text()
text = self.keys_widget.get_text()
cosigner_data = self.wizard.current_cosigner(self.wizard_data)
cosigner_data['master_key'] = text
@ -942,25 +937,20 @@ class WCImport(WalletWizardComponent):
header_layout.addWidget(label)
header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignmentFlag.AlignRight)
# TODO: KeysLayout assumes too much in parent, refactor KeysLayout
# for now, fake parent.next_button.setEnabled
class Hack:
def setEnabled(self2, b):
self.valid = b
def setToolTip(self2, b):
pass
self.next_button = Hack()
def is_valid(x) -> bool:
return keystore.is_address_list(x) or keystore.is_private_key_list(x, raise_on_error=True)
self.slayout = KeysLayout(parent=self, header_layout=header_layout, is_valid=is_valid,
allow_multi=True, config=self.wizard.config)
self.layout().addLayout(self.slayout)
self.keys_widget = KeysWidget(header_layout=header_layout, is_valid=is_valid,
allow_multi=True, config=self.wizard.config)
def key_valid_changed(valid):
self.valid = valid
self.keys_widget.validChanged.connect(key_valid_changed)
self.layout().addWidget(self.keys_widget)
def apply(self):
text = self.slayout.get_text()
text = self.keys_widget.get_text()
if keystore.is_address_list(text):
self.wizard_data['address_list'] = text
elif keystore.is_private_key_list(text):

Loading…
Cancel
Save