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