Finish wizard unification
This commit is contained in:
@@ -5,6 +5,10 @@ from PyQt4.QtCore import *
|
||||
import PyQt4.QtCore as QtCore
|
||||
|
||||
import electrum
|
||||
from electrum.wallet import Wallet
|
||||
from electrum.mnemonic import prepare_seed
|
||||
from electrum.util import UserCancelled
|
||||
from electrum.base_wizard import BaseWizard
|
||||
from electrum.i18n import _
|
||||
|
||||
from seed_dialog import SeedDisplayLayout, SeedWarningLayout, SeedInputLayout
|
||||
@@ -12,14 +16,23 @@ from network_dialog import NetworkChoiceLayout
|
||||
from util import *
|
||||
from password_dialog import PasswordLayout, PW_NEW, PW_PASSPHRASE
|
||||
|
||||
from electrum.wallet import Wallet
|
||||
from electrum.mnemonic import prepare_seed
|
||||
from electrum.util import UserCancelled
|
||||
from electrum.wizard import (WizardBase,
|
||||
MSG_ENTER_PASSWORD, MSG_RESTORE_PASSPHRASE,
|
||||
MSG_COSIGNER, MSG_ENTER_SEED_OR_MPK,
|
||||
MSG_SHOW_MPK, MSG_VERIFY_SEED,
|
||||
MSG_GENERATING_WAIT)
|
||||
|
||||
class GoBack(Exception):
|
||||
pass
|
||||
|
||||
MSG_GENERATING_WAIT = _("Electrum is generating your addresses, please wait...")
|
||||
MSG_ENTER_ANYTHING = _("Please enter a seed phrase, a master key, a list of "
|
||||
"Bitcoin addresses, or a list of private keys")
|
||||
MSG_ENTER_SEED_OR_MPK = _("Please enter a seed phrase or a master key (xpub or xprv):")
|
||||
MSG_VERIFY_SEED = _("Your seed is important!\nTo make sure that you have properly saved your seed, please retype it here.")
|
||||
MSG_COSIGNER = _("Please enter the master public key of cosigner #%d:")
|
||||
MSG_SHOW_MPK = _("Here is your master public key:")
|
||||
MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys. "
|
||||
"Enter nothing if you want to disable encryption.")
|
||||
MSG_RESTORE_PASSPHRASE = \
|
||||
_("Please enter the passphrase you used when creating your %s wallet. "
|
||||
"Note this is NOT a password. Enter nothing if you did not use "
|
||||
"one or are unsure.")
|
||||
|
||||
def clean_text(seed_e):
|
||||
text = unicode(seed_e.toPlainText()).strip()
|
||||
@@ -63,14 +76,42 @@ class CosignWidget(QWidget):
|
||||
qp.end()
|
||||
|
||||
|
||||
# WindowModalDialog must come first as it overrides show_error
|
||||
class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
|
||||
|
||||
def __init__(self, config, app, plugins):
|
||||
def wizard_dialog(func):
|
||||
def func_wrapper(*args, **kwargs):
|
||||
run_next = kwargs['run_next']
|
||||
wizard = args[0]
|
||||
wizard.back_button.setText(_('Back') if wizard.can_go_back() else _('Cancel'))
|
||||
try:
|
||||
out = func(*args, **kwargs)
|
||||
except GoBack:
|
||||
print "go back"
|
||||
wizard.go_back()
|
||||
return
|
||||
except UserCancelled:
|
||||
print "usercancelled"
|
||||
return
|
||||
#if out is None:
|
||||
# out = ()
|
||||
if type(out) is not tuple:
|
||||
out = (out,)
|
||||
apply(run_next, out)
|
||||
return func_wrapper
|
||||
|
||||
|
||||
|
||||
# WindowModalDialog must come first as it overrides show_error
|
||||
class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
||||
|
||||
def __init__(self, config, app, plugins, network, storage):
|
||||
|
||||
BaseWizard.__init__(self, config, network, storage)
|
||||
QDialog.__init__(self, None)
|
||||
|
||||
self.setWindowTitle('Electrum - ' + _('Install Wizard'))
|
||||
self.app = app
|
||||
self.config = config
|
||||
|
||||
# Set for base base class
|
||||
self.plugins = plugins
|
||||
self.language_for_seed = config.get('language')
|
||||
@@ -79,7 +120,7 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
|
||||
self.connect(self, QtCore.SIGNAL('accept'), self.accept)
|
||||
self.title = WWLabel()
|
||||
self.main_widget = QWidget()
|
||||
self.cancel_button = QPushButton(_("Cancel"), self)
|
||||
self.back_button = QPushButton(_("Back"), self)
|
||||
self.next_button = QPushButton(_("Next"), self)
|
||||
self.next_button.setDefault(True)
|
||||
self.logo = QLabel()
|
||||
@@ -87,9 +128,9 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
|
||||
self.please_wait.setAlignment(Qt.AlignCenter)
|
||||
self.icon_filename = None
|
||||
self.loop = QEventLoop()
|
||||
self.rejected.connect(lambda: self.loop.exit(False))
|
||||
self.cancel_button.clicked.connect(lambda: self.loop.exit(False))
|
||||
self.next_button.clicked.connect(lambda: self.loop.exit(True))
|
||||
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 = QVBoxLayout()
|
||||
@@ -107,12 +148,35 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
|
||||
hbox.addLayout(inner_vbox)
|
||||
hbox.setStretchFactor(inner_vbox, 1)
|
||||
outer_vbox.addLayout(hbox)
|
||||
outer_vbox.addLayout(Buttons(self.cancel_button, self.next_button))
|
||||
outer_vbox.addLayout(Buttons(self.back_button, self.next_button))
|
||||
self.set_icon(':icons/electrum.png')
|
||||
self.show()
|
||||
self.raise_()
|
||||
self.refresh_gui() # Need for QT on MacOSX. Lame.
|
||||
|
||||
def run_and_get_wallet(self):
|
||||
# Show network dialog if config does not exist
|
||||
if self.network:
|
||||
if self.config.get('auto_connect') is None:
|
||||
self.choose_server(self.network)
|
||||
|
||||
action = self.get_action()
|
||||
if action != 'new':
|
||||
self.hide()
|
||||
path = self.storage.path
|
||||
msg = _("The file '%s' contains an incompletely created wallet.\n"
|
||||
"Do you want to complete its creation now?") % path
|
||||
if not self.question(msg):
|
||||
if self.question(_("Do you want to delete '%s'?") % path):
|
||||
import os
|
||||
os.remove(path)
|
||||
self.show_warning(_('The file was removed'))
|
||||
return
|
||||
return
|
||||
self.show()
|
||||
self.run(action)
|
||||
return self.wallet
|
||||
|
||||
def finished(self):
|
||||
'''Ensure the dialog is closed.'''
|
||||
self.accept()
|
||||
@@ -137,15 +201,17 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
|
||||
if prior_layout:
|
||||
QWidget().setLayout(prior_layout)
|
||||
self.main_widget.setLayout(layout)
|
||||
self.cancel_button.setEnabled(True)
|
||||
self.back_button.setEnabled(True)
|
||||
self.next_button.setEnabled(next_enabled)
|
||||
self.main_widget.setVisible(True)
|
||||
self.please_wait.setVisible(False)
|
||||
result = self.loop.exec_()
|
||||
if not result and raise_on_cancel:
|
||||
raise UserCancelled
|
||||
if result == 1:
|
||||
raise GoBack
|
||||
self.title.setVisible(False)
|
||||
self.cancel_button.setEnabled(False)
|
||||
self.back_button.setEnabled(False)
|
||||
self.next_button.setEnabled(False)
|
||||
self.main_widget.setVisible(False)
|
||||
self.please_wait.setVisible(True)
|
||||
@@ -157,58 +223,42 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
|
||||
self.app.processEvents()
|
||||
self.app.processEvents()
|
||||
|
||||
def run(self, *args):
|
||||
'''Wrap the base wizard implementation with try/except blocks
|
||||
to give a sensible error message to the user.'''
|
||||
wallet = None
|
||||
try:
|
||||
wallet = WizardBase.run(self, *args)
|
||||
except UserCancelled:
|
||||
self.print_error("wallet creation cancelled by user")
|
||||
self.accept() # For when called from menu
|
||||
except BaseException as e:
|
||||
self.on_error(sys.exc_info())
|
||||
raise
|
||||
return wallet
|
||||
|
||||
def remove_from_recently_open(self, filename):
|
||||
self.config.remove_from_recently_open(filename)
|
||||
|
||||
def request_seed(self, title, is_valid=None):
|
||||
is_valid = is_valid or Wallet.is_any
|
||||
slayout = SeedInputLayout()
|
||||
def text_input(self, title, message, is_valid):
|
||||
slayout = SeedInputLayout(title=message)
|
||||
def sanitized_seed():
|
||||
return clean_text(slayout.seed_edit())
|
||||
def set_enabled():
|
||||
self.next_button.setEnabled(is_valid(sanitized_seed()))
|
||||
slayout.seed_edit().textChanged.connect(set_enabled)
|
||||
self.set_main_layout(slayout.layout(), title, next_enabled=False)
|
||||
return sanitized_seed()
|
||||
seed = sanitized_seed()
|
||||
return seed
|
||||
|
||||
def show_seed(self, seed):
|
||||
slayout = SeedWarningLayout(seed)
|
||||
self.set_main_layout(slayout.layout())
|
||||
@wizard_dialog
|
||||
def add_xpub_dialog(self, title, message, is_valid, run_next):
|
||||
return self.text_input(title, message, is_valid)
|
||||
|
||||
def verify_seed(self, seed, is_valid=None):
|
||||
while True:
|
||||
r = self.request_seed(MSG_VERIFY_SEED, is_valid)
|
||||
if prepare_seed(r) == prepare_seed(seed):
|
||||
return
|
||||
self.show_error(_('Incorrect seed'))
|
||||
|
||||
def show_and_verify_seed(self, seed, is_valid=None):
|
||||
"""Show the user their seed. Ask them to re-enter it. Return
|
||||
True on success."""
|
||||
self.show_seed(seed)
|
||||
@wizard_dialog
|
||||
def enter_seed_dialog(self, run_next, title, message, is_valid):
|
||||
self.app.clipboard().clear()
|
||||
self.verify_seed(seed, is_valid)
|
||||
return self.text_input(title, message, is_valid)
|
||||
|
||||
@wizard_dialog
|
||||
def show_seed_dialog(self, run_next, message, seed_text):
|
||||
slayout = SeedWarningLayout(seed_text)
|
||||
self.set_main_layout(slayout.layout())
|
||||
return seed_text
|
||||
|
||||
def pw_layout(self, msg, kind):
|
||||
playout = PasswordLayout(None, msg, kind, self.next_button)
|
||||
self.set_main_layout(playout.layout())
|
||||
return playout.new_password()
|
||||
|
||||
def request_passphrase(self, device_text):
|
||||
@wizard_dialog
|
||||
def request_passphrase(self, device_text, run_next):
|
||||
"""When restoring a wallet, request the passphrase that was used for
|
||||
the wallet on the given device and confirm it. Should return
|
||||
a unicode string."""
|
||||
@@ -218,10 +268,11 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
|
||||
raise UserCancelled
|
||||
return phrase
|
||||
|
||||
def request_password(self, msg=None):
|
||||
@wizard_dialog
|
||||
def request_password(self, run_next):
|
||||
"""Request the user enter a new password and confirm it. Return
|
||||
the password or None for no password."""
|
||||
return self.pw_layout(msg or MSG_ENTER_PASSWORD, PW_NEW)
|
||||
return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW)
|
||||
|
||||
def show_restore(self, wallet, network):
|
||||
# FIXME: these messages are shown after the install wizard is
|
||||
@@ -244,85 +295,43 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
|
||||
"contain more addresses than displayed.")
|
||||
self.show_message(msg)
|
||||
|
||||
def create_addresses(self, wallet):
|
||||
def task():
|
||||
wallet.synchronize()
|
||||
self.emit(QtCore.SIGNAL('accept'))
|
||||
t = threading.Thread(target = task)
|
||||
t.start()
|
||||
def confirm(self, msg):
|
||||
vbox = QVBoxLayout()
|
||||
vbox.addWidget(WWLabel(msg))
|
||||
self.set_main_layout(vbox)
|
||||
|
||||
@wizard_dialog
|
||||
def action_dialog(self, action, run_next):
|
||||
self.run(action)
|
||||
|
||||
def terminate(self):
|
||||
self.wallet.start_threads(self.network)
|
||||
self.emit(QtCore.SIGNAL('accept'))
|
||||
|
||||
def waiting_dialog(self, task, msg):
|
||||
self.please_wait.setText(MSG_GENERATING_WAIT)
|
||||
self.refresh_gui()
|
||||
t = threading.Thread(target = task)
|
||||
t.start()
|
||||
|
||||
def query_create_or_restore(self, wallet_kinds):
|
||||
"""Ask the user what they want to do, and which wallet kind.
|
||||
wallet_kinds is an array of translated wallet descriptions.
|
||||
Return a a tuple (action, kind_index). Action is 'create' or
|
||||
'restore', and kind the index of the wallet kind chosen."""
|
||||
|
||||
actions = [_("Create a new wallet"),
|
||||
_("Restore a wallet from seed words or from keys")]
|
||||
title = _("Electrum could not find an existing wallet.")
|
||||
actions_clayout = ChoicesLayout(_("What do you want to do?"), actions)
|
||||
wallet_clayout = ChoicesLayout(_("Wallet kind:"), wallet_kinds)
|
||||
|
||||
@wizard_dialog
|
||||
def choice_dialog(self, title, message, choices, run_next):
|
||||
c_values = map(lambda x: x[0], choices)
|
||||
c_titles = map(lambda x: x[1], choices)
|
||||
clayout = ChoicesLayout(message, c_titles)
|
||||
vbox = QVBoxLayout()
|
||||
vbox.addLayout(actions_clayout.layout())
|
||||
vbox.addLayout(wallet_clayout.layout())
|
||||
vbox.addLayout(clayout.layout())
|
||||
self.set_main_layout(vbox, title)
|
||||
action = c_values[clayout.selected_index()]
|
||||
return action
|
||||
|
||||
action = ['create', 'restore'][actions_clayout.selected_index()]
|
||||
return action, wallet_clayout.selected_index()
|
||||
|
||||
def query_hw_wallet_choice(self, msg, choices):
|
||||
@wizard_dialog
|
||||
def show_xpub_dialog(self, xpub, run_next):
|
||||
vbox = QVBoxLayout()
|
||||
if choices:
|
||||
wallet_clayout = ChoicesLayout(msg, choices)
|
||||
vbox.addLayout(wallet_clayout.layout())
|
||||
else:
|
||||
vbox.addWidget(QLabel(msg, wordWrap=True))
|
||||
self.set_main_layout(vbox, next_enabled=len(choices) != 0)
|
||||
return wallet_clayout.selected_index() if choices else 0
|
||||
|
||||
def request_many(self, n, xpub_hot=None):
|
||||
vbox = QVBoxLayout()
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setFrameShape(QFrame.NoFrame)
|
||||
vbox.addWidget(scroll)
|
||||
|
||||
w = QWidget()
|
||||
innerVbox = QVBoxLayout(w)
|
||||
scroll.setWidget(w)
|
||||
|
||||
entries = []
|
||||
|
||||
if xpub_hot:
|
||||
layout = SeedDisplayLayout(xpub_hot, title=MSG_SHOW_MPK, sid='hot')
|
||||
else:
|
||||
layout = SeedInputLayout(title=MSG_ENTER_SEED_OR_MPK, sid='hot')
|
||||
entries.append(layout.seed_edit())
|
||||
innerVbox.addLayout(layout.layout())
|
||||
|
||||
for i in range(n):
|
||||
msg = MSG_COSIGNER % (i + 1) if xpub_hot else MSG_ENTER_SEED_OR_MPK
|
||||
layout = SeedInputLayout(title=msg, sid='cold')
|
||||
innerVbox.addLayout(layout.layout())
|
||||
entries.append(layout.seed_edit())
|
||||
|
||||
def get_texts():
|
||||
return [clean_text(entry) for entry in entries]
|
||||
def set_enabled():
|
||||
texts = get_texts()
|
||||
is_valid = Wallet.is_xpub if xpub_hot else Wallet.is_any
|
||||
all_valid = all(is_valid(text) for text in texts)
|
||||
if xpub_hot:
|
||||
texts.append(xpub_hot)
|
||||
has_dups = len(set(texts)) < len(texts)
|
||||
self.next_button.setEnabled(all_valid and not has_dups)
|
||||
for e in entries:
|
||||
e.textChanged.connect(set_enabled)
|
||||
self.set_main_layout(vbox, next_enabled=False)
|
||||
return get_texts()
|
||||
layout = SeedDisplayLayout(xpub, title=MSG_SHOW_MPK, sid='hot')
|
||||
vbox.addLayout(layout.layout())
|
||||
self.set_main_layout(vbox, MSG_SHOW_MPK)
|
||||
return None
|
||||
|
||||
def choose_server(self, network):
|
||||
title = _("Electrum communicates with remote servers to get "
|
||||
@@ -335,7 +344,6 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
|
||||
choices_title = _("How do you want to connect to a server? ")
|
||||
clayout = ChoicesLayout(choices_title, choices)
|
||||
self.set_main_layout(clayout.layout(), title)
|
||||
|
||||
auto_connect = True
|
||||
if clayout.selected_index() == 1:
|
||||
nlayout = NetworkChoiceLayout(network, self.config, wizard=True)
|
||||
@@ -345,12 +353,8 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
|
||||
self.config.set_key('auto_connect', auto_connect, True)
|
||||
network.auto_connect = auto_connect
|
||||
|
||||
def query_choice(self, msg, choices):
|
||||
clayout = ChoicesLayout(msg, choices)
|
||||
self.set_main_layout(clayout.layout(), next_enabled=bool(choices))
|
||||
return clayout.selected_index()
|
||||
|
||||
def query_multisig(self, action):
|
||||
@wizard_dialog
|
||||
def multisig_dialog(self, run_next):
|
||||
cw = CosignWidget(2, 2)
|
||||
m_edit = QSlider(Qt.Horizontal, self)
|
||||
n_edit = QSlider(Qt.Horizontal, self)
|
||||
@@ -360,7 +364,6 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
|
||||
m_edit.setMaximum(2)
|
||||
n_edit.setValue(2)
|
||||
m_edit.setValue(2)
|
||||
|
||||
n_label = QLabel()
|
||||
m_label = QLabel()
|
||||
grid = QGridLayout()
|
||||
@@ -379,14 +382,11 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
|
||||
m_edit.valueChanged.connect(on_m)
|
||||
on_n(2)
|
||||
on_m(2)
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
vbox.addWidget(cw)
|
||||
vbox.addWidget(WWLabel(_("Choose the number of signatures needed "
|
||||
"to unlock funds in your wallet:")))
|
||||
vbox.addWidget(WWLabel(_("Choose the number of signatures needed to unlock funds in your wallet:")))
|
||||
vbox.addLayout(grid)
|
||||
self.set_main_layout(vbox, _("Multi-Signature Wallet"))
|
||||
m = int(m_edit.value())
|
||||
n = int(n_edit.value())
|
||||
wallet_type = '%dof%d'%(m,n)
|
||||
return wallet_type
|
||||
return (m, n)
|
||||
|
||||
Reference in New Issue
Block a user