3a0be2825ee809e298616ef163159559139e5d68
[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):
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_seed(text1, password)
335                     if Wallet.is_seed(text2):
336                         wallet.add_cold_seed(text2, password)
337                     else:
338                         wallet.add_master_public_key("cold/", text2)
339
340                 elif Wallet.is_mpk(text1):
341                     if Wallet.is_seed(text2):
342                         wallet.add_seed(text2, password)
343                         wallet.add_master_public_key("cold/", text1)
344                     else:
345                         wallet.add_master_public_key("m/", text1)
346                         wallet.add_master_public_key("cold/", text2)
347
348                 run_hook('restore_third_key', wallet, self)
349
350                 wallet.create_accounts(None)
351
352             else:
353                 raise
354
355
356
357
358         else: raise
359                 
360         #if not self.config.get('server'):
361         if self.network:
362             if self.network.interfaces:
363                 self.network_dialog()
364             else:
365                 QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
366                 self.network.stop()
367                 self.network = None
368
369         # start wallet threads
370         wallet.start_threads(self.network)
371
372         if action == 'restore':
373
374             self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText))
375
376             if self.network:
377                 if wallet.is_found():
378                     QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
379                 else:
380                     QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK'))
381             else:
382                 QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))
383
384         return wallet