URI: 
       tFinish wizard unification - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit e7d25faf028543760f7ae114c395a4400c507636
   DIR parent 97dc130e26b36af84f5fb54b468d867423243c97
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Mon, 20 Jun 2016 16:25:11 +0200
       
       Finish wizard unification
       
       Diffstat:
         M gui/kivy/__init__.py                |       1 +
         M gui/kivy/main_window.py             |      41 ++++++++++++++-----------------
         M gui/kivy/uix/dialogs/installwizard… |      97 +++++++++++++++++++------------
         M gui/qt/__init__.py                  |      15 ++++++++-------
         M gui/qt/installwizard.py             |     276 +++++++++++++++---------------
         M lib/base_wizard.py                  |     236 +++++++++++++++++++++----------
         M lib/daemon.py                       |      36 ++++++++++++++++---------------
         D lib/wizard.py                       |     328 -------------------------------
         M plugins/hw_wallet/plugin.py         |      18 ++++++++++++------
         M plugins/trustedcoin/qt.py           |      11 +++--------
         M plugins/trustedcoin/trustedcoin.py  |      39 +++++++++++++++++++++----------
       
       11 files changed, 450 insertions(+), 648 deletions(-)
       ---
   DIR diff --git a/gui/kivy/__init__.py b/gui/kivy/__init__.py
       t@@ -45,6 +45,7 @@ class ElectrumGui:
        
            def __init__(self, config, daemon, plugins):
                Logger.debug('ElectrumGUI: initialising')
       +        self.daemon = daemon
                self.network = daemon.network
                self.config = config
                self.plugins = plugins
   DIR diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py
       t@@ -199,8 +199,8 @@ class ElectrumWindow(App):
                self.plugins = kwargs.get('plugins', [])
        
                self.gui_object = kwargs.get('gui_object', None)
       +        self.daemon = self.gui_object.daemon
        
       -        #self.config = self.gui_object.config
                self.contacts = Contacts(self.electrum_config)
                self.invoices = InvoiceStore(self.electrum_config)
        
       t@@ -408,36 +408,32 @@ class ElectrumWindow(App):
                else:
                    return ''
        
       -    def load_wallet_by_name(self, wallet_path):
       -        if not wallet_path:
       -            return
       -        config = self.electrum_config
       -        try:
       -            storage = WalletStorage(wallet_path)
       -        except IOError:
       -            self.show_error("Cannot read wallet file")
       +    def on_wizard_complete(self, instance, wallet):
       +        if wallet:
       +            self.daemon.add_wallet(wallet)
       +            self.load_wallet(wallet)
       +        self.on_resume()
       +
       +    def load_wallet_by_name(self, path):
       +        if not path:
                    return
       -        if storage.file_exists:
       -            wallet = Wallet(storage)
       -            action = wallet.get_action()
       +        wallet = self.daemon.load_wallet(path)
       +        if wallet:
       +            self.load_wallet(wallet)
       +            self.on_resume()
                else:
       -            action = 'new'
       -        if action is not None:
       -            # start installation wizard
                    Logger.debug('Electrum: Wallet not found. Launching install wizard')
       -            wizard = Factory.InstallWizard(config, self.network, storage)
       -            wizard.bind(on_wizard_complete=lambda instance, wallet: self.load_wallet(wallet))
       +            wizard = Factory.InstallWizard(self.electrum_config, self.network, path)
       +            wizard.bind(on_wizard_complete=self.on_wizard_complete)
       +            action = wizard.get_action()
                    wizard.run(action)
       -        else:
       -            self.load_wallet(wallet)
       -        self.on_resume()
        
            def on_stop(self):
                self.stop_wallet()
        
            def stop_wallet(self):
                if self.wallet:
       -            self.wallet.stop_threads()
       +            self.daemon.stop_wallet(self.wallet.storage.path)
                    self.wallet = None
        
            def on_key_down(self, instance, key, keycode, codepoint, modifiers):
       t@@ -539,9 +535,10 @@ class ElectrumWindow(App):
        
            @profiler
            def load_wallet(self, wallet):
       +        print "load wallet", wallet.storage.path
       +
                self.stop_wallet()
                self.wallet = wallet
       -        self.wallet.start_threads(self.network)
                self.current_account = self.wallet.storage.get('current_account', None)
                self.update_wallet()
                # Once GUI has been initialized check if we want to announce something
   DIR diff --git a/gui/kivy/uix/dialogs/installwizard.py b/gui/kivy/uix/dialogs/installwizard.py
       t@@ -24,7 +24,9 @@ from password_dialog import PasswordDialog
        
        from electrum.base_wizard import BaseWizard
        
       -
       +is_test = True
       +test_seed = "time taxi field recycle tiny license olive virus report rare steel portion achieve"
       +test_xpub = "xpub661MyMwAqRbcEbvVtRRSjqxVnaWVUMewVzMiURAKyYratih4TtBpMypzzefmv8zUNebmNVzB3PojdC5sV2P9bDgMoo9B3SARw1MXUUfU1GL"
        
        Builder.load_string('''
        #:import Window kivy.core.window.Window
       t@@ -152,7 +154,7 @@ Builder.load_string('''
        
        
        <WizardChoiceDialog>
       -    msg : ''
       +    message : ''
            Widget:
                size_hint: 1, 1
            Label:
       t@@ -160,7 +162,7 @@ Builder.load_string('''
                size_hint: 1, None
                text_size: self.width, None
                height: self.texture_size[1]
       -        text: root.msg
       +        text: root.message
            Widget
                size_hint: 1, 1
            GridLayout:
       t@@ -408,11 +410,12 @@ class WizardDialog(EventsDialog):
            '''
            crcontent = ObjectProperty(None)
        
       -    def __init__(self, **kwargs):
       +    def __init__(self, wizard, **kwargs):
                super(WizardDialog, self).__init__(**kwargs)
       -        #self.action = kwargs.get('action')
       +        self.wizard = wizard
       +        self.ids.back.disabled = not wizard.can_go_back()
       +        self.app = App.get_running_app()
                self.run_next = kwargs['run_next']
       -        self.run_prev = kwargs['run_prev']
                _trigger_size_dialog = Clock.create_trigger(self._size_dialog)
                Window.bind(size=_trigger_size_dialog,
                            rotation=_trigger_size_dialog)
       t@@ -443,7 +446,7 @@ class WizardDialog(EventsDialog):
                    app.stop()
        
            def get_params(self, button):
       -        return ()
       +        return (None,)
        
            def on_release(self, button):
                self._on_release = True
       t@@ -452,7 +455,7 @@ class WizardDialog(EventsDialog):
                    self.parent.dispatch('on_wizard_complete', None)
                    return
                if button is self.ids.back:
       -            self.run_prev()
       +            self.wizard.go_back()
                    return
                params = self.get_params(button)
                self.run_next(*params)
       t@@ -467,13 +470,13 @@ class WizardMultisigDialog(WizardDialog):
        
        class WizardChoiceDialog(WizardDialog):
        
       -    def __init__(self, **kwargs):
       -        super(WizardChoiceDialog, self).__init__(**kwargs)
       -        self.msg = kwargs.get('msg', '')
       +    def __init__(self, wizard, **kwargs):
       +        super(WizardChoiceDialog, self).__init__(wizard, **kwargs)
       +        self.message = kwargs.get('message', '')
                choices = kwargs.get('choices', [])
                layout = self.ids.choices
                layout.bind(minimum_height=layout.setter('height'))
       -        for text, action in choices:
       +        for action, text in choices:
                    l = WizardButton(text=text)
                    l.action = action
                    l.height = '48dp'
       t@@ -508,17 +511,18 @@ class WordButton(Button):
        class WizardButton(Button):
            pass
        
       +
        class RestoreSeedDialog(WizardDialog):
        
            message = StringProperty('')
        
       -    def __init__(self, **kwargs):
       -        super(RestoreSeedDialog, self).__init__(**kwargs)
       -        self._test = kwargs['test']
       +    def __init__(self, wizard, **kwargs):
       +        super(RestoreSeedDialog, self).__init__(wizard, **kwargs)
       +        self._test = kwargs['is_valid']
                from electrum.mnemonic import Mnemonic
                from electrum.old_mnemonic import words as old_wordlist
                self.words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
       -        self.ids.text_input_seed.text = ''
       +        self.ids.text_input_seed.text = test_seed if is_test else ''
        
            def get_suggestions(self, prefix):
                for w in self.words:
       t@@ -616,9 +620,8 @@ class RestoreSeedDialog(WizardDialog):
        
        class ShowXpubDialog(WizardDialog):
        
       -    def __init__(self, **kwargs):
       -        WizardDialog.__init__(self, **kwargs)
       -        self.app = App.get_running_app()
       +    def __init__(self, wizard, **kwargs):
       +        WizardDialog.__init__(self, wizard, **kwargs)
                self.xpub = kwargs['xpub']
                self.ids.next.disabled = False
        
       t@@ -636,15 +639,14 @@ class ShowXpubDialog(WizardDialog):
        
        class AddXpubDialog(WizardDialog):
        
       -    def __init__(self, **kwargs):
       -        WizardDialog.__init__(self, **kwargs)
       -        self.app = App.get_running_app()
       -        self._test = kwargs['test']
       +    def __init__(self, wizard, **kwargs):
       +        WizardDialog.__init__(self, wizard, **kwargs)
       +        self.is_valid = kwargs['is_valid']
                self.title = kwargs['title']
                self.message = kwargs['message']
        
            def check_text(self, dt):
       -        self.ids.next.disabled = not bool(self._test(self.get_text()))
       +        self.ids.next.disabled = not bool(self.is_valid(self.get_text()))
        
            def get_text(self):
                ti = self.ids.text_input
       t@@ -659,7 +661,7 @@ class AddXpubDialog(WizardDialog):
                self.app.scan_qr(on_complete)
        
            def do_paste(self):
       -        self.ids.text_input.text = unicode(self.app._clipboard.paste())
       +        self.ids.text_input.text = test_xpub if is_test else unicode(self.app._clipboard.paste())
        
            def do_clear(self):
                self.ids.text_input.text = ''
       t@@ -681,7 +683,7 @@ class InstallWizard(BaseWizard, Widget):
                """overriden by main_window"""
                pass
        
       -    def waiting_dialog(self, task, msg, on_complete=None):
       +    def waiting_dialog(self, task, msg):
                '''Perform a blocking task in the background by running the passed
                method in a thread.
                '''
       t@@ -693,8 +695,6 @@ class InstallWizard(BaseWizard, Widget):
                        Clock.schedule_once(lambda dt: app.show_error(str(err)))
                    # on  completion hide message
                    Clock.schedule_once(lambda dt: app.info_bubble.hide(now=True), -1)
       -            if on_complete:
       -                on_complete()
        
                app.show_info_bubble(
                    text=msg, icon='atlas://gui/kivy/theming/light/important',
       t@@ -702,17 +702,42 @@ class InstallWizard(BaseWizard, Widget):
                t = threading.Thread(target = target)
                t.start()
        
       -    def choice_dialog(self, **kwargs): WizardChoiceDialog(**kwargs).open()
       -    def multisig_dialog(self, **kwargs): WizardMultisigDialog(**kwargs).open()
       -    def show_seed_dialog(self, **kwargs): ShowSeedDialog(**kwargs).open()
       -    def restore_seed_dialog(self, **kwargs): RestoreSeedDialog(**kwargs).open()
       -    def add_xpub_dialog(self, **kwargs): AddXpubDialog(**kwargs).open()
       -    def show_xpub_dialog(self, **kwargs): ShowXpubDialog(**kwargs).open()
       +    def terminate(self, **kwargs):
       +        self.wallet.start_threads(self.network)
       +        self.dispatch('on_wizard_complete', self.wallet)
       +
       +    def choice_dialog(self, **kwargs): WizardChoiceDialog(self, **kwargs).open()
       +    def multisig_dialog(self, **kwargs): WizardMultisigDialog(self, **kwargs).open()
       +    def show_seed_dialog(self, **kwargs): ShowSeedDialog(self, **kwargs).open()
       +    def enter_seed_dialog(self, **kwargs): RestoreSeedDialog(self, **kwargs).open()
       +    def add_xpub_dialog(self, **kwargs): AddXpubDialog(self, **kwargs).open()
       +    def show_xpub_dialog(self, **kwargs): ShowXpubDialog(self, **kwargs).open()
       +
       +    def show_error(self, msg):
       +        app.show_error(msg, duration=0.5)
        
            def password_dialog(self, message, callback):
                popup = PasswordDialog()
                popup.init(message, callback)
                popup.open()
        
       -    def show_error(self, msg):
       -        app.show_error(msg, duration=0.5)
       +    def request_password(self, run_next):
       +        def callback(pin):
       +            if pin:
       +                self.run('confirm_password', (pin, run_next))
       +            else:
       +                run_next(None)
       +        self.password_dialog('Choose a PIN code', callback)
       +
       +    def confirm_password(self, pin, run_next):
       +        def callback(conf):
       +            if conf == pin:
       +                run_next(pin)
       +            else:
       +                self.show_error(_('PIN mismatch'))
       +                self.run('request_password', (run_next,))
       +        self.password_dialog('Confirm your PIN code', callback)
       +
       +    def action_dialog(self, action, run_next):
       +        f = getattr(self, action)
       +        f()
   DIR diff --git a/gui/qt/__init__.py b/gui/qt/__init__.py
       t@@ -149,9 +149,6 @@ class ElectrumGui:
                run_hook('on_new_window', w)
                return w
        
       -    def get_wizard(self):
       -        return InstallWizard(self.config, self.app, self.plugins)
       -
            def start_new_window(self, path, uri):
                '''Raises the window for the wallet if it is open.  Otherwise
                opens the wallet and creates a new window for it.'''
       t@@ -160,14 +157,18 @@ class ElectrumGui:
                        w.bring_to_top()
                        break
                else:
       -            wallet = self.daemon.load_wallet(path, self.get_wizard)
       +            wallet = self.daemon.load_wallet(path)
                    if not wallet:
       -                return
       +                wizard = InstallWizard(self.config, self.app, self.plugins, self.daemon.network, path)
       +                wallet = wizard.run_and_get_wallet()
       +                if not wallet:
       +                    return
       +                if wallet.get_action():
       +                    return
       +                self.daemon.add_wallet(wallet)
                    w = self.create_window_for_wallet(wallet)
       -
                if uri:
                    w.pay_to_URI(uri)
       -
                return w
        
            def close_window(self, window):
   DIR diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
       t@@ -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
       t@@ -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()
       t@@ -63,14 +76,42 @@ class CosignWidget(QWidget):
                qp.end()
        
        
       +
       +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, WizardBase):
       +class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
       +
       +    def __init__(self, config, app, plugins, network, storage):
        
       -    def __init__(self, config, app, plugins):
       +        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')
       t@@ -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()
       t@@ -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()
       t@@ -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()
       t@@ -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)
       t@@ -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())
       -
       -    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'))
       +    @wizard_dialog
       +    def add_xpub_dialog(self, title, message, is_valid, run_next):
       +        return self.text_input(title, message, is_valid)
        
       -    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."""
       t@@ -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
       t@@ -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()
       -        self.please_wait.setText(MSG_GENERATING_WAIT)
       -        self.refresh_gui()
       -
       -    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."""
       +    def confirm(self, msg):
       +        vbox = QVBoxLayout()
       +        vbox.addWidget(WWLabel(msg))
       +        self.set_main_layout(vbox)
        
       -        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 action_dialog(self, action, run_next):
       +        self.run(action)
        
       -        vbox = QVBoxLayout()
       -        vbox.addLayout(actions_clayout.layout())
       -        vbox.addLayout(wallet_clayout.layout())
       -        self.set_main_layout(vbox, title)
       +    def terminate(self):
       +        self.wallet.start_threads(self.network)
       +        self.emit(QtCore.SIGNAL('accept'))
        
       -        action = ['create', 'restore'][actions_clayout.selected_index()]
       -        return action, wallet_clayout.selected_index()
       +    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_hw_wallet_choice(self, msg, choices):
       +    @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()
       -        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
       +        vbox.addLayout(clayout.layout())
       +        self.set_main_layout(vbox, title)
       +        action = c_values[clayout.selected_index()]
       +        return action
        
       -    def request_many(self, n, xpub_hot=None):
       +    @wizard_dialog
       +    def show_xpub_dialog(self, xpub, run_next):
                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 "
       t@@ -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)
       t@@ -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)
       t@@ -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()
       t@@ -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)
   DIR diff --git a/lib/base_wizard.py b/lib/base_wizard.py
       t@@ -24,144 +24,232 @@
        # SOFTWARE.
        
        import os
       -from electrum.wallet import Wallet, Multisig_Wallet
       +from electrum.wallet import Wallet, Multisig_Wallet, WalletStorage
        from i18n import _
        
        
        class BaseWizard(object):
        
       -    def __init__(self, config, network, storage):
       +    def __init__(self, config, network, path):
                super(BaseWizard, self).__init__()
                self.config  = config
                self.network = network
       -        self.storage = storage
       +        self.storage = WalletStorage(path)
                self.wallet = None
       +        self.stack = []
        
            def run(self, action, *args):
       -        '''Entry point of our Installation wizard'''
       +        self.stack.append((action, args))
                if not action:
                    return
       -        if hasattr(self, action):
       +        if hasattr(self.wallet, 'plugin'):
       +            if hasattr(self.wallet.plugin, action):
       +                f = getattr(self.wallet.plugin, action)
       +                apply(f, (self.wallet, self) + args)
       +        elif hasattr(self, action):
                    f = getattr(self, action)
                    apply(f, *args)
                else:
                    raise BaseException("unknown action", action)
        
       +    def get_action(self):
       +        if self.storage.file_exists:
       +            self.wallet = Wallet(self.storage)
       +            action = self.wallet.get_action()
       +        else:
       +            action = 'new'
       +        return action
       +
       +    def get_wallet(self):
       +        if self.wallet and self.wallet.get_action() is None:
       +            return self.wallet
       +
       +    def can_go_back(self):
       +        return len(self.stack)>1
       +
       +    def go_back(self):
       +        if not self.can_go_back():
       +            return
       +        self.stack.pop()
       +        action, args = self.stack.pop()
       +        self.run(action, *args)
       +
       +    def run_wallet(self):
       +        self.stack = []
       +        action = self.wallet.get_action()
       +        if action:
       +            self.action_dialog(action=action, run_next=lambda x: self.run_wallet())
       +
            def new(self):
                name = os.path.basename(self.storage.path)
       -        msg = "\n".join([
       -            _("Welcome to the Electrum installation wizard."),
       +        title = _("Welcome to the Electrum installation wizard.")
       +        message = '\n'.join([
                    _("The wallet '%s' does not exist.") % name,
                    _("What kind of wallet do you want to create?")
                ])
       -        choices = [
       -            (_('Standard wallet'), 'create_standard'),
       -            (_('Multi-signature wallet'), 'create_multisig'),
       +        wallet_kinds = [
       +            ('standard',  _("Standard wallet")),
       +            ('twofactor', _("Wallet with two-factor authentication")),
       +            ('multisig',  _("Multi-signature wallet")),
       +            ('hardware',  _("Hardware wallet")),
                ]
       -        self.choice_dialog(msg=msg, choices=choices, run_prev=self.cancel, run_next=self.run)
       +        registered_kinds = Wallet.categories()
       +        choices = [pair for pair in wallet_kinds if pair[0] in registered_kinds]
       +        self.choice_dialog(title = title, message=message, choices=choices, run_next=self.on_wallet_type)
       +
       +    def on_wallet_type(self, choice):
       +        self.wallet_type = choice
       +        if choice == 'standard':
       +            action = 'choose_seed'
       +        elif choice == 'multisig':
       +            action = 'choose_multisig'
       +        elif choice == 'hardware':
       +            action = 'choose_hw'
       +        elif choice == 'twofactor':
       +            action = 'choose_seed'
       +        self.run(action)
       +
       +    def choose_multisig(self):
       +        def on_multisig(m, n):
       +            self.multisig_type = "%dof%d"%(m, n)
       +            self.run('choose_seed')
       +        self.multisig_dialog(run_next=on_multisig)
        
            def choose_seed(self):
       -        msg = ' '.join([
       -            _("Do you want to create a new seed, or to restore a wallet using an existing seed?")
       -        ])
       -        choices = [
       -            (_('Create a new seed'), 'create_seed'),
       -            (_('I already have a seed'), 'restore_seed'),
       -            (_('Watching-only wallet'), 'restore_xpub')
       -        ]
       -        self.choice_dialog(msg=msg, choices=choices, run_prev=self.new, run_next=self.run)
       -
       -    def create_multisig(self):
       -        def f(m, n):
       -            self.wallet_type = "%dof%d"%(m, n)
       -            self.run('choose_seed')
       -        name = os.path.basename(self.storage.path)
       -        self.multisig_dialog(run_prev=self.new, run_next=f)
       +        title = _('Private Keys')
       +        message = _("Do you want to create a new seed, or to restore a wallet using an existing seed?")
       +        if self.wallet_type == 'standard':
       +            choices = [
       +                ('create_seed', _('Create a new seed')),
       +                ('restore_seed', _('I already have a seed')),
       +                ('restore_xpub', _('Watching-only wallet')),
       +            ]
       +        elif self.wallet_type == 'twofactor':
       +            choices = [
       +                ('create_2fa', _('Create a new seed')),
       +                ('restore_2fa', _('I already have a seed')),
       +            ]
       +        elif self.wallet_type == 'multisig':
       +            choices = [
       +                ('create_seed', _('Create a new seed')),
       +                ('restore_seed', _('I already have a seed')),
       +                ('restore_xpub', _('Watching-only wallet')),
       +                ('choose_hw', _('Cosign with hardware wallet')),
       +            ]
       +        self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
       +
       +    def create_2fa(self):
       +        print 'create 2fa'
       +        self.storage.put('wallet_type', '2fa')
       +        self.wallet = Wallet(self.storage)
       +        self.run_wallet()
        
            def restore_seed(self):
                msg = _('Please type your seed phrase using the virtual keyboard.')
       -        self.restore_seed_dialog(run_prev=self.new, run_next=self.enter_pin, test=Wallet.is_seed, message=msg)
       +        title = _('Enter Seed')
       +        self.enter_seed_dialog(run_next=self.add_password, title=title, message=msg, is_valid=Wallet.is_seed)
        
            def restore_xpub(self):
                title = "MASTER PUBLIC KEY"
       -        message =  _('To create a watching-only wallet, paste your master public key, or scan it using the camera button.')
       -        self.add_xpub_dialog(run_prev=self.new, run_next=lambda xpub: self.create_wallet(xpub, None), title=title, message=message, test=Wallet.is_mpk)
       -
       -    def create_standard(self):
       -        self.wallet_type = 'standard'
       -        self.run('choose_seed')
       +        message = _('To create a watching-only wallet, paste your master public key, or scan it using the camera button.')
       +        self.add_xpub_dialog(run_next=lambda xpub: self.create_wallet(xpub, None), title=title, message=message, is_valid=Wallet.is_mpk)
       +
       +    def restore_2fa(self):
       +        self.storage.put('wallet_type', '2fa')
       +        self.wallet = Wallet(self.storage)
       +        self.wallet.plugin.on_restore_wallet(self.wallet, self)
       +
       +    def choose_hw(self):
       +        hw_wallet_types, choices = self.plugins.hardware_wallets('create')
       +        choices = zip(hw_wallet_types, choices)
       +        title = _('Hardware wallet')
       +        if choices:
       +            msg = _('Select the type of hardware wallet: ')
       +        else:
       +            msg = ' '.join([
       +                _('No hardware wallet support found on your system.'),
       +                _('Please install the relevant libraries (eg python-trezor for Trezor).'),
       +            ])
       +        self.choice_dialog(title=title, message=msg, choices=choices, run_next=self.on_hardware)
       +
       +    def on_hardware(self, hw_type):
       +        self.hw_type = hw_type
       +        if self.wallet_type == 'multisig':
       +            self.create_hardware_multisig()
       +        else:
       +            title = _('Hardware wallet') + ' [%s]' % hw_type
       +            message = _('Do you have a device, or do you want to restore a wallet using an existing seed?')
       +            choices = [
       +                ('create_hardware_wallet', _('I have a device')),
       +                ('restore_hardware_wallet', _('Use hardware wallet seed')),
       +            ]
       +            self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
       +
       +    def create_hardware_multisig(self):
       +        self.storage.put('wallet_type', self.multisig_type)
       +        self.wallet = Multisig_Wallet(self.storage)
       +        # todo: get the xpub from the plugin
       +        self.run('create_wallet', xpub, None)
       +
       +    def create_hardware_wallet(self):
       +        self.storage.put('wallet_type', self.hw_type)
       +        self.wallet = Wallet(self.storage)
       +        self.wallet.plugin.on_create_wallet(self.wallet, self)
       +        self.terminate()
       +
       +    def restore_hardware_wallet(self):
       +        self.storage.put('wallet_type', self.wallet_type)
       +        self.wallet = Wallet(self.storage)
       +        self.wallet.plugin.on_restore_wallet(self.wallet, self)
       +        self.terminate()
        
            def create_wallet(self, text, password):
                if self.wallet_type == 'standard':
                    self.wallet = Wallet.from_text(text, password, self.storage)
                    self.run('create_addresses')
       -        else:
       -            self.storage.put('wallet_type', self.wallet_type)
       +        elif self.wallet_type == 'multisig':
       +            self.storage.put('wallet_type', self.multisig_type)
                    self.wallet = Multisig_Wallet(self.storage)
                    self.wallet.add_seed(text, password)
                    self.wallet.create_master_keys(password)
       -            action = self.wallet.get_action()
       -            self.run(action)
       +            self.run_wallet()
        
            def add_cosigners(self):
                xpub = self.wallet.master_public_keys.get('x1/')
       -        self.show_xpub_dialog(run_prev=self.create_multisig, run_next=self.add_cosigner, xpub=xpub, test=Wallet.is_xpub)
       +        self.show_xpub_dialog(run_next=lambda x: self.add_cosigner(), xpub=xpub)
        
            def add_cosigner(self):
                def on_xpub(xpub):
                    self.wallet.add_cosigner(xpub)
                    i = self.wallet.get_missing_cosigner()
       -            action = 'add_cosigner' if i else 'create_main_account'
       +            action = 'add_cosigner' if i else 'create_addresses'
                    self.run(action)
       -        title = "ADD COSIGNER"
       +        i = self.wallet.get_missing_cosigner()
       +        title = _("Add Cosigner") + " %d"%(i-1)
                message = _('Please paste your cosigners master public key, or scan it using the camera button.')
       -        self.add_xpub_dialog(run_prev=self.add_cosigners, run_next=on_xpub, title=title, message=message, test=Wallet.is_xpub)
       -
       -    def create_main_account(self):
       -        self.wallet.create_main_account()
       -        self.run('create_addresses')
       +        self.add_xpub_dialog(run_next=on_xpub, title=title, message=message, is_valid=Wallet.is_any)
        
            def create_addresses(self):
                def task():
                    self.wallet.create_main_account()
                    self.wallet.synchronize()
       +            self.terminate()
                msg= _("Electrum is generating your addresses, please wait.")
       -        self.waiting_dialog(task, msg, on_complete=self.terminate)
       +        self.waiting_dialog(task, msg)
        
            def create_seed(self):
                from electrum.wallet import BIP32_Wallet
                seed = BIP32_Wallet.make_seed()
                msg = _("If you forget your PIN or lose your device, your seed phrase will be the "
                        "only way to recover your funds.")
       -        self.show_seed_dialog(run_prev=self.new, run_next=self.confirm_seed, message=msg, seed_text=seed)
       +        self.show_seed_dialog(run_next=self.confirm_seed, message=msg, seed_text=seed)
        
            def confirm_seed(self, seed):
                assert Wallet.is_seed(seed)
       +        title = _('Confirm Seed')
                msg = _('Please retype your seed phrase, to confirm that you properly saved it')
       -        self.restore_seed_dialog(run_prev=self.create_seed, run_next=self.enter_pin, test=lambda x: x==seed, message=msg)
       -
       -    def enter_pin(self, seed):
       -        def callback(pin):
       -            action = 'confirm_pin' if pin else 'create_wallet'
       -            self.run(action, (seed, pin))
       -        self.password_dialog('Choose a PIN code', callback)
       -
       -    def confirm_pin(self, seed, pin):
       -        def callback(conf):
       -            if conf == pin:
       -                self.run('create_wallet', (seed, pin))
       -            else:
       -                self.show_error(_('PIN mismatch'))
       -                self.run('enter_pin', (seed,))
       -        self.password_dialog('Confirm your PIN code', callback)
       -
       -    def terminate(self):
       -        self.wallet.start_threads(self.network)
       -        self.dispatch('on_wizard_complete', self.wallet)
       -
       -    def cancel(self):
       -        self.dispatch('on_wizard_complete', None)
       -        return True
       -
       -
       +        self.enter_seed_dialog(run_next=self.add_password, title=title, message=msg, is_valid=lambda x: x==seed)
        
       +    def add_password(self, seed):
       +        f = lambda x: self.create_wallet(seed, x)
       +        self.request_password(run_next=f)
   DIR diff --git a/lib/daemon.py b/lib/daemon.py
       t@@ -171,27 +171,29 @@ class Daemon(DaemonThread):
                    response = "Error: Electrum is running in daemon mode. Please stop the daemon first."
                return response
        
       -    def load_wallet(self, path, get_wizard=None):
       +    def load_wallet(self, path):
                if path in self.wallets:
                    wallet = self.wallets[path]
       -        else:
       -            storage = WalletStorage(path)
       -            if storage.file_exists:
       -                wallet = Wallet(storage)
       -                action = wallet.get_action()
       -            else:
       -                action = 'new'
       -            if action:
       -                if get_wizard is None:
       -                    return None
       -                wizard = get_wizard()
       -                wallet = wizard.run(self.network, storage)
       -            else:
       -                wallet.start_threads(self.network)
       -            if wallet:
       -                self.wallets[path] = wallet
       +            return wallet
       +        storage = WalletStorage(path)
       +        if not storage.file_exists:
       +            return
       +        wallet = Wallet(storage)
       +        action = wallet.get_action()
       +        if action:
       +            return
       +        wallet.start_threads(self.network)
       +        self.wallets[path] = wallet
                return wallet
        
       +    def add_wallet(self, wallet):
       +        path = wallet.storage.path
       +        self.wallets[path] = wallet
       +
       +    def stop_wallet(self, path):
       +        wallet = self.wallets.pop(path)
       +        wallet.stop_threads()
       +
            def run_cmdline(self, config_options):
                config = SimpleConfig(config_options)
                cmdname = config.get('cmd')
   DIR diff --git a/lib/wizard.py b/lib/wizard.py
       t@@ -1,328 +0,0 @@
       -#!/usr/bin/env python
       -#
       -# Electrum - lightweight Bitcoin client
       -# Copyright (C) 2015 thomasv@gitorious, kyuupichan@gmail
       -#
       -# Permission is hereby granted, free of charge, to any person
       -# obtaining a copy of this software and associated documentation files
       -# (the "Software"), to deal in the Software without restriction,
       -# including without limitation the rights to use, copy, modify, merge,
       -# publish, distribute, sublicense, and/or sell copies of the Software,
       -# and to permit persons to whom the Software is furnished to do so,
       -# subject to the following conditions:
       -#
       -# The above copyright notice and this permission notice shall be
       -# included in all copies or substantial portions of the Software.
       -#
       -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       -# SOFTWARE.
       -
       -from electrum import WalletStorage
       -from electrum.plugins import run_hook
       -from util import PrintError
       -from wallet import Wallet
       -from i18n import _
       -
       -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.")
       -
       -class WizardBase(PrintError):
       -    '''Base class for gui-specific install wizards.'''
       -    user_actions = ('create', 'restore')
       -    wallet_kinds = [
       -        ('standard',  _("Standard wallet")),
       -        ('twofactor', _("Wallet with two-factor authentication")),
       -        ('multisig',  _("Multi-signature wallet")),
       -        ('hardware',  _("Hardware wallet")),
       -    ]
       -
       -    # Derived classes must set:
       -    #   self.language_for_seed
       -    #   self.plugins
       -
       -    def show_error(self, msg):
       -        raise NotImplementedError
       -
       -    def show_warning(self, msg):
       -        raise NotImplementedError
       -
       -    def remove_from_recently_open(self, filename):
       -        """Remove filename from the recently used list."""
       -        raise NotImplementedError
       -
       -    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."""
       -        raise NotImplementedError
       -
       -    def query_multisig(self, action):
       -        """Asks the user what kind of multisig wallet they want.  Returns a
       -        string like "2of3".  Action is 'create' or 'restore'."""
       -        raise NotImplementedError
       -
       -    def query_choice(self, msg, choices):
       -        """Asks the user which of several choices they would like.
       -        Return the index of the choice."""
       -        raise NotImplementedError
       -
       -    def query_hw_wallet_choice(self, msg, action, choices):
       -        """Asks the user which hardware wallet kind they are using.  Action is
       -        'create' or 'restore' from the initial screen.  As this is
       -        confusing for hardware wallets ask a new question with the
       -        three possibilities Initialize ('create'), Use ('create') or
       -        Restore a software-only wallet ('restore').  Return a pair
       -        (action, choice)."""
       -        raise NotImplementedError
       -
       -    def show_and_verify_seed(self, seed):
       -        """Show the user their seed.  Ask them to re-enter it.  Return
       -        True on success."""
       -        raise NotImplementedError
       -
       -    def request_passphrase(self, device_text):
       -        """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."""
       -        raise NotImplementedError
       -
       -    def request_password(self, msg=None):
       -        """Request the user enter a new password and confirm it.  Return
       -        the password or None for no password."""
       -        raise NotImplementedError
       -
       -    def request_seed(self, msg, is_valid=None):
       -        """Request the user enter a seed.  Returns the seed the user entered.
       -        is_valid is a function that returns True if a seed is valid, for
       -        dynamic feedback.  If not provided, Wallet.is_any is used."""
       -        raise NotImplementedError
       -
       -    def request_many(self, n, xpub_hot=None):
       -        """If xpub_hot is provided, a new wallet is being created.  Request N
       -        master public keys for cosigners; xpub_hot is the master xpub
       -        key for the wallet.
       -
       -        If xpub_hot is None, request N cosigning master xpub keys,
       -        xprv keys, or seeds in order to perform wallet restore."""
       -        raise NotImplementedError
       -
       -    def choose_server(self, network):
       -        """Choose a server if one is not set in the config anyway."""
       -        raise NotImplementedError
       -
       -    def show_restore(self, wallet, network):
       -        """Show restore result"""
       -        pass
       -
       -    def finished(self):
       -        """Called when the wizard is done."""
       -        pass
       -
       -    def run(self, network, storage):
       -        '''The main entry point of the wizard.  Open a wallet from the given
       -        filename.  If the file doesn't exist launch the GUI-specific
       -        install wizard proper, created by calling create_wizard().'''
       -        need_sync = False
       -        is_restore = False
       -
       -        if storage.file_exists:
       -            wallet = Wallet(storage)
       -            if wallet.imported_keys:
       -                self.update_wallet_format(wallet)
       -            action = wallet.get_action()
       -            if action != 'new':
       -                self.hide()
       -                path = 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()
       -        else:
       -            cr, wallet = self.create_or_restore(storage)
       -            if not wallet:
       -                return
       -            need_sync = True
       -            is_restore = (cr == 'restore')
       -
       -        while True:
       -            action = wallet.get_action()
       -            if not action:
       -                break
       -            need_sync = True
       -            self.run_wallet_action(wallet, action)
       -            # Save the wallet after each action
       -            wallet.storage.write()
       -
       -        if network:
       -            # Show network dialog if config does not exist
       -            if self.config.get('auto_connect') is None:
       -                self.choose_server(network)
       -        else:
       -            self.show_warning(_('You are offline'))
       -
       -        if need_sync:
       -            self.create_addresses(wallet)
       -
       -        # start wallet threads
       -        if network:
       -            wallet.start_threads(network)
       -
       -        if is_restore:
       -            self.show_restore(wallet, network)
       -
       -        self.finished()
       -
       -        return wallet
       -
       -    def run_wallet_action(self, wallet, action):
       -        self.print_error("action %s on %s" % (action, wallet.basename()))
       -        # Run the action on the wallet plugin, if any, then the
       -        # wallet and finally ourselves
       -        calls = []
       -        if hasattr(wallet, 'plugin'):
       -            calls.append((wallet.plugin, (wallet, self)))
       -        calls.extend([(wallet, ()), (self, (wallet, ))])
       -        calls = [(getattr(actor, action), args) for (actor, args) in calls
       -                 if hasattr(actor, action)]
       -        if not calls:
       -            raise RuntimeError("No handler found for %s action" % action)
       -        for method, args in calls:
       -            method(*args)
       -
       -    def create_or_restore(self, storage):
       -        '''After querying the user what they wish to do, create or restore
       -        a wallet and return it.'''
       -        self.remove_from_recently_open(storage.path)
       -
       -        # Filter out any unregistered wallet kinds
       -        registered_kinds = Wallet.categories()
       -        kinds, descriptions = zip(*[pair for pair in WizardBase.wallet_kinds
       -                                    if pair[0] in registered_kinds])
       -        action, kind_index = self.query_create_or_restore(descriptions)
       -        assert action in WizardBase.user_actions
       -        kind = kinds[kind_index]
       -        if kind == 'multisig':
       -            wallet_type = self.query_multisig(action)
       -        elif kind == 'hardware':
       -            hw_wallet_types, choices = self.plugins.hardware_wallets(action)
       -            if choices:
       -                msg = _('Select the type of hardware wallet: ')
       -            else:
       -                msg = ' '.join([
       -                    _('No hardware wallet support found on your system.'),
       -                    _('Please install the relevant libraries (eg python-trezor for Trezor).'),
       -                ])
       -            choice = self.query_hw_wallet_choice(msg, choices)
       -            wallet_type = hw_wallet_types[choice]
       -        elif kind == 'twofactor':
       -            wallet_type = '2fa'
       -        else:
       -            wallet_type = 'standard'
       -
       -        if action == 'create':
       -            wallet = self.create_wallet(storage, wallet_type, kind)
       -        else:
       -            wallet = self.restore_wallet(storage, wallet_type, kind)
       -
       -        return action, wallet
       -
       -    def construct_wallet(self, storage, wallet_type):
       -        storage.put('wallet_type', wallet_type)
       -        return Wallet(storage)
       -
       -    def create_wallet(self, storage, wallet_type, kind):
       -        wallet = self.construct_wallet(storage, wallet_type)
       -        if kind == 'hardware':
       -            wallet.plugin.on_create_wallet(wallet, self)
       -        return wallet
       -
       -    def restore_wallet(self, storage, wallet_type, kind):
       -        if wallet_type == 'standard':
       -            return self.restore_standard_wallet(storage)
       -
       -        if kind == 'multisig':
       -            return self.restore_multisig_wallet(storage, wallet_type)
       -
       -        # Plugin (two-factor or hardware)
       -        wallet = self.construct_wallet(storage, wallet_type)
       -        return wallet.plugin.on_restore_wallet(wallet, self)
       -
       -    def restore_standard_wallet(self, storage):
       -        text = self.request_seed(MSG_ENTER_ANYTHING)
       -        need_password = Wallet.should_encrypt(text)
       -        password = self.request_password() if need_password else None
       -        return Wallet.from_text(text, password, storage)
       -
       -    def restore_multisig_wallet(self, storage, wallet_type):
       -        # FIXME: better handling of duplicate keys
       -        m, n = Wallet.multisig_type(wallet_type)
       -        key_list = self.request_many(n - 1)
       -        need_password = any(Wallet.should_encrypt(text) for text in key_list)
       -        password = self.request_password() if need_password else None
       -        return Wallet.from_multisig(key_list, password, storage, wallet_type)
       -
       -    def create_seed(self, wallet):
       -        '''The create_seed action creates a seed and generates
       -        master keys.'''
       -        seed = wallet.make_seed(self.language_for_seed)
       -        self.show_and_verify_seed(seed)
       -        password = self.request_password()
       -        wallet.add_seed(seed, password)
       -        wallet.create_master_keys(password)
       -
       -    def create_main_account(self, wallet):
       -        # FIXME: BIP44 restore requires password
       -        wallet.create_main_account()
       -
       -    def create_addresses(self, wallet):
       -        wallet.synchronize()
       -
       -    def add_cosigners(self, wallet):
       -        # FIXME: better handling of duplicate keys
       -        m, n = Wallet.multisig_type(wallet.wallet_type)
       -        xpub1 = wallet.master_public_keys.get("x1/")
       -        xpubs = self.request_many(n - 1, xpub1)
       -        for i, xpub in enumerate(xpubs):
       -            wallet.add_master_public_key("x%d/" % (i + 2), xpub)
       -
       -    def update_wallet_format(self, wallet):
       -        # Backwards compatibility: convert old-format imported keys
       -        msg = _("Please enter your password in order to update "
       -                "imported keys")
       -        if wallet.use_encryption:
       -            password = self.request_password(msg)
       -        else:
       -            password = None
       -
       -        try:
       -            wallet.convert_imported_keys(password)
       -        except Exception as e:
       -            self.show_error(str(e))
       -
       -        # Call synchronize to regenerate addresses in case we're offline
       -        if wallet.get_master_public_keys() and not wallet.addresses():
       -            wallet.synchronize()
   DIR diff --git a/plugins/hw_wallet/plugin.py b/plugins/hw_wallet/plugin.py
       t@@ -53,21 +53,27 @@ class HW_PluginBase(BasePlugin):
        
            def on_restore_wallet(self, wallet, wizard):
                assert isinstance(wallet, self.wallet_class)
       -
                msg = _("Enter the seed for your %s wallet:" % self.device)
       -        seed = wizard.request_seed(msg, is_valid = self.is_valid_seed)
       +        f = lambda x: wizard.run('on_restore_seed', x)
       +        wizard.enter_seed_dialog(run_next=f, title=_('Restore hardware wallet'), message=msg, is_valid=self.is_valid_seed)
       +
       +    def on_restore_seed(self, wallet, wizard, seed):
       +        f = lambda x: wizard.run('on_restore_passphrase', seed, x)
       +        wizard.request_passphrase(self.device, run_next=f)
        
       +    def on_restore_passphrase(self, wallet, wizard, seed, passphrase):
       +        f = lambda x: wizard.run('on_restore_password', seed, passphrase, x)
       +        wizard.request_password(run_next=f)
       +
       +    def on_restore_password(self, wallet, wizard, seed, passphrase, password):
                # Restored wallets are not hardware wallets
                wallet_class = self.wallet_class.restore_wallet_class
                wallet.storage.put('wallet_type', wallet_class.wallet_type)
                wallet = wallet_class(wallet.storage)
       -
       -        passphrase = wizard.request_passphrase(self.device)
       -        password = wizard.request_password()
                wallet.add_seed(seed, password)
                wallet.add_xprv_from_seed(seed, 'x/', password, passphrase)
                wallet.create_hd_account(password)
       -        return wallet
       +        wizard.create_addresses()
        
            @staticmethod
            def is_valid_seed(seed):
   DIR diff --git a/plugins/trustedcoin/qt.py b/plugins/trustedcoin/qt.py
       t@@ -110,14 +110,9 @@ class Plugin(TrustedCoinPlugin):
                return WaitingDialog(window, 'Getting billing information...', task,
                                     on_finished)
        
       -    def confirm(self, window, msg):
       -        vbox = QVBoxLayout()
       -        vbox.addWidget(WWLabel(msg))
       -        window.set_main_layout(vbox)
       -
       -    def show_disclaimer(self, wallet, window):
       -        window.set_icon(':icons/trustedcoin.png')
       -        self.confirm(window, '\n\n'.join(DISCLAIMER))
       +    def show_disclaimer(self, wallet, wizard):
       +        wizard.set_icon(':icons/trustedcoin.png')
       +        wizard.confirm('\n\n'.join(DISCLAIMER))
                self.set_enabled(wallet, True)
        
            @hook
   DIR diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py
       t@@ -346,21 +346,32 @@ class TrustedCoinPlugin(BasePlugin):
                wallet.price_per_tx = dict(billing_info['price_per_tx'])
                return True
        
       -    def create_extended_seed(self, wallet, window):
       +    def create_extended_seed(self, wallet, wizard):
       +        self.wallet = wallet
       +        self.wizard = wizard
                seed = wallet.make_seed()
       -        window.show_and_verify_seed(seed, is_valid=self.is_valid_seed)
       +        f = lambda x: wizard.run('confirm_seed', x)
       +        self.wizard.show_seed_dialog(run_next=f, message="z", seed_text=seed)
        
       -        password = window.request_password()
       +    def confirm_seed(self, wallet, wizard, seed):
       +        title = _('Confirm Seed')
       +        msg = _('Please retype your seed phrase, to confirm that you properly saved it')
       +        f = lambda x: wizard.run('add_password', x)
       +        self.wizard.enter_seed_dialog(run_next=f, title=title, message=msg, is_valid=lambda x: x==seed)
       +
       +    def add_password(self, wallet, wizard, seed):
       +        f = lambda x: self.create_wallet(seed, x)
       +        self.wizard.request_password(run_next=f)
       +
       +    def create_wallet(self, seed, password):
       +        wallet = self.wallet
                wallet.storage.put('seed_version', wallet.seed_version)
                wallet.storage.put('use_encryption', password is not None)
       -
                words = seed.split()
                n = len(words)/2
                wallet.add_xprv_from_seed(' '.join(words[0:n]), 'x1/', password)
                wallet.add_xpub_from_seed(' '.join(words[n:]), 'x2/')
       -
                wallet.storage.write()
       -
                msg = [
                    _("Your wallet file is: %s.")%os.path.abspath(wallet.storage.path),
                    _("You need to be online in order to complete the creation of "
       t@@ -371,7 +382,8 @@ class TrustedCoinPlugin(BasePlugin):
                    _('If you are online, click on "%s" to continue.') % _('Next')
                ]
                msg = '\n\n'.join(msg)
       -        self.confirm(window, msg)
       +        self.wizard.confirm(msg)
       +        return wallet
        
            @hook
            def do_clear(self, window):
       t@@ -379,19 +391,22 @@ class TrustedCoinPlugin(BasePlugin):
        
            def on_restore_wallet(self, wallet, wizard):
                assert isinstance(wallet, self.wallet_class)
       +        title = _("Restore two-factor Wallet")
       +        f = lambda x: wizard.run('on_restore_seed', x)
       +        wizard.enter_seed_dialog(run_next=f, title=title, message=RESTORE_MSG, is_valid=self.is_valid_seed)
        
       -        seed = wizard.request_seed(RESTORE_MSG, is_valid=self.is_valid_seed)
       -        password = wizard.request_password()
       +    def on_restore_seed(self, wallet, wizard, seed):
       +        f = lambda x: wizard.run('on_restore_pw', seed, x)
       +        wizard.request_password(run_next=f)
        
       +    def on_restore_pw(self, wallet, wizard, seed, password):
                wallet.add_seed(seed, password)
                words = seed.split()
                n = len(words)/2
                wallet.add_xprv_from_seed(' '.join(words[0:n]), 'x1/', password)
                wallet.add_xprv_from_seed(' '.join(words[n:]), 'x2/', password)
       -
                restore_third_key(wallet)
       -        wallet.create_main_account()
       -        return wallet
       +        wizard.create_addresses()
        
            def create_remote_key(self, wallet, window):
                email = self.accept_terms_of_use(window)