|
|
|
|
@ -1,30 +1,43 @@
|
|
|
|
|
import os |
|
|
|
|
|
|
|
|
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject |
|
|
|
|
from PyQt5.QtQml import QQmlApplicationEngine |
|
|
|
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, Qt, QTimer |
|
|
|
|
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget, QFileDialog |
|
|
|
|
|
|
|
|
|
from electrum.daemon import Daemon |
|
|
|
|
from electrum.i18n import _ |
|
|
|
|
from electrum.storage import StorageReadWriteError |
|
|
|
|
from electrum.util import WalletFileException, get_new_wallet_name |
|
|
|
|
from electrum.wallet import wallet_types |
|
|
|
|
from .wizard import QEAbstractWizard, WizardComponent |
|
|
|
|
from electrum.logging import get_logger |
|
|
|
|
from electrum import mnemonic |
|
|
|
|
from electrum.wizard import NewWalletWizard, ServerConnectWizard |
|
|
|
|
from electrum import WalletStorage, mnemonic |
|
|
|
|
from electrum.wizard import NewWalletWizard |
|
|
|
|
from ..installwizard import MSG_PASSPHRASE_WARN_ISSUE4566 |
|
|
|
|
from ..seed_dialog import SeedLayout |
|
|
|
|
from ..util import ChoicesLayout, PasswordLineEdit, char_width_in_lineedit, WWLabel |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class QENewWalletWizard(NewWalletWizard, QEAbstractWizard): |
|
|
|
|
_logger = get_logger(__name__) |
|
|
|
|
|
|
|
|
|
createError = pyqtSignal([str], arguments=["error"]) |
|
|
|
|
createSuccess = pyqtSignal() |
|
|
|
|
# createError = pyqtSignal([str], arguments=["error"]) |
|
|
|
|
# createSuccess = pyqtSignal() |
|
|
|
|
|
|
|
|
|
def __init__(self, daemon, parent = None): |
|
|
|
|
def __init__(self, config: 'SimpleConfig', app: QApplication, daemon: Daemon, path, parent=None): |
|
|
|
|
NewWalletWizard.__init__(self, daemon) |
|
|
|
|
QEAbstractWizard.__init__(self, parent) |
|
|
|
|
QEAbstractWizard.__init__(self, config, app, parent) |
|
|
|
|
self._daemon = daemon |
|
|
|
|
self._path = path |
|
|
|
|
|
|
|
|
|
# attach view names and accept handlers |
|
|
|
|
self.navmap_merge({ |
|
|
|
|
'wallet_name': { 'gui': 'WCWalletName' }, |
|
|
|
|
'wallet_type': { 'gui': 'WCWalletType' }, |
|
|
|
|
'keystore_type': { 'gui': 'WCKeystoreType' }, |
|
|
|
|
'create_seed': { 'gui': 'WCCreateSeed' }, |
|
|
|
|
'confirm_seed': { 'gui': 'WCConfirmSeed' }, |
|
|
|
|
'wallet_name': { 'gui': WCWalletName }, |
|
|
|
|
'wallet_type': { 'gui': WCWalletType }, |
|
|
|
|
'keystore_type': { 'gui': WCKeystoreType }, |
|
|
|
|
'create_seed': { 'gui': WCCreateSeed }, |
|
|
|
|
'create_ext': { 'gui': WCCreateExt }, |
|
|
|
|
'confirm_seed': { 'gui': WCConfirmSeed }, |
|
|
|
|
'confirm_ext': { 'gui': WCConfirmExt }, |
|
|
|
|
'have_seed': { 'gui': 'WCHaveSeed' }, |
|
|
|
|
'bip39_refine': { 'gui': 'WCBIP39Refine' }, |
|
|
|
|
'have_master_key': { 'gui': 'WCHaveMasterKey' }, |
|
|
|
|
@ -37,54 +50,353 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
|
|
|
|
|
'wallet_password': { 'gui': 'WCWalletPassword' } |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
pathChanged = pyqtSignal() |
|
|
|
|
@pyqtProperty(str, notify=pathChanged) |
|
|
|
|
def path(self): |
|
|
|
|
return self._path |
|
|
|
|
|
|
|
|
|
@path.setter |
|
|
|
|
def path(self, path): |
|
|
|
|
self._path = path |
|
|
|
|
self.pathChanged.emit() |
|
|
|
|
# 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' |
|
|
|
|
}, |
|
|
|
|
'create_ext': { |
|
|
|
|
'next': 'confirm_seed', |
|
|
|
|
}, |
|
|
|
|
'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), |
|
|
|
|
}, |
|
|
|
|
'confirm_ext': { |
|
|
|
|
'next': self.on_have_or_confirm_seed, |
|
|
|
|
'accept': self.maybe_master_pubkey, |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
# pathChanged = pyqtSignal() |
|
|
|
|
# @pyqtProperty(str, notify=pathChanged) |
|
|
|
|
# def path(self): |
|
|
|
|
# return self._path |
|
|
|
|
# |
|
|
|
|
# @path.setter |
|
|
|
|
# def path(self, path): |
|
|
|
|
# self._path = path |
|
|
|
|
# self.pathChanged.emit() |
|
|
|
|
# |
|
|
|
|
def is_single_password(self): |
|
|
|
|
return self._daemon.singlePasswordEnabled |
|
|
|
|
|
|
|
|
|
@pyqtSlot('QJSValue', result=bool) |
|
|
|
|
def hasDuplicateMasterKeys(self, js_data): |
|
|
|
|
self._logger.info('Checking for duplicate masterkeys') |
|
|
|
|
data = js_data.toVariant() |
|
|
|
|
return self.has_duplicate_masterkeys(data) |
|
|
|
|
|
|
|
|
|
@pyqtSlot('QJSValue', result=bool) |
|
|
|
|
def hasHeterogeneousMasterKeys(self, js_data): |
|
|
|
|
self._logger.info('Checking for heterogeneous masterkeys') |
|
|
|
|
data = js_data.toVariant() |
|
|
|
|
return self.has_heterogeneous_masterkeys(data) |
|
|
|
|
|
|
|
|
|
@pyqtSlot(str, str, result=bool) |
|
|
|
|
def isMatchingSeed(self, seed, seed_again): |
|
|
|
|
return mnemonic.is_matching_seed(seed=seed, seed_again=seed_again) |
|
|
|
|
|
|
|
|
|
@pyqtSlot('QJSValue', bool, str) |
|
|
|
|
def createStorage(self, js_data, single_password_enabled, single_password): |
|
|
|
|
self._logger.info('Creating wallet from wizard data') |
|
|
|
|
data = js_data.toVariant() |
|
|
|
|
|
|
|
|
|
if single_password_enabled and single_password: |
|
|
|
|
data['encrypt'] = True |
|
|
|
|
data['password'] = single_password |
|
|
|
|
|
|
|
|
|
path = os.path.join(os.path.dirname(self._daemon.daemon.config.get_wallet_path()), data['wallet_name']) |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
self.create_storage(path, data) |
|
|
|
|
|
|
|
|
|
# minimally populate self after create |
|
|
|
|
self._password = data['password'] |
|
|
|
|
self.path = path |
|
|
|
|
|
|
|
|
|
self.createSuccess.emit() |
|
|
|
|
except Exception as e: |
|
|
|
|
self._logger.error(f"createStorage errored: {e!r}") |
|
|
|
|
self.createError.emit(str(e)) |
|
|
|
|
# TODO: also take into account if possible with existing set of wallets. see qedaemon.py |
|
|
|
|
return self._daemon.config.WALLET_USE_SINGLE_PASSWORD |
|
|
|
|
|
|
|
|
|
# @pyqtSlot('QJSValue', result=bool) |
|
|
|
|
# def hasDuplicateMasterKeys(self, js_data): |
|
|
|
|
# self._logger.info('Checking for duplicate masterkeys') |
|
|
|
|
# data = js_data.toVariant() |
|
|
|
|
# return self.has_duplicate_masterkeys(data) |
|
|
|
|
# |
|
|
|
|
# @pyqtSlot('QJSValue', result=bool) |
|
|
|
|
# def hasHeterogeneousMasterKeys(self, js_data): |
|
|
|
|
# self._logger.info('Checking for heterogeneous masterkeys') |
|
|
|
|
# data = js_data.toVariant() |
|
|
|
|
# return self.has_heterogeneous_masterkeys(data) |
|
|
|
|
# |
|
|
|
|
# @pyqtSlot(str, str, result=bool) |
|
|
|
|
# def isMatchingSeed(self, seed, seed_again): |
|
|
|
|
# return mnemonic.is_matching_seed(seed=seed, seed_again=seed_again) |
|
|
|
|
# |
|
|
|
|
# @pyqtSlot('QJSValue', bool, str) |
|
|
|
|
# def createStorage(self, js_data, single_password_enabled, single_password): |
|
|
|
|
# self._logger.info('Creating wallet from wizard data') |
|
|
|
|
# data = js_data.toVariant() |
|
|
|
|
# |
|
|
|
|
# if single_password_enabled and single_password: |
|
|
|
|
# data['encrypt'] = True |
|
|
|
|
# data['password'] = single_password |
|
|
|
|
# |
|
|
|
|
# path = os.path.join(os.path.dirname(self._daemon.daemon.config.get_wallet_path()), data['wallet_name']) |
|
|
|
|
# |
|
|
|
|
# try: |
|
|
|
|
# self.create_storage(path, data) |
|
|
|
|
# |
|
|
|
|
# # minimally populate self after create |
|
|
|
|
# self._password = data['password'] |
|
|
|
|
# self.path = path |
|
|
|
|
# |
|
|
|
|
# self.createSuccess.emit() |
|
|
|
|
# except Exception as e: |
|
|
|
|
# self._logger.error(f"createStorage errored: {e!r}") |
|
|
|
|
# self.createError.emit(str(e)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WCWalletName(WizardComponent): |
|
|
|
|
def __init__(self, parent, wizard): |
|
|
|
|
WizardComponent.__init__(self, parent, wizard, title=_('Electrum wallet')) |
|
|
|
|
|
|
|
|
|
path = wizard._path |
|
|
|
|
|
|
|
|
|
if os.path.isdir(path): |
|
|
|
|
raise Exception("wallet path cannot point to a directory") |
|
|
|
|
|
|
|
|
|
hbox = QHBoxLayout() |
|
|
|
|
hbox.addWidget(QLabel(_('Wallet') + ':')) |
|
|
|
|
self.name_e = QLineEdit() |
|
|
|
|
hbox.addWidget(self.name_e) |
|
|
|
|
button = QPushButton(_('Choose...')) |
|
|
|
|
hbox.addWidget(button) |
|
|
|
|
self.layout().addLayout(hbox) |
|
|
|
|
|
|
|
|
|
msg_label = WWLabel('') |
|
|
|
|
self.layout().addWidget(msg_label) |
|
|
|
|
hbox2 = QHBoxLayout() |
|
|
|
|
pw_e = PasswordLineEdit('', self) |
|
|
|
|
pw_e.setFixedWidth(17 * char_width_in_lineedit()) |
|
|
|
|
pw_label = QLabel(_('Password') + ':') |
|
|
|
|
hbox2.addWidget(pw_label) |
|
|
|
|
hbox2.addWidget(pw_e) |
|
|
|
|
hbox2.addStretch() |
|
|
|
|
self.layout().addLayout(hbox2) |
|
|
|
|
|
|
|
|
|
self.layout().addSpacing(50) |
|
|
|
|
vbox_create_new = QVBoxLayout() |
|
|
|
|
vbox_create_new.addWidget(QLabel(_('Alternatively') + ':'), alignment=Qt.AlignLeft) |
|
|
|
|
button_create_new = QPushButton(_('Create New Wallet')) |
|
|
|
|
button_create_new.setMinimumWidth(120) |
|
|
|
|
vbox_create_new.addWidget(button_create_new, alignment=Qt.AlignLeft) |
|
|
|
|
widget_create_new = QWidget() |
|
|
|
|
widget_create_new.setLayout(vbox_create_new) |
|
|
|
|
vbox_create_new.setContentsMargins(0, 0, 0, 0) |
|
|
|
|
self.layout().addWidget(widget_create_new) |
|
|
|
|
|
|
|
|
|
temp_storage = None # type: Optional[WalletStorage] |
|
|
|
|
wallet_folder = os.path.dirname(path) |
|
|
|
|
|
|
|
|
|
def on_choose(): |
|
|
|
|
path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) |
|
|
|
|
if path: |
|
|
|
|
self.name_e.setText(path) |
|
|
|
|
|
|
|
|
|
def on_filename(filename): |
|
|
|
|
# FIXME? "filename" might contain ".." (etc) and hence sketchy path traversals are possible |
|
|
|
|
nonlocal temp_storage |
|
|
|
|
temp_storage = None |
|
|
|
|
msg = None |
|
|
|
|
if filename: |
|
|
|
|
path = os.path.join(wallet_folder, filename) |
|
|
|
|
# wallet_from_memory = get_wallet_from_daemon(path) |
|
|
|
|
wallet_from_memory = self.wizard._daemon.get_wallet(path) |
|
|
|
|
try: |
|
|
|
|
if wallet_from_memory: |
|
|
|
|
temp_storage = wallet_from_memory.storage # type: Optional[WalletStorage] |
|
|
|
|
else: |
|
|
|
|
temp_storage = WalletStorage(path) |
|
|
|
|
except (StorageReadWriteError, WalletFileException) as e: |
|
|
|
|
msg = _('Cannot read file') + f'\n{repr(e)}' |
|
|
|
|
except Exception as e: |
|
|
|
|
self.logger.exception('') |
|
|
|
|
msg = _('Cannot read file') + f'\n{repr(e)}' |
|
|
|
|
else: |
|
|
|
|
msg = "" |
|
|
|
|
# self.next_button.setEnabled(temp_storage is not None) |
|
|
|
|
self.valid = temp_storage is not None |
|
|
|
|
user_needs_to_enter_password = False |
|
|
|
|
if temp_storage: |
|
|
|
|
if not temp_storage.file_exists(): |
|
|
|
|
msg =_("This file does not exist.") + '\n' \ |
|
|
|
|
+ _("Press 'Next' to create this wallet, or choose another file.") |
|
|
|
|
elif not wallet_from_memory: |
|
|
|
|
if temp_storage.is_encrypted_with_user_pw(): |
|
|
|
|
msg = _("This file is encrypted with a password.") + '\n' \ |
|
|
|
|
+ _('Enter your password or choose another file.') |
|
|
|
|
user_needs_to_enter_password = True |
|
|
|
|
elif temp_storage.is_encrypted_with_hw_device(): |
|
|
|
|
msg = _("This file is encrypted using a hardware device.") + '\n' \ |
|
|
|
|
+ _("Press 'Next' to choose device to decrypt.") |
|
|
|
|
else: |
|
|
|
|
msg = _("Press 'Next' to open this wallet.") |
|
|
|
|
else: |
|
|
|
|
msg = _("This file is already open in memory.") + "\n" \ |
|
|
|
|
+ _("Press 'Next' to create/focus window.") |
|
|
|
|
if msg is None: |
|
|
|
|
msg = _('Cannot read file') |
|
|
|
|
msg_label.setText(msg) |
|
|
|
|
widget_create_new.setVisible(bool(temp_storage and temp_storage.file_exists())) |
|
|
|
|
if user_needs_to_enter_password: |
|
|
|
|
pw_label.show() |
|
|
|
|
pw_e.show() |
|
|
|
|
pw_e.setFocus() |
|
|
|
|
else: |
|
|
|
|
pw_label.hide() |
|
|
|
|
pw_e.hide() |
|
|
|
|
|
|
|
|
|
button.clicked.connect(on_choose) |
|
|
|
|
button_create_new.clicked.connect( |
|
|
|
|
lambda: self.name_e.setText(get_new_wallet_name(wallet_folder))) # FIXME get_new_wallet_name might raise |
|
|
|
|
self.name_e.textChanged.connect(on_filename) |
|
|
|
|
self.name_e.setText(os.path.basename(path)) |
|
|
|
|
|
|
|
|
|
def apply(self): |
|
|
|
|
self.wizard_data['wallet_name'] = self.name_e.text() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WCWalletType(WizardComponent): |
|
|
|
|
def __init__(self, parent, wizard): |
|
|
|
|
WizardComponent.__init__(self, parent, wizard, title=_('Create new wallet')) |
|
|
|
|
message = _('What kind of wallet do you want to create?') |
|
|
|
|
wallet_kinds = [ |
|
|
|
|
('standard', _('Standard wallet')), |
|
|
|
|
('2fa', _('Wallet with two-factor authentication')), |
|
|
|
|
('multisig', _('Multi-signature wallet')), |
|
|
|
|
('imported', _('Import Bitcoin addresses or private keys')), |
|
|
|
|
] |
|
|
|
|
choices = [pair for pair in wallet_kinds if pair[0] in wallet_types] |
|
|
|
|
|
|
|
|
|
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._valid = True |
|
|
|
|
|
|
|
|
|
def apply(self): |
|
|
|
|
self.wizard_data['wallet_type'] = self.c_values[self.clayout.selected_index()] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WCKeystoreType(WizardComponent): |
|
|
|
|
def __init__(self, parent, wizard): |
|
|
|
|
WizardComponent.__init__(self, parent, wizard, title=_('Keystore')) |
|
|
|
|
message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?') |
|
|
|
|
choices = [ |
|
|
|
|
('createseed', _('Create a new seed')), |
|
|
|
|
('haveseed', _('I already have a seed')), |
|
|
|
|
('masterkey', _('Use a master key')), |
|
|
|
|
('hardware', _('Use a hardware device')) |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
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._valid = True |
|
|
|
|
|
|
|
|
|
def apply(self): |
|
|
|
|
self.wizard_data['keystore_type'] = self.c_values[self.clayout.selected_index()] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WCCreateSeed(WizardComponent): |
|
|
|
|
def __init__(self, parent, wizard): |
|
|
|
|
WizardComponent.__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 = None |
|
|
|
|
QTimer.singleShot(100, self.create_seed) |
|
|
|
|
|
|
|
|
|
def apply(self): |
|
|
|
|
if self.slayout: |
|
|
|
|
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_variant'] = 'electrum' |
|
|
|
|
|
|
|
|
|
def create_seed(self): |
|
|
|
|
self.busy = True |
|
|
|
|
self.seed = mnemonic.Mnemonic('en').make_seed(seed_type=self.seed_type) |
|
|
|
|
|
|
|
|
|
self.slayout = SeedLayout( |
|
|
|
|
title=_('Your wallet generation seed is:'), |
|
|
|
|
seed=self.seed, |
|
|
|
|
options=['ext'], |
|
|
|
|
msg=True, |
|
|
|
|
parent=self, |
|
|
|
|
config=self.wizard.config, |
|
|
|
|
) |
|
|
|
|
self.layout().addLayout(self.slayout) |
|
|
|
|
self.busy = False |
|
|
|
|
self.valid = True |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WCConfirmSeed(WizardComponent): |
|
|
|
|
def __init__(self, parent, wizard): |
|
|
|
|
WizardComponent.__init__(self, parent, wizard, title=_('Confirm Seed')) |
|
|
|
|
message = ' '.join([ |
|
|
|
|
_('Your seed is important!'), |
|
|
|
|
_('If you lose your seed, your money will be permanently lost.'), |
|
|
|
|
_('To make sure that you have properly saved your seed, please retype it here.') |
|
|
|
|
]) |
|
|
|
|
|
|
|
|
|
self.layout().addWidget(QLabel(message)) |
|
|
|
|
|
|
|
|
|
self._valid = True |
|
|
|
|
|
|
|
|
|
def apply(self): |
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WCCreateExt(WizardComponent): |
|
|
|
|
def __init__(self, parent, wizard): |
|
|
|
|
WizardComponent.__init__(self, parent, wizard, title=_('Seed Extension')) |
|
|
|
|
|
|
|
|
|
message = '\n'.join([ |
|
|
|
|
_('You may extend your seed with custom words.'), |
|
|
|
|
_('Your seed extension must be saved together with your seed.'), |
|
|
|
|
]) |
|
|
|
|
warning = '\n'.join([ |
|
|
|
|
_('Note that this is NOT your encryption password.'), |
|
|
|
|
_('If you do not know what this is, leave this field empty.'), |
|
|
|
|
]) |
|
|
|
|
|
|
|
|
|
self.ext_edit = SeedExtensionEdit(self, message=message, warning=warning) |
|
|
|
|
self.ext_edit.textEdited.connect(self.on_text_edited) |
|
|
|
|
self.layout().addWidget(self.ext_edit) |
|
|
|
|
|
|
|
|
|
def on_text_edited(self, text): |
|
|
|
|
self.ext_edit.warn_issue4566 = self.wizard_data['keystore_type'] == 'haveseed' and \ |
|
|
|
|
self.wizard_data['seed_type'] == 'bip39' |
|
|
|
|
self.valid = len(text) > 0 |
|
|
|
|
|
|
|
|
|
def apply(self): |
|
|
|
|
self.wizard_data['seed_extra_words'] = self.ext_edit.text() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WCConfirmExt(WizardComponent): |
|
|
|
|
def __init__(self, parent, wizard): |
|
|
|
|
WizardComponent.__init__(self, parent, wizard, title=_('Confirm Seed Extension')) |
|
|
|
|
message = '\n'.join([ |
|
|
|
|
_('Your seed extension must be saved together with your seed.'), |
|
|
|
|
_('Please type it here.'), |
|
|
|
|
]) |
|
|
|
|
self.ext_edit = SeedExtensionEdit(self, message=message) |
|
|
|
|
self.ext_edit.textEdited.connect(self.on_text_edited) |
|
|
|
|
self.layout().addWidget(self.ext_edit) |
|
|
|
|
|
|
|
|
|
def on_text_edited(self, text): |
|
|
|
|
self.valid = text == self.wizard_data['seed_extra_words'] |
|
|
|
|
|
|
|
|
|
def apply(self): |
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SeedExtensionEdit(QWidget): |
|
|
|
|
def __init__(self, parent, *, message: str = None, warning: str = None, warn_issue4566: bool = False): |
|
|
|
|
super().__init__(parent) |
|
|
|
|
|
|
|
|
|
self.warn_issue4566 = warn_issue4566 |
|
|
|
|
|
|
|
|
|
layout = QVBoxLayout() |
|
|
|
|
self.setLayout(layout) |
|
|
|
|
|
|
|
|
|
if message: |
|
|
|
|
layout.addWidget(WWLabel(message)) |
|
|
|
|
|
|
|
|
|
self.line = QLineEdit() |
|
|
|
|
layout.addWidget(self.line) |
|
|
|
|
|
|
|
|
|
def f(text): |
|
|
|
|
if self.warn_issue4566: |
|
|
|
|
text_whitespace_normalised = ' '.join(text.split()) |
|
|
|
|
warn_issue4566_label.setVisible(text != text_whitespace_normalised) |
|
|
|
|
self.line.textEdited.connect(f) |
|
|
|
|
|
|
|
|
|
if warning: |
|
|
|
|
layout.addWidget(WWLabel(warning)) |
|
|
|
|
|
|
|
|
|
warn_issue4566_label = WWLabel(MSG_PASSPHRASE_WARN_ISSUE4566) |
|
|
|
|
warn_issue4566_label.setVisible(False) |
|
|
|
|
layout.addWidget(warn_issue4566_label) |
|
|
|
|
|
|
|
|
|
# expose textEdited signal and text() func to widget |
|
|
|
|
self.textEdited = self.line.textEdited |
|
|
|
|
self.text = self.line.text |
|
|
|
|
|