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