more robust 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_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 (protected by password)"))
254         b1.setChecked(True)
255
256         b2 = QRadioButton(gb)
257         b2.setText(_("Multi-signature wallet (two-factor authentication)"))
258
259         grid.addWidget(b1,1,0)
260         grid.addWidget(b2,2,0)
261
262         vbox = QVBoxLayout()
263
264         vbox.addLayout(grid)
265         vbox.addStretch(1)
266         vbox.addLayout(ok_cancel_buttons(self, _('Next')))
267
268         self.set_layout(vbox)
269         if not self.exec_():
270             return
271         
272         if b1.isChecked():
273             return 'standard'
274         elif b2.isChecked():
275             return '2of3'
276
277
278     def run(self, action):
279
280         if action == 'new':
281             action = self.restore_or_create()
282
283         if action is None: 
284             return
285
286         if action == 'create':            
287             t = self.choose_wallet_type()
288             if not t:
289                 return 
290
291             if t == '2of3':
292                 action = 'create_cold'
293         
294
295         if action in ['create', 'create_cold', 'create_hot', 'create_remote']:
296             wallet = Wallet(self.storage)
297
298         if action == 'create':
299             seed = wallet.make_seed()
300             if not self.show_seed(seed, None):
301                 return
302             if not self.verify_seed(seed, None):
303                 return
304             password = self.password_dialog()
305             wallet.add_seed(seed, password)
306             wallet.create_accounts(password)
307             # generate first addresses offline
308             self.waiting_dialog(wallet.synchronize)
309
310
311         if action == 'create_cold':
312             run_hook('create_cold_seed', self.storage, self)
313             return
314
315
316         if action == 'create_hot':
317             msg = _('You are about to create the hot seed of a multisig wallet')
318             if not self.question(msg):
319                 return
320             seed = wallet.make_seed()
321             if not self.show_seed(seed, 'hot'):
322                 return
323             if not self.verify_seed(seed, 'hot'):
324                 return
325             password = self.password_dialog()
326             wallet.add_seed(seed, password)
327             action = 'create_remote'
328
329
330         if action == 'create_remote':
331             run_hook('create_remote_key', wallet, self)
332             if not wallet.master_public_keys.get("remote/"):
333                 return
334             wallet.create_account()
335             self.waiting_dialog(wallet.synchronize)
336
337
338         if action == 'restore':
339             t = self.choose_wallet_type()
340             if not t: 
341                 return
342
343             if t == 'standard':
344                 text = self.enter_seed_dialog(True, None)
345                 if Wallet.is_seed(text):
346                     password = self.password_dialog()
347                     wallet = Wallet.from_seed(text, self.storage)
348                     wallet.add_seed(text, password)
349                     wallet.create_accounts(password)
350                 elif Wallet.is_mpk(text):
351                     wallet = Wallet.from_mpk(text, self.storage)
352                 else:
353                     raise
354
355             elif t in ['2of2', '2of3']:
356                 r = self.double_seed_dialog()
357                 if not r: 
358                     return
359                 text1, text2 = r
360                 password = self.password_dialog()
361                 wallet = Wallet_2of3(self.storage)
362
363                 if Wallet.is_seed(text1):
364                     wallet.add_seed(text1, password)
365                     if Wallet.is_seed(text2):
366                         wallet.add_cold_seed(text2, password)
367                     else:
368                         wallet.add_master_public_key("cold/", text2)
369
370                 elif Wallet.is_mpk(text1):
371                     if Wallet.is_seed(text2):
372                         wallet.add_seed(text2, password)
373                         wallet.add_master_public_key("cold/", text1)
374                     else:
375                         wallet.add_master_public_key("m/", text1)
376                         wallet.add_master_public_key("cold/", text2)
377
378                 run_hook('restore_third_key', wallet, self)
379
380                 wallet.create_accounts(None)
381
382             else:
383                 raise
384
385
386                 
387         #if not self.config.get('server'):
388         if self.network:
389             if self.network.interfaces:
390                 self.network_dialog()
391             else:
392                 QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
393                 self.network.stop()
394                 self.network = None
395
396         # start wallet threads
397         wallet.start_threads(self.network)
398
399         if action == 'restore':
400
401             self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText))
402
403             if self.network:
404                 if wallet.is_found():
405                     QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
406                 else:
407                     QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK'))
408             else:
409                 QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))
410
411         return wallet