more robust get_seed_text
[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()).lower().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):
198         vbox = QVBoxLayout()
199         vbox.addWidget(QLabel(msg))
200         vbox.addStretch(1)
201         vbox.addLayout(close_button(self, _('Next')))
202         self.set_layout(vbox)
203         if not self.exec_(): 
204             return None
205
206     def question(self, msg):
207         vbox = QVBoxLayout()
208         vbox.addWidget(QLabel(msg))
209         vbox.addStretch(1)
210         vbox.addLayout(ok_cancel_buttons(self, _('OK')))
211         self.set_layout(vbox)
212         if not self.exec_(): 
213             return None
214         return True
215
216
217     def show_seed(self, seed, sid):
218         vbox = seed_dialog.show_seed_box(seed, sid)
219         vbox.addLayout(ok_cancel_buttons(self, _("Next")))
220         self.set_layout(vbox)
221         return self.exec_()
222
223
224     def password_dialog(self):
225         msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
226               +_("Leave these fields empty if you want to disable encryption.")
227         from password_dialog import make_password_dialog, run_password_dialog
228         self.set_layout( make_password_dialog(self, None, msg) )
229         return run_password_dialog(self, None, self)[2]
230
231
232     def choose_wallet_type(self):
233         grid = QGridLayout()
234         grid.setSpacing(5)
235
236         msg = _("Choose your wallet.")
237         label = QLabel(msg)
238         label.setWordWrap(True)
239         grid.addWidget(label, 0, 0)
240
241         gb = QGroupBox()
242
243         b1 = QRadioButton(gb)
244         b1.setText(_("Standard wallet (protected by password)"))
245         b1.setChecked(True)
246
247         b2 = QRadioButton(gb)
248         b2.setText(_("Multi-signature wallet (two-factor authentication)"))
249
250         grid.addWidget(b1,1,0)
251         grid.addWidget(b2,2,0)
252
253         vbox = QVBoxLayout()
254
255         vbox.addLayout(grid)
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 b1.isChecked():
264             return 'standard'
265         elif b2.isChecked():
266             return '2of3'
267
268
269     def run(self, action = None):
270
271         if action is None:
272             action = self.restore_or_create()
273
274         if action is None: 
275             return
276
277         if action == 'create':            
278             t = self.choose_wallet_type()
279             if not t:
280                 return 
281
282             if t == '2of3':
283                 run_hook('create_cold_seed', self.storage, self)
284                 return
285
286
287         if action in ['create', 'create2of3']:
288
289             wallet = Wallet(self.storage)
290             seed = wallet.make_seed()
291             sid = 'hot' if action == 'create2of3' else None
292             if not self.show_seed(seed, sid):
293                 return
294             if not self.verify_seed(seed, sid):
295                 return
296             password = self.password_dialog()
297             wallet.save_seed(seed, password)
298
299             if action == 'create2of3':
300                 run_hook('create_third_key', wallet, self)
301                 if not wallet.master_public_keys.get("remote/"):
302                     return
303
304             wallet.create_accounts(password)
305             # generate first addresses offline
306             self.waiting_dialog(wallet.synchronize)
307
308         elif action == 'restore':
309             t = self.choose_wallet_type()
310             if not t: 
311                 return
312
313             if t == 'standard':
314                 text = self.enter_seed_dialog(True, None)
315                 if Wallet.is_seed(text):
316                     password = self.password_dialog()
317                     wallet = Wallet.from_seed(text, self.storage)
318                     wallet.save_seed(text, password)
319                     wallet.create_accounts(password)
320                 elif Wallet.is_mpk(text):
321                     wallet = Wallet.from_mpk(text, self.storage)
322                 else:
323                     raise
324
325             elif t in ['2of2', '2of3']:
326                 r = self.double_seed_dialog()
327                 if not r: 
328                     return
329                 text1, text2 = r
330                 password = self.password_dialog()
331                 wallet = Wallet_2of3(self.storage)
332
333                 if Wallet.is_seed(text1):
334                     wallet.add_root("m/", text1, password)
335                 elif Wallet.is_mpk(text1):
336                     wallet.add_master_public_key("m/", text1)
337                 
338                 if Wallet.is_seed(text2):
339                     wallet.add_root("cold/", text2, password)
340                 elif Wallet.is_mpk(text2):
341                     wallet.add_master_public_key("cold/", text2)
342
343                 run_hook('restore_third_key', wallet, self)
344
345                 wallet.create_accounts(None)
346
347             else:
348                 raise
349
350
351
352
353         else: raise
354                 
355         #if not self.config.get('server'):
356         if self.network:
357             if self.network.interfaces:
358                 self.network_dialog()
359             else:
360                 QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
361                 self.network.stop()
362                 self.network = None
363
364         # start wallet threads
365         wallet.start_threads(self.network)
366
367         if action == 'restore':
368
369             self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText))
370
371             if self.network:
372                 if wallet.is_found():
373                     QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
374                 else:
375                     QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK'))
376             else:
377                 QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))
378
379         return wallet