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