merge screens of the install wizard
[electrum-nvc.git] / gui / qt / installwizard.py
1 from PyQt4.QtGui import *
2 from PyQt4.QtCore import *
3 import PyQt4.QtCore as QtCore
4
5 from electrum.i18n import _
6 from electrum import Wallet, Wallet_2of2, Wallet_2of3
7 import electrum.bitcoin as bitcoin
8
9 import seed_dialog
10 from network_dialog import NetworkDialog
11 from util import *
12 from amountedit import AmountEdit
13
14 import sys
15 import threading
16 from electrum.plugins import run_hook
17
18
19 MSG_ENTER_ANYTHING    = _("Please enter a wallet seed, a master public key, a list of Bitcoin addresses, or a list of private keys")
20 MSG_ENTER_MPK         = _("Please enter your master public key")
21 MSG_ENTER_SEED_OR_MPK = _("Please enter a wallet seed, or master public key")
22 MSG_VERIFY_SEED       = _("Your seed is important!") + "\n" + _("To make sure that you have properly saved your seed, please retype it here.")
23
24
25 class InstallWizard(QDialog):
26
27     def __init__(self, config, network, storage):
28         QDialog.__init__(self)
29         self.config = config
30         self.network = network
31         self.storage = storage
32         self.setMinimumSize(575, 400)
33         self.setWindowTitle('Electrum')
34         self.connect(self, QtCore.SIGNAL('accept'), self.accept)
35
36         self.stack = QStackedLayout()
37         self.setLayout(self.stack)
38
39
40     def set_layout(self, layout):
41         w = QWidget()
42         w.setLayout(layout)
43         self.stack.setCurrentIndex(self.stack.addWidget(w))
44
45
46     def restore_or_create(self):
47
48         vbox = QVBoxLayout()
49
50         main_label = QLabel(_("Electrum could not find an existing wallet."))
51         vbox.addWidget(main_label)
52
53         grid = QGridLayout()
54         grid.setSpacing(5)
55
56         label = QLabel(_("What do you want to do?"))
57         label.setWordWrap(True)
58         grid.addWidget(label, 0, 0)
59
60         gb1 = QGroupBox()
61         grid.addWidget(gb1, 0, 0)
62
63         group1 = QButtonGroup()
64
65         b1 = QRadioButton(gb1)
66         b1.setText(_("Create new wallet"))
67         b1.setChecked(True)
68
69         b2 = QRadioButton(gb1)
70         b2.setText(_("Restore an existing wallet"))
71
72         group1.addButton(b1)
73         group1.addButton(b2)
74
75         grid.addWidget(b1, 1, 0)
76         grid.addWidget(b2, 2, 0)
77         vbox.addLayout(grid)
78
79         grid2 = QGridLayout()
80         grid2.setSpacing(5)
81
82         label2 = QLabel(_("Wallet type:"))
83         grid2.addWidget(label2, 3, 0)
84         
85         gb2 = QGroupBox()
86         grid.addWidget(gb2, 3, 0)
87
88         group2 = QButtonGroup()
89
90         bb1 = QRadioButton(gb2)
91         bb1.setText(_("Standard wallet"))
92         bb1.setChecked(True)
93
94         bb2 = QRadioButton(gb2)
95         bb2.setText(_("Wallet with two-factor authentication (plugin)"))
96
97         bb3 = QRadioButton(gb2)
98         bb3.setText(_("Multisig wallet (paired manually)"))
99
100         grid2.addWidget(bb1, 4, 0)
101         grid2.addWidget(bb2, 5, 0)
102         grid2.addWidget(bb3, 6, 0)
103
104         group2.addButton(bb1)
105         group2.addButton(bb2)
106         group2.addButton(bb3)
107  
108         vbox.addLayout(grid2)
109         vbox.addStretch(1)
110         vbox.addLayout(ok_cancel_buttons(self, _('Next')))
111
112         self.set_layout(vbox)
113         if not self.exec_():
114             return None, None
115         
116         action = 'create' if b1.isChecked() else 'restore'
117
118         if bb1.isChecked():
119             t = 'standard'
120         elif bb2.isChecked():
121             t = 'multisig_plugin'
122         elif bb3.isChecked():
123             t = 'multisig_manual'
124
125         return action, t
126
127
128     def verify_seed(self, seed, sid):
129         r = self.enter_seed_dialog(MSG_VERIFY_SEED, sid)
130         if not r:
131             return
132
133         if r != seed:
134             QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
135             return False
136         else:
137             return True
138
139
140     def get_seed_text(self, seed_e):
141         text = unicode(seed_e.toPlainText()).strip()
142         text = ' '.join(text.split())
143         return text
144
145
146     def is_seed(self, seed_e):
147         text = self.get_seed_text(seed_e)
148         return Wallet.is_seed(text) or Wallet.is_mpk(text) or Wallet.is_address(text) or Wallet.is_private_key(text)
149
150
151     def enter_seed_dialog(self, msg, sid):
152         vbox, seed_e = seed_dialog.enter_seed_box(msg, sid)
153         vbox.addStretch(1)
154         hbox, button = ok_cancel_buttons2(self, _('Next'))
155         vbox.addLayout(hbox)
156         button.setEnabled(False)
157         seed_e.textChanged.connect(lambda: button.setEnabled(self.is_seed(seed_e)))
158         self.set_layout(vbox)
159         if not self.exec_():
160             return
161         return self.get_seed_text(seed_e)
162
163
164     def double_seed_dialog(self):
165         vbox = QVBoxLayout()
166         vbox1, seed_e1 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'hot')
167         vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'cold')
168         vbox.addLayout(vbox1)
169         vbox.addLayout(vbox2)
170         vbox.addStretch(1)
171         hbox, button = ok_cancel_buttons2(self, _('Next'))
172         vbox.addLayout(hbox)
173         button.setEnabled(False)
174         f = lambda: button.setEnabled(self.is_seed(seed_e1) and self.is_seed(seed_e2))
175         seed_e1.textChanged.connect(f)
176         seed_e2.textChanged.connect(f)
177         self.set_layout(vbox)
178         if not self.exec_():
179             return 
180         return self.get_seed_text(seed_e1), self.get_seed_text(seed_e2)
181
182
183
184
185     def waiting_dialog(self, task, msg= _("Electrum is generating your addresses, please wait.")):
186         def target():
187             task()
188             self.emit(QtCore.SIGNAL('accept'))
189
190         vbox = QVBoxLayout()
191         self.waiting_label = QLabel(msg)
192         vbox.addWidget(self.waiting_label)
193         self.set_layout(vbox)
194         t = threading.Thread(target = target)
195         t.start()
196         self.exec_()
197
198
199
200
201     def network_dialog(self):
202         
203         grid = QGridLayout()
204         grid.setSpacing(5)
205
206         label = QLabel(_("Electrum communicates with remote servers to get information about your transactions and addresses. The servers all fulfil the same purpose only differing in hardware. In most cases you simply want to let Electrum pick one at random if you have a preference though feel free to select a server manually.") + "\n\n" \
207                       + _("How do you want to connect to a server:")+" ")
208         label.setWordWrap(True)
209         grid.addWidget(label, 0, 0)
210
211         gb = QGroupBox()
212
213         b1 = QRadioButton(gb)
214         b1.setText(_("Auto connect"))
215         b1.setChecked(True)
216
217         b2 = QRadioButton(gb)
218         b2.setText(_("Select server manually"))
219
220         #b3 = QRadioButton(gb)
221         #b3.setText(_("Stay offline"))
222
223         grid.addWidget(b1,1,0)
224         grid.addWidget(b2,2,0)
225         #grid.addWidget(b3,3,0)
226
227         vbox = QVBoxLayout()
228         vbox.addLayout(grid)
229
230         vbox.addStretch(1)
231         vbox.addLayout(ok_cancel_buttons(self, _('Next')))
232
233         self.set_layout(vbox)
234         if not self.exec_():
235             return
236         
237         if b2.isChecked():
238             return NetworkDialog(self.network, self.config, None).do_exec()
239
240         elif b1.isChecked():
241             self.config.set_key('auto_cycle', True, True)
242             return
243
244         else:
245             self.config.set_key("server", None, True)
246             self.config.set_key('auto_cycle', False, True)
247             return
248         
249
250     def show_message(self, msg, icon=None):
251         vbox = QVBoxLayout()
252         self.set_layout(vbox)
253         if icon:
254             logo = QLabel()
255             logo.setPixmap(icon)
256             vbox.addWidget(logo)
257         vbox.addWidget(QLabel(msg))
258         vbox.addStretch(1)
259         vbox.addLayout(close_button(self, _('Next')))
260         if not self.exec_(): 
261             return None
262
263
264     def question(self, msg, icon=None):
265         vbox = QVBoxLayout()
266         self.set_layout(vbox)
267         if icon:
268             logo = QLabel()
269             logo.setPixmap(icon)
270             vbox.addWidget(logo)
271         vbox.addWidget(QLabel(msg))
272         vbox.addStretch(1)
273         vbox.addLayout(ok_cancel_buttons(self, _('OK')))
274         if not self.exec_(): 
275             return None
276         return True
277
278
279     def show_seed(self, seed, sid):
280         vbox = seed_dialog.show_seed_box(seed, sid)
281         vbox.addLayout(ok_cancel_buttons(self, _("Next")))
282         self.set_layout(vbox)
283         return self.exec_()
284
285
286     def password_dialog(self):
287         msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
288               +_("Leave these fields empty if you want to disable encryption.")
289         from password_dialog import make_password_dialog, run_password_dialog
290         self.set_layout( make_password_dialog(self, None, msg) )
291         return run_password_dialog(self, None, self)[2]
292
293
294
295
296     def run(self, action):
297
298         if action == 'new':
299             action, t = self.restore_or_create()
300
301         if action is None: 
302             return
303
304         if action == 'create':
305             if t == 'multisig_plugin':
306                 action = 'create_2of3_1'
307             if t == 'multisig_manual':
308                 action = 'create_2of2_1'
309
310         if action in ['create']:
311             wallet = Wallet(self.storage)
312         elif action in ['create_2of2_1','create_2of2_2']:
313             wallet = Wallet_2of2(self.storage)
314
315
316         if action == 'create':
317             seed = wallet.make_seed()
318             if not self.show_seed(seed, None):
319                 return
320             if not self.verify_seed(seed, None):
321                 return
322             password = self.password_dialog()
323             wallet.add_seed(seed, password)
324             wallet.create_accounts(password)
325             self.waiting_dialog(wallet.synchronize)
326
327
328         if action == 'create_2of3_1':
329             run_hook('create_cold_seed', self.storage, self)
330             return
331
332
333         if action in ['create_2of2_1', 'create_2of3_2']:
334             msg = _('You are about to create the hot seed of a multisig wallet')
335             if not self.question(msg):
336                 return
337             seed = wallet.make_seed()
338             if not self.show_seed(seed, 'hot'):
339                 return
340             if not self.verify_seed(seed, 'hot'):
341                 return
342             password = self.password_dialog()
343             wallet.add_seed(seed, password)
344             if action == 'create_2of2_1':
345                 # display mpk
346                 action = 'create_2of2_2'
347             else:
348                 action = 'create_2of3_3'
349
350         if action == 'create_2of2_2':
351             xpub = self.enter_seed_dialog(MSG_ENTER_MPK, 'cold')
352             if not Wallet.is_mpk(xpub):
353                 return
354             wallet.add_master_public_key("cold/", xpub)
355             wallet.create_account()
356             self.waiting_dialog(wallet.synchronize)
357
358
359         if action == 'create_2of3_3':
360             run_hook('create_remote_key', wallet, self)
361             if not wallet.master_public_keys.get("remote/"):
362                 return
363             wallet.create_account()
364             self.waiting_dialog(wallet.synchronize)
365
366
367         if action == 'restore':
368
369             if t == 'standard':
370                 text = self.enter_seed_dialog(MSG_ENTER_ANYTHING, None)
371                 if not text:
372                     return
373                 if Wallet.is_seed(text):
374                     password = self.password_dialog()
375                     wallet = Wallet.from_seed(text, self.storage)
376                     wallet.add_seed(text, password)
377                     wallet.create_accounts(password)
378                 elif Wallet.is_mpk(text):
379                     wallet = Wallet.from_mpk(text, self.storage)
380                 elif Wallet.is_address(text):
381                     wallet = Wallet.from_address(text, self.storage)
382                 elif Wallet.is_private_key(text):
383                     wallet = Wallet.from_private_key(text, self.storage)
384                 else:
385                     raise
386
387             elif t in ['multisig_plugin', 'multisig_manual']:
388                 r = self.double_seed_dialog()
389                 if not r: 
390                     return
391                 text1, text2 = r
392                 password = self.password_dialog()
393                 if t == 'multisig_manual':
394                     wallet = Wallet_2of2(self.storage)
395                 else:
396                     wallet = Wallet_2of3(self.storage)
397
398                 if Wallet.is_seed(text1):
399                     wallet.add_seed(text1, password)
400                     if Wallet.is_seed(text2):
401                         wallet.add_cold_seed(text2, password)
402                     else:
403                         wallet.add_master_public_key("cold/", text2)
404
405                 elif Wallet.is_mpk(text1):
406                     if Wallet.is_seed(text2):
407                         wallet.add_seed(text2, password)
408                         wallet.add_master_public_key("cold/", text1)
409                     else:
410                         wallet.add_master_public_key("m/", text1)
411                         wallet.add_master_public_key("cold/", text2)
412
413                 if t == '2of3':
414                     run_hook('restore_third_key', wallet, self)
415
416                 wallet.create_account()
417
418             else:
419                 raise
420
421
422                 
423         #if not self.config.get('server'):
424         if self.network:
425             if self.network.interfaces:
426                 self.network_dialog()
427             else:
428                 QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
429                 self.network.stop()
430                 self.network = None
431
432         # start wallet threads
433         wallet.start_threads(self.network)
434
435         if action == 'restore':
436
437             self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText))
438
439             if self.network:
440                 if wallet.is_found():
441                     QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
442                 else:
443                     QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK'))
444             else:
445                 QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))
446
447         return wallet