Files
pallectrum/electrum/gui/qt/installwizard.py

798 lines
32 KiB
Python
Raw Normal View History

# Copyright (C) 2018 The Electrum developers
# Distributed under the MIT software license, see the accompanying
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
2017-01-22 21:25:24 +03:00
import os
import json
import sys
import threading
import traceback
2019-09-09 22:19:36 +02:00
from typing import Tuple, List, Callable, NamedTuple, Optional, TYPE_CHECKING
from functools import partial
2015-06-26 14:29:26 +02:00
from PyQt5.QtCore import QRect, QEventLoop, Qt, pyqtSignal
from PyQt5.QtGui import QPalette, QPen, QPainter, QPixmap
from PyQt5.QtWidgets import (QWidget, QDialog, QLabel, QHBoxLayout, QMessageBox,
QVBoxLayout, QLineEdit, QFileDialog, QPushButton,
2019-09-09 22:19:36 +02:00
QGridLayout, QSlider, QScrollArea, QApplication)
2019-03-04 02:08:23 +01:00
from electrum.wallet import Wallet, Abstract_Wallet
from electrum.storage import WalletStorage, StorageReadWriteError
from electrum.util import UserCancelled, InvalidPassword, WalletFileException, get_new_wallet_name
from electrum.base_wizard import BaseWizard, HWD_SETUP_DECRYPT_WALLET, GoBack, ReRunDialog
from electrum.network import Network
2013-09-11 13:55:49 +02:00
from electrum.i18n import _
2013-08-29 16:07:55 +02:00
2017-01-22 21:25:24 +03:00
from .seed_dialog import SeedLayout, KeysLayout
from .network_dialog import NetworkChoiceLayout
from .util import (MessageBoxMixin, Buttons, icon_path, ChoicesLayout, WWLabel,
InfoButton, char_width_in_lineedit, PasswordLineEdit, font_height)
from .password_dialog import PasswordLayout, PasswordLayoutForHW, PW_NEW
2020-06-05 10:09:45 +02:00
from .bip39_recovery_dialog import Bip39RecoveryDialog
2019-09-09 22:19:36 +02:00
from electrum.plugin import run_hook, Plugins
if TYPE_CHECKING:
from electrum.simple_config import SimpleConfig
2020-04-01 18:49:45 +02:00
from electrum.wallet_db import WalletDB
from . import ElectrumGui
2019-09-09 22:19:36 +02:00
2016-06-20 16:25:11 +02:00
2016-08-01 18:16:22 +02:00
MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys.") + '\n'\
+ _("Leave this field empty if you want to disable encryption.")
MSG_HW_STORAGE_ENCRYPTION = _("Set wallet file encryption.") + '\n'\
+ _("Your wallet file does not contain secrets, mostly just metadata. ") \
+ _("It also contains your master public key that allows watching your addresses.") + '\n\n'\
+ _("Note: If you enable this setting, you will need your hardware device to open your wallet.")
WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\n\n' +
_('A few examples') + ':\n' +
'p2pkh:KxZcY47uGp9a... \t-> 1DckmggQM...\n' +
'p2wpkh-p2sh:KxZcY47uGp9a... \t-> 3NhNeZQXF...\n' +
'p2wpkh:KxZcY47uGp9a... \t-> bc1q3fjfk...')
# note: full key is KxZcY47uGp9aVQAb6VVvuBs8SwHKgkSR2DbZUzjDzXf2N2GPhG9n
MSG_PASSPHRASE_WARN_ISSUE4566 = _("Warning") + ": "\
+ _("You have multiple consecutive whitespaces or leading/trailing "
"whitespaces in your passphrase.") + " " \
+ _("This is discouraged.") + " " \
+ _("Due to a bug, old versions of Electrum will NOT be creating the "
"same wallet as newer versions or other software.")
2014-04-29 21:19:42 +02:00
2015-06-26 14:29:26 +02:00
class CosignWidget(QWidget):
def __init__(self, m, n):
QWidget.__init__(self)
self.size = max(120, 9 * font_height())
2015-06-26 14:29:26 +02:00
self.R = QRect(0, 0, self.size, self.size)
self.setGeometry(self.R)
2016-01-13 19:20:58 +09:00
self.setMinimumHeight(self.size)
self.setMaximumHeight(self.size)
2015-06-26 14:29:26 +02:00
self.m = m
self.n = n
def set_n(self, n):
self.n = n
self.update()
def set_m(self, m):
self.m = m
self.update()
def paintEvent(self, event):
bgcolor = self.palette().color(QPalette.Background)
pen = QPen(bgcolor, 7, Qt.SolidLine)
2015-06-26 14:29:26 +02:00
qp = QPainter()
qp.begin(self)
qp.setPen(pen)
qp.setRenderHint(QPainter.Antialiasing)
qp.setBrush(Qt.gray)
for i in range(self.n):
alpha = int(16* 360 * i/self.n)
alpha2 = int(16* 360 * 1/self.n)
qp.setBrush(Qt.green if i<self.m else Qt.gray)
qp.drawPie(self.R, alpha, alpha2)
qp.end()
2016-06-20 16:25:11 +02:00
def wizard_dialog(func):
def func_wrapper(*args, **kwargs):
run_next = kwargs['run_next']
wizard = args[0] # type: InstallWizard
while True:
#wizard.logger.debug(f"dialog stack. len: {len(wizard._stack)}. stack: {wizard._stack}")
wizard.back_button.setText(_('Back') if wizard.can_go_back() else _('Cancel'))
# current dialog
try:
out = func(*args, **kwargs)
if type(out) is not tuple:
out = (out,)
except GoBack:
if not wizard.can_go_back():
wizard.close()
raise UserCancelled
else:
# to go back from the current dialog, we just let the caller unroll the stack:
raise
# next dialog
try:
while True:
try:
run_next(*out)
except ReRunDialog:
# restore state, and then let the loop re-run next
wizard.go_back(rerun_previous=False)
else:
break
except GoBack as e:
# to go back from the next dialog, we ask the wizard to restore state
wizard.go_back(rerun_previous=False)
# and we re-run the current dialog
if wizard.can_go_back():
# also rerun any calculations that might have populated the inputs to the current dialog,
# by going back to just after the *previous* dialog finished
raise ReRunDialog() from e
else:
continue
else:
break
2016-06-20 16:25:11 +02:00
return func_wrapper
2019-03-04 02:08:23 +01:00
class WalletAlreadyOpenInMemory(Exception):
def __init__(self, wallet: Abstract_Wallet):
super().__init__()
self.wallet = wallet
2016-06-20 16:25:11 +02:00
2016-01-03 11:18:20 +09:00
# WindowModalDialog must come first as it overrides show_error
2016-06-20 16:25:11 +02:00
class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
2017-09-23 05:54:38 +02:00
accept_signal = pyqtSignal()
def __init__(self, config: 'SimpleConfig', app: QApplication, plugins: 'Plugins', *, gui_object: 'ElectrumGui'):
QDialog.__init__(self, None)
2019-04-26 18:52:26 +02:00
BaseWizard.__init__(self, config, plugins)
self.setWindowTitle('Electrum - ' + _('Install Wizard'))
self.app = app
self.config = config
self.gui_thread = gui_object.gui_thread
2016-08-28 09:43:22 +02:00
self.setMinimumSize(600, 400)
2017-09-23 05:54:38 +02:00
self.accept_signal.connect(self.accept)
2016-08-01 18:16:22 +02:00
self.title = QLabel()
self.main_widget = QWidget()
2016-06-20 16:25:11 +02:00
self.back_button = QPushButton(_("Back"), self)
self.back_button.setText(_('Back') if self.can_go_back() else _('Cancel'))
self.next_button = QPushButton(_("Next"), self)
self.next_button.setDefault(True)
self.logo = QLabel()
self.please_wait = QLabel(_("Please wait..."))
self.please_wait.setAlignment(Qt.AlignCenter)
self.icon_filename = None
self.loop = QEventLoop()
2016-06-20 16:25:11 +02:00
self.rejected.connect(lambda: self.loop.exit(0))
self.back_button.clicked.connect(lambda: self.loop.exit(1))
self.next_button.clicked.connect(lambda: self.loop.exit(2))
outer_vbox = QVBoxLayout(self)
inner_vbox = QVBoxLayout()
inner_vbox.addWidget(self.title)
inner_vbox.addWidget(self.main_widget)
inner_vbox.addStretch(1)
inner_vbox.addWidget(self.please_wait)
inner_vbox.addStretch(1)
2017-09-24 11:52:53 +02:00
scroll_widget = QWidget()
scroll_widget.setLayout(inner_vbox)
scroll = QScrollArea()
2021-09-06 23:56:27 -07:00
scroll.setFocusPolicy(Qt.NoFocus)
2017-09-24 11:52:53 +02:00
scroll.setWidget(scroll_widget)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(True)
icon_vbox = QVBoxLayout()
icon_vbox.addWidget(self.logo)
icon_vbox.addStretch(1)
hbox = QHBoxLayout()
hbox.addLayout(icon_vbox)
2016-01-13 19:20:58 +09:00
hbox.addSpacing(5)
2017-09-24 11:52:53 +02:00
hbox.addWidget(scroll)
hbox.setStretchFactor(scroll, 1)
outer_vbox.addLayout(hbox)
2016-06-20 16:25:11 +02:00
outer_vbox.addLayout(Buttons(self.back_button, self.next_button))
self.set_icon('electrum.png')
self.show()
self.raise_()
self.refresh_gui() # Need for QT on MacOSX. Lame.
2019-03-04 02:08:23 +01:00
def select_storage(self, path, get_wallet_from_daemon) -> Tuple[str, Optional[WalletStorage]]:
if os.path.isdir(path):
raise Exception("wallet path cannot point to a directory")
2016-06-20 16:25:11 +02:00
2017-06-29 18:23:10 +02:00
vbox = QVBoxLayout()
hbox = QHBoxLayout()
hbox.addWidget(QLabel(_('Wallet') + ':'))
name_e = QLineEdit()
hbox.addWidget(name_e)
2017-06-29 18:23:10 +02:00
button = QPushButton(_('Choose...'))
hbox.addWidget(button)
vbox.addLayout(hbox)
msg_label = WWLabel('')
vbox.addWidget(msg_label)
2017-06-29 18:23:10 +02:00
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)
2017-06-29 18:23:10 +02:00
hbox2.addStretch()
vbox.addLayout(hbox2)
vbox.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)
vbox.addWidget(widget_create_new)
2017-06-29 18:23:10 +02:00
self.set_layout(vbox, title=_('Electrum wallet'))
2019-09-14 16:25:35 +02:00
temp_storage = None # type: Optional[WalletStorage]
wallet_folder = os.path.dirname(path)
2017-06-29 18:23:10 +02:00
def on_choose():
path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder)
if path:
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
2020-12-31 13:00:24 +01:00
if filename:
path = os.path.join(wallet_folder, filename)
wallet_from_memory = get_wallet_from_daemon(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)
2019-03-04 02:20:34 +01:00
user_needs_to_enter_password = False
if temp_storage:
if not temp_storage.file_exists():
2017-06-29 18:23:10 +02:00
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.')
2019-03-04 02:20:34 +01:00
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:
2017-06-29 18:23:10 +02:00
msg = _('Cannot read file')
msg_label.setText(msg)
widget_create_new.setVisible(bool(temp_storage and temp_storage.file_exists()))
2019-03-04 02:20:34 +01:00
if user_needs_to_enter_password:
pw_label.show()
pw_e.show()
pw_e.setFocus()
2017-06-29 18:23:10 +02:00
else:
pw_label.hide()
pw_e.hide()
2017-06-29 18:23:10 +02:00
button.clicked.connect(on_choose)
button_create_new.clicked.connect(
lambda: name_e.setText(get_new_wallet_name(wallet_folder))) # FIXME get_new_wallet_name might raise
name_e.textChanged.connect(on_filename)
name_e.setText(os.path.basename(path))
def run_user_interaction_loop():
while True:
if self.loop.exec_() != 2: # 2 = next
2020-07-01 18:37:31 +03:00
raise UserCancelled()
assert temp_storage
if temp_storage.file_exists() and not temp_storage.is_encrypted():
break
if not temp_storage.file_exists():
break
wallet_from_memory = get_wallet_from_daemon(temp_storage.path)
if wallet_from_memory:
raise WalletAlreadyOpenInMemory(wallet_from_memory)
if temp_storage.file_exists() and temp_storage.is_encrypted():
if temp_storage.is_encrypted_with_user_pw():
password = pw_e.text()
try:
temp_storage.decrypt(password)
break
except InvalidPassword as e:
self.show_message(title=_('Error'), msg=str(e))
continue
except BaseException as e:
self.logger.exception('')
self.show_message(title=_('Error'), msg=repr(e))
raise UserCancelled()
elif temp_storage.is_encrypted_with_hw_device():
try:
self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET, storage=temp_storage)
except InvalidPassword as e:
self.show_message(title=_('Error'),
msg=_('Failed to decrypt using this hardware device.') + '\n' +
_('If you use a passphrase, make sure it is correct.'))
self.reset_stack()
return self.select_storage(path, get_wallet_from_daemon)
except (UserCancelled, GoBack):
raise
except BaseException as e:
self.logger.exception('')
self.show_message(title=_('Error'), msg=repr(e))
raise UserCancelled()
if temp_storage.is_past_initial_decryption():
break
else:
raise UserCancelled()
else:
raise Exception('Unexpected encryption version')
try:
run_user_interaction_loop()
finally:
try:
pw_e.clear()
except RuntimeError: # wrapped C/C++ object has been deleted.
pass # happens when decrypting with hw device
return temp_storage.path, (temp_storage if temp_storage.file_exists() else None)
2020-04-01 18:49:45 +02:00
def run_upgrades(self, storage: WalletStorage, db: 'WalletDB') -> None:
path = storage.path
if db.requires_split():
self.hide()
msg = _("The wallet '{}' contains multiple accounts, which are no longer supported since Electrum 2.7.\n\n"
"Do you want to split your wallet into multiple files?").format(path)
if not self.question(msg):
return
file_list = db.split_accounts(path)
msg = _('Your accounts have been moved to') + ':\n' + '\n'.join(file_list) + '\n\n'+ _('Do you want to delete the old file') + ':\n' + path
if self.question(msg):
os.remove(path)
self.show_warning(_('The file was removed'))
# raise now, to avoid having the old storage opened
raise UserCancelled()
action = db.get_action()
if action and db.requires_upgrade():
raise WalletFileException('Incomplete wallet files cannot be upgraded.')
if action:
2016-06-20 16:25:11 +02:00
self.hide()
msg = _("The file '{}' contains an incompletely created wallet.\n"
"Do you want to complete its creation now?").format(path)
2016-06-20 16:25:11 +02:00
if not self.question(msg):
if self.question(_("Do you want to delete '{}'?").format(path)):
2016-06-20 16:25:11 +02:00
os.remove(path)
self.show_warning(_('The file was removed'))
return
self.show()
self.data = json.loads(storage.read())
self.run(action)
for k, v in self.data.items():
db.put(k, v)
db.write(storage)
return
if db.requires_upgrade():
self.upgrade_db(storage, db)
2016-01-17 22:03:57 +09:00
def on_error(self, exc_info):
if not isinstance(exc_info[1], UserCancelled):
2019-04-26 18:52:26 +02:00
self.logger.error("on_error", exc_info=exc_info)
2016-01-17 22:03:57 +09:00
self.show_error(str(exc_info[1]))
def set_icon(self, filename):
prior_filename, self.icon_filename = self.icon_filename, filename
self.logo.setPixmap(QPixmap(icon_path(filename))
.scaledToWidth(60, mode=Qt.SmoothTransformation))
return prior_filename
2017-03-17 13:47:27 +01:00
def set_layout(self, layout, title=None, next_enabled=True):
2016-08-01 18:16:22 +02:00
self.title.setText("<b>%s</b>"%title if title else "")
2016-01-12 23:32:13 +09:00
self.title.setVisible(bool(title))
# Get rid of any prior layout by assigning it to a temporary widget
prior_layout = self.main_widget.layout()
if prior_layout:
QWidget().setLayout(prior_layout)
self.main_widget.setLayout(layout)
2016-06-20 16:25:11 +02:00
self.back_button.setEnabled(True)
self.next_button.setEnabled(next_enabled)
2016-08-28 10:47:12 +02:00
if next_enabled:
self.next_button.setFocus()
self.main_widget.setVisible(True)
self.please_wait.setVisible(False)
def exec_layout(self, layout, title=None, raise_on_cancel=True,
next_enabled=True, focused_widget=None):
self.set_layout(layout, title, next_enabled)
if focused_widget:
focused_widget.setFocus()
result = self.loop.exec_()
if not result and raise_on_cancel:
2020-07-01 18:37:31 +03:00
raise UserCancelled()
2016-06-20 16:25:11 +02:00
if result == 1:
raise GoBack from None
2016-01-12 23:32:13 +09:00
self.title.setVisible(False)
2016-06-20 16:25:11 +02:00
self.back_button.setEnabled(False)
self.next_button.setEnabled(False)
self.main_widget.setVisible(False)
self.please_wait.setVisible(True)
2016-01-12 23:36:25 +09:00
self.refresh_gui()
return result
2016-01-12 23:36:25 +09:00
def refresh_gui(self):
# For some reason, to refresh the GUI this needs to be called twice
self.app.processEvents()
self.app.processEvents()
2013-11-03 11:03:45 +01:00
def remove_from_recently_open(self, filename):
self.config.remove_from_recently_open(filename)
def text_input(self, title, message, is_valid, allow_multi=False):
slayout = KeysLayout(parent=self, header_layout=message, is_valid=is_valid,
allow_multi=allow_multi, config=self.config)
self.exec_layout(slayout, title, next_enabled=False)
return slayout.get_text()
def seed_input(self, title, message, is_seed, options):
qt: follow-up passing-around-config follow-up b28b3994c7c879d43325faa8db8f4691be1f920f E | gui.qt.exception_window.Exception_Hook | exception caught by crash reporter Traceback (most recent call last): File "...\electrum\electrum\gui\qt\main_window.py", line 670, in new_wallet self.gui_object.start_new_window(full_path, None) File "...\electrum\electrum\gui\qt\__init__.py", line 245, in wrapper return func(self, *args, **kwargs) File "...\electrum\electrum\gui\qt\__init__.py", line 269, in start_new_window wallet = self._start_wizard_to_select_or_create_wallet(path) File "...\electrum\electrum\gui\qt\__init__.py", line 311, in _start_wizard_to_select_or_create_wallet wizard.run('new') File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 153, in new self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type) File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 193, in on_wallet_type self.run(action) File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 201, in choose_multisig self.multisig_dialog(run_next=on_multisig) File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 200, in on_multisig self.run('choose_keystore') File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 225, in choose_keystore self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run) File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 275, in choose_hw_device self._choose_hw_device(purpose=purpose, storage=storage) File "...\electrum\electrum\base_wizard.py", line 358, in _choose_hw_device self.choice_dialog(title=title, message=msg, choices=choices, File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 359, in <lambda> run_next=lambda *args: self.on_device(*args, purpose=purpose, storage=storage)) File "...\electrum\electrum\base_wizard.py", line 394, in on_device self.derivation_and_script_type_dialog(f) File "...\electrum\electrum\base_wizard.py", line 441, in derivation_and_script_type_dialog self.derivation_and_script_type_gui_specific_dialog( File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 393, in f self.run('on_hw_derivation', name, device_info, derivation, script_type) File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 490, in on_hw_derivation self.on_keystore(k) File "...\electrum\electrum\base_wizard.py", line 592, in on_keystore self.run('show_xpub_and_add_cosigners', xpub) File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 686, in show_xpub_and_add_cosigners self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore')) File "...\electrum\electrum\gui\qt\installwizard.py", line 106, in func_wrapper out = func(*args, **kwargs) File "...\electrum\electrum\gui\qt\installwizard.py", line 700, in show_xpub_dialog layout = SeedLayout(xpub, title=msg, icon=False, for_seed_words=False) File "...\electrum\electrum\gui\qt\seed_dialog.py", line 108, in __init__ self.seed_e = ShowQRTextEdit(config=self.config) AttributeError: 'SeedLayout' object has no attribute 'config'
2020-12-23 17:34:21 +01:00
slayout = SeedLayout(
title=message,
is_seed=is_seed,
options=options,
parent=self,
config=self.config,
)
self.exec_layout(slayout, title, next_enabled=False)
2020-10-12 17:21:14 +02:00
return slayout.get_seed(), slayout.seed_type, slayout.is_ext
2016-06-20 16:25:11 +02:00
@wizard_dialog
def add_xpub_dialog(self, title, message, is_valid, run_next, allow_multi=False, show_wif_help=False):
header_layout = QHBoxLayout()
label = WWLabel(message)
label.setMinimumWidth(400)
header_layout.addWidget(label)
if show_wif_help:
header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight)
return self.text_input(title, header_layout, is_valid, allow_multi)
2016-06-20 16:25:11 +02:00
@wizard_dialog
def add_cosigner_dialog(self, run_next, index, is_valid):
title = _("Add Cosigner") + " %d"%index
message = ' '.join([
2016-09-28 17:03:02 +02:00
_('Please enter the master public key (xpub) of your cosigner.'),
_('Enter their master private key (xprv) if you want to be able to sign for them.')
])
return self.text_input(title, message, is_valid)
@wizard_dialog
def restore_seed_dialog(self, run_next, test):
2016-12-19 13:25:16 +01:00
options = []
if self.opt_ext:
options.append('ext')
if self.opt_bip39:
options.append('bip39')
2020-10-12 17:21:14 +02:00
if self.opt_slip39:
options.append('slip39')
title = _('Enter Seed')
message = _('Please enter your seed phrase in order to restore your wallet.')
2016-12-19 13:25:16 +01:00
return self.seed_input(title, message, test, options)
@wizard_dialog
def confirm_seed_dialog(self, run_next, seed, test):
self.app.clipboard().clear()
title = _('Confirm Seed')
2016-08-01 18:16:22 +02:00
message = ' '.join([
2016-08-31 08:50:31 +02:00
_('Your seed is important!'),
_('If you lose your seed, your money will be permanently lost.'),
2016-08-01 18:16:22 +02:00
_('To make sure that you have properly saved your seed, please retype it here.')
])
2020-10-12 17:21:14 +02:00
seed, seed_type, is_ext = self.seed_input(title, message, test, None)
return seed
2016-06-20 16:25:11 +02:00
@wizard_dialog
def show_seed_dialog(self, run_next, seed_text):
qt: follow-up passing-around-config follow-up b28b3994c7c879d43325faa8db8f4691be1f920f E | gui.qt.exception_window.Exception_Hook | exception caught by crash reporter Traceback (most recent call last): File "...\electrum\electrum\gui\qt\main_window.py", line 670, in new_wallet self.gui_object.start_new_window(full_path, None) File "...\electrum\electrum\gui\qt\__init__.py", line 245, in wrapper return func(self, *args, **kwargs) File "...\electrum\electrum\gui\qt\__init__.py", line 269, in start_new_window wallet = self._start_wizard_to_select_or_create_wallet(path) File "...\electrum\electrum\gui\qt\__init__.py", line 311, in _start_wizard_to_select_or_create_wallet wizard.run('new') File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 153, in new self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type) File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 193, in on_wallet_type self.run(action) File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 201, in choose_multisig self.multisig_dialog(run_next=on_multisig) File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 200, in on_multisig self.run('choose_keystore') File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 225, in choose_keystore self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run) File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 275, in choose_hw_device self._choose_hw_device(purpose=purpose, storage=storage) File "...\electrum\electrum\base_wizard.py", line 358, in _choose_hw_device self.choice_dialog(title=title, message=msg, choices=choices, File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 359, in <lambda> run_next=lambda *args: self.on_device(*args, purpose=purpose, storage=storage)) File "...\electrum\electrum\base_wizard.py", line 394, in on_device self.derivation_and_script_type_dialog(f) File "...\electrum\electrum\base_wizard.py", line 441, in derivation_and_script_type_dialog self.derivation_and_script_type_gui_specific_dialog( File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 393, in f self.run('on_hw_derivation', name, device_info, derivation, script_type) File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 490, in on_hw_derivation self.on_keystore(k) File "...\electrum\electrum\base_wizard.py", line 592, in on_keystore self.run('show_xpub_and_add_cosigners', xpub) File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 686, in show_xpub_and_add_cosigners self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore')) File "...\electrum\electrum\gui\qt\installwizard.py", line 106, in func_wrapper out = func(*args, **kwargs) File "...\electrum\electrum\gui\qt\installwizard.py", line 700, in show_xpub_dialog layout = SeedLayout(xpub, title=msg, icon=False, for_seed_words=False) File "...\electrum\electrum\gui\qt\seed_dialog.py", line 108, in __init__ self.seed_e = ShowQRTextEdit(config=self.config) AttributeError: 'SeedLayout' object has no attribute 'config'
2020-12-23 17:34:21 +01:00
title = _("Your wallet generation seed is:")
slayout = SeedLayout(
seed=seed_text,
title=title,
msg=True,
options=['ext'],
config=self.config,
)
self.exec_layout(slayout)
return slayout.is_ext
def pw_layout(self, msg, kind, force_disable_encrypt_cb):
pw_layout = PasswordLayout(
msg=msg, kind=kind, OK_button=self.next_button,
force_disable_encrypt_cb=force_disable_encrypt_cb)
pw_layout.encrypt_cb.setChecked(True)
try:
self.exec_layout(pw_layout.layout(), focused_widget=pw_layout.new_pw)
return pw_layout.new_password(), pw_layout.encrypt_cb.isChecked()
finally:
pw_layout.clear_password_fields()
2016-06-20 16:25:11 +02:00
@wizard_dialog
def request_password(self, run_next, force_disable_encrypt_cb=False):
"""Request the user enter a new password and confirm it. Return
the password or None for no password."""
return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW, force_disable_encrypt_cb)
@wizard_dialog
def request_storage_encryption(self, run_next):
playout = PasswordLayoutForHW(MSG_HW_STORAGE_ENCRYPTION)
playout.encrypt_cb.setChecked(True)
self.exec_layout(playout.layout())
return playout.encrypt_cb.isChecked()
2016-07-31 10:59:42 +02:00
@wizard_dialog
2016-08-23 13:40:11 +02:00
def confirm_dialog(self, title, message, run_next):
self.confirm(message, title)
2016-07-31 10:59:42 +02:00
2016-08-23 13:40:11 +02:00
def confirm(self, message, title):
label = WWLabel(message)
2016-06-20 16:25:11 +02:00
vbox = QVBoxLayout()
2017-09-24 11:52:53 +02:00
vbox.addWidget(label)
self.exec_layout(vbox, title)
2015-08-26 18:35:21 +02:00
2016-06-20 16:25:11 +02:00
@wizard_dialog
def action_dialog(self, action, run_next):
self.run(action)
2014-05-09 13:12:07 +02:00
def terminate(self, **kwargs):
2017-09-23 05:54:38 +02:00
self.accept_signal.emit()
2016-01-12 23:32:13 +09:00
def waiting_dialog(self, task, msg, on_finished=None):
label = WWLabel(msg)
vbox = QVBoxLayout()
vbox.addSpacing(100)
label.setMinimumWidth(300)
label.setAlignment(Qt.AlignCenter)
vbox.addWidget(label)
self.set_layout(vbox, next_enabled=False)
self.back_button.setEnabled(False)
t = threading.Thread(target=task)
2016-06-20 16:25:11 +02:00
t.start()
while True:
t.join(1.0/60)
if t.is_alive():
self.refresh_gui()
else:
break
if on_finished:
on_finished()
def run_task_without_blocking_gui(self, task, *, msg=None):
assert self.gui_thread == threading.current_thread(), 'must be called from GUI thread'
if msg is None:
msg = _("Please wait...")
exc = None # type: Optional[Exception]
res = None
def task_wrapper():
nonlocal exc
nonlocal res
try:
res = task()
except Exception as e:
exc = e
self.waiting_dialog(task_wrapper, msg=msg)
if exc is None:
return res
else:
raise exc
2016-06-20 16:25:11 +02:00
@wizard_dialog
def choice_dialog(self, title, message, choices, run_next):
2017-01-30 12:36:56 +03:00
c_values = [x[0] for x in choices]
c_titles = [x[1] for x in choices]
2016-06-20 16:25:11 +02:00
clayout = ChoicesLayout(message, c_titles)
vbox = QVBoxLayout()
2016-06-20 16:25:11 +02:00
vbox.addLayout(clayout.layout())
self.exec_layout(vbox, title)
2016-06-20 16:25:11 +02:00
action = c_values[clayout.selected_index()]
return action
2016-08-23 10:00:46 +02:00
def query_choice(self, msg, choices):
"""called by hardware wallets"""
clayout = ChoicesLayout(msg, choices)
vbox = QVBoxLayout()
vbox.addLayout(clayout.layout())
self.exec_layout(vbox, '')
2016-08-23 10:00:46 +02:00
return clayout.selected_index()
@wizard_dialog
def derivation_and_script_type_gui_specific_dialog(
self,
*,
title: str,
message1: str,
choices: List[Tuple[str, str, str]],
hide_choices: bool = False,
message2: str,
test_text: Callable[[str], int],
run_next,
default_choice_idx: int = 0,
get_account_xpub=None,
) -> Tuple[str, str]:
vbox = QVBoxLayout()
2020-06-05 10:09:45 +02:00
if get_account_xpub:
button = QPushButton(_("Detect Existing Accounts"))
def on_account_select(account):
script_type = account["script_type"]
if script_type == "p2pkh":
script_type = "standard"
button_index = c_values.index(script_type)
button = clayout.group.buttons()[button_index]
button.setChecked(True)
line.setText(account["derivation_path"])
button.clicked.connect(lambda: Bip39RecoveryDialog(self, get_account_xpub, on_account_select))
vbox.addWidget(button, alignment=Qt.AlignLeft)
vbox.addWidget(QLabel(_("Or")))
c_values = [x[0] for x in choices]
c_titles = [x[1] for x in choices]
c_default_text = [x[2] for x in choices]
def on_choice_click(clayout):
idx = clayout.selected_index()
line.setText(c_default_text[idx])
clayout = ChoicesLayout(message1, c_titles, on_choice_click,
checked_index=default_choice_idx)
if not hide_choices:
vbox.addLayout(clayout.layout())
vbox.addWidget(WWLabel(message2))
line = QLineEdit()
def on_text_change(text):
self.next_button.setEnabled(test_text(text))
line.textEdited.connect(on_text_change)
on_choice_click(clayout) # set default text for "line"
vbox.addWidget(line)
self.exec_layout(vbox, title)
choice = c_values[clayout.selected_index()]
return str(line.text()), choice
@wizard_dialog
def line_dialog(self, run_next, title, message, default, test, warning='',
presets=(), warn_issue4566=False):
vbox = QVBoxLayout()
vbox.addWidget(WWLabel(message))
line = QLineEdit()
line.setText(default)
def f(text):
self.next_button.setEnabled(test(text))
if warn_issue4566:
text_whitespace_normalised = ' '.join(text.split())
warn_issue4566_label.setVisible(text != text_whitespace_normalised)
line.textEdited.connect(f)
vbox.addWidget(line)
vbox.addWidget(WWLabel(warning))
warn_issue4566_label = WWLabel(MSG_PASSPHRASE_WARN_ISSUE4566)
warn_issue4566_label.setVisible(False)
vbox.addWidget(warn_issue4566_label)
for preset in presets:
button = QPushButton(preset[0])
button.clicked.connect(lambda __, text=preset[1]: line.setText(text))
button.setMinimumWidth(150)
hbox = QHBoxLayout()
hbox.addWidget(button, alignment=Qt.AlignCenter)
vbox.addLayout(hbox)
self.exec_layout(vbox, title, next_enabled=test(default))
return line.text()
2016-06-20 16:25:11 +02:00
@wizard_dialog
def show_xpub_dialog(self, xpub, run_next):
2016-08-01 18:16:22 +02:00
msg = ' '.join([
_("Here is your master public key."),
_("Please share it with your cosigners.")
])
2014-05-09 16:27:12 +02:00
vbox = QVBoxLayout()
qt: follow-up passing-around-config follow-up b28b3994c7c879d43325faa8db8f4691be1f920f E | gui.qt.exception_window.Exception_Hook | exception caught by crash reporter Traceback (most recent call last): File "...\electrum\electrum\gui\qt\main_window.py", line 670, in new_wallet self.gui_object.start_new_window(full_path, None) File "...\electrum\electrum\gui\qt\__init__.py", line 245, in wrapper return func(self, *args, **kwargs) File "...\electrum\electrum\gui\qt\__init__.py", line 269, in start_new_window wallet = self._start_wizard_to_select_or_create_wallet(path) File "...\electrum\electrum\gui\qt\__init__.py", line 311, in _start_wizard_to_select_or_create_wallet wizard.run('new') File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 153, in new self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type) File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 193, in on_wallet_type self.run(action) File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 201, in choose_multisig self.multisig_dialog(run_next=on_multisig) File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 200, in on_multisig self.run('choose_keystore') File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 225, in choose_keystore self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run) File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 275, in choose_hw_device self._choose_hw_device(purpose=purpose, storage=storage) File "...\electrum\electrum\base_wizard.py", line 358, in _choose_hw_device self.choice_dialog(title=title, message=msg, choices=choices, File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 359, in <lambda> run_next=lambda *args: self.on_device(*args, purpose=purpose, storage=storage)) File "...\electrum\electrum\base_wizard.py", line 394, in on_device self.derivation_and_script_type_dialog(f) File "...\electrum\electrum\base_wizard.py", line 441, in derivation_and_script_type_dialog self.derivation_and_script_type_gui_specific_dialog( File "...\electrum\electrum\gui\qt\installwizard.py", line 120, in func_wrapper run_next(*out) File "...\electrum\electrum\base_wizard.py", line 393, in f self.run('on_hw_derivation', name, device_info, derivation, script_type) File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 490, in on_hw_derivation self.on_keystore(k) File "...\electrum\electrum\base_wizard.py", line 592, in on_keystore self.run('show_xpub_and_add_cosigners', xpub) File "...\electrum\electrum\base_wizard.py", line 115, in run f(*args, **kwargs) File "...\electrum\electrum\base_wizard.py", line 686, in show_xpub_and_add_cosigners self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore')) File "...\electrum\electrum\gui\qt\installwizard.py", line 106, in func_wrapper out = func(*args, **kwargs) File "...\electrum\electrum\gui\qt\installwizard.py", line 700, in show_xpub_dialog layout = SeedLayout(xpub, title=msg, icon=False, for_seed_words=False) File "...\electrum\electrum\gui\qt\seed_dialog.py", line 108, in __init__ self.seed_e = ShowQRTextEdit(config=self.config) AttributeError: 'SeedLayout' object has no attribute 'config'
2020-12-23 17:34:21 +01:00
layout = SeedLayout(
xpub,
title=msg,
icon=False,
for_seed_words=False,
config=self.config,
)
2016-06-20 16:25:11 +02:00
vbox.addLayout(layout.layout())
self.exec_layout(vbox, _('Master Public Key'))
2016-06-20 16:25:11 +02:00
return None
2013-09-12 19:42:00 +02:00
def init_network(self, network: 'Network'):
message = _("Electrum communicates with remote servers to get "
"information about your transactions and addresses. The "
"servers all fulfill the same purpose only differing in "
"hardware. In most cases you simply want to let Electrum "
"pick one at random. However if you prefer feel free to "
"select a server manually.")
choices = [_("Auto connect"), _("Select server manually")]
title = _("How do you want to connect to a server? ")
clayout = ChoicesLayout(message, choices)
self.back_button.setText(_('Cancel'))
self.exec_layout(clayout.layout(), title)
r = clayout.selected_index()
2017-03-29 10:07:42 +02:00
if r == 1:
nlayout = NetworkChoiceLayout(network, self.config, wizard=True)
if self.exec_layout(nlayout.layout()):
2017-03-29 10:07:42 +02:00
nlayout.accept()
self.config.NETWORK_AUTO_CONNECT = network.auto_connect
else:
2017-03-29 10:07:42 +02:00
network.auto_connect = True
self.config.NETWORK_AUTO_CONNECT = True
2013-09-03 14:32:56 +02:00
2016-06-20 16:25:11 +02:00
@wizard_dialog
def multisig_dialog(self, run_next):
2015-06-26 14:29:26 +02:00
cw = CosignWidget(2, 2)
2016-06-14 11:16:57 +02:00
n_edit = QSlider(Qt.Horizontal, self)
2021-09-06 23:56:27 -07:00
m_edit = QSlider(Qt.Horizontal, self)
2015-06-26 14:29:26 +02:00
n_edit.setMinimum(2)
n_edit.setMaximum(15)
m_edit.setMinimum(1)
m_edit.setMaximum(2)
2016-06-14 11:16:57 +02:00
n_edit.setValue(2)
m_edit.setValue(2)
n_label = QLabel()
m_label = QLabel()
grid = QGridLayout()
grid.addWidget(n_label, 0, 0)
grid.addWidget(n_edit, 0, 1)
grid.addWidget(m_label, 1, 0)
grid.addWidget(m_edit, 1, 1)
def on_m(m):
m_label.setText(_('Require {0} signatures').format(m))
2016-06-14 11:16:57 +02:00
cw.set_m(m)
backup_warning_label.setVisible(cw.m != cw.n)
2016-06-14 11:16:57 +02:00
def on_n(n):
n_label.setText(_('From {0} cosigners').format(n))
2016-06-14 11:16:57 +02:00
cw.set_n(n)
m_edit.setMaximum(n)
backup_warning_label.setVisible(cw.m != cw.n)
2016-06-14 11:16:57 +02:00
n_edit.valueChanged.connect(on_n)
m_edit.valueChanged.connect(on_m)
2016-01-13 19:20:58 +09:00
vbox = QVBoxLayout()
vbox.addWidget(cw)
2016-06-20 16:25:11 +02:00
vbox.addWidget(WWLabel(_("Choose the number of signatures needed to unlock funds in your wallet:")))
2016-06-14 11:16:57 +02:00
vbox.addLayout(grid)
vbox.addSpacing(2 * char_width_in_lineedit())
backup_warning_label = WWLabel(_("Warning: to be able to restore a multisig wallet, "
"you should include the master public key for each cosigner "
"in all of your backups."))
vbox.addWidget(backup_warning_label)
on_n(2)
on_m(2)
self.exec_layout(vbox, _("Multi-Signature Wallet"))
2015-06-26 14:29:26 +02:00
m = int(m_edit.value())
n = int(n_edit.value())
2016-06-20 16:25:11 +02:00
return (m, n)