common wallet.restore() method for GUIs
[electrum-nvc.git] / gui / gtk.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 import datetime
20 import thread, time, ast, sys, re
21 import socket, traceback
22 import pygtk
23 pygtk.require('2.0')
24 import gtk, gobject
25 from decimal import Decimal
26 from electrum.util import print_error
27 from electrum.bitcoin import is_valid
28 from electrum import mnemonic, pyqrnative, WalletStorage, Wallet
29
30 gtk.gdk.threads_init()
31 APP_NAME = "Electrum"
32 import platform
33 MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace'
34
35 from electrum.util import format_satoshis
36 from electrum.network import DEFAULT_SERVERS
37 from electrum.bitcoin import MIN_RELAY_TX_FEE
38
39 def numbify(entry, is_int = False):
40     text = entry.get_text().strip()
41     chars = '0123456789'
42     if not is_int: chars +='.'
43     s = ''.join([i for i in text if i in chars])
44     if not is_int:
45         if '.' in s:
46             p = s.find('.')
47             s = s.replace('.','')
48             s = s[:p] + '.' + s[p:p+8]
49         try:
50             amount = int( Decimal(s) * 100000000 )
51         except:
52             amount = None
53     else:
54         try:
55             amount = int( s )
56         except:
57             amount = None
58     entry.set_text(s)
59     return amount
60
61
62
63
64 def show_seed_dialog(wallet, password, parent):
65     if not wallet.seed:
66         show_message("No seed")
67         return
68     try:
69         seed = wallet.decode_seed(password)
70     except:
71         show_message("Incorrect password")
72         return
73     dialog = gtk.MessageDialog(
74         parent = parent,
75         flags = gtk.DIALOG_MODAL, 
76         buttons = gtk.BUTTONS_OK, 
77         message_format = "Your wallet generation seed is:\n\n" + seed \
78             + "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \
79             + "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" + ' '.join(mnemonic.mn_encode(seed)) + "\"" )
80     dialog.set_title("Seed")
81     dialog.show()
82     dialog.run()
83     dialog.destroy()
84
85 def restore_create_dialog():
86
87     # ask if the user wants to create a new wallet, or recover from a seed. 
88     # if he wants to recover, and nothing is found, do not create wallet
89     dialog = gtk.Dialog("electrum", parent=None, 
90                         flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, 
91                         buttons= ("create", 0, "restore",1, "cancel",2)  )
92
93     label = gtk.Label("Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?"  )
94     label.show()
95     dialog.vbox.pack_start(label)
96     dialog.show()
97     r = dialog.run()
98     dialog.destroy()
99
100     if r==2: return False
101     return 'restore' if r==1 else 'create'
102
103
104
105 def run_recovery_dialog():
106     message = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet."
107     dialog = gtk.MessageDialog(
108         parent = None,
109         flags = gtk.DIALOG_MODAL, 
110         buttons = gtk.BUTTONS_OK_CANCEL,
111         message_format = message)
112
113     vbox = dialog.vbox
114     dialog.set_default_response(gtk.RESPONSE_OK)
115
116     # ask seed, server and gap in the same dialog
117     seed_box = gtk.HBox()
118     seed_label = gtk.Label('Seed or mnemonic:')
119     seed_label.set_size_request(150,-1)
120     seed_box.pack_start(seed_label, False, False, 10)
121     seed_label.show()
122     seed_entry = gtk.Entry()
123     seed_entry.show()
124     seed_entry.set_size_request(450,-1)
125     seed_box.pack_start(seed_entry, False, False, 10)
126     add_help_button(seed_box, '.')
127     seed_box.show()
128     vbox.pack_start(seed_box, False, False, 5)    
129
130     dialog.show()
131     r = dialog.run()
132     seed = seed_entry.get_text()
133     dialog.destroy()
134
135     if r==gtk.RESPONSE_CANCEL:
136         return False
137
138     try:
139         seed.decode('hex')
140     except:
141         print_error("Warning: Not hex, trying decode")
142         seed = mnemonic.mn_decode( seed.split(' ') )
143     if not seed:
144         show_message("no seed")
145         return False
146         
147     return seed
148
149
150
151 def run_settings_dialog(wallet, parent):
152
153     message = "Here are the settings of your wallet. For more explanations, click on the question mark buttons next to each input field."
154         
155     dialog = gtk.MessageDialog(
156         parent = parent,
157         flags = gtk.DIALOG_MODAL, 
158         buttons = gtk.BUTTONS_OK_CANCEL,
159         message_format = message)
160
161     image = gtk.Image()
162     image.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG)
163     image.show()
164     dialog.set_image(image)
165     dialog.set_title("Settings")
166
167     vbox = dialog.vbox
168     dialog.set_default_response(gtk.RESPONSE_OK)
169
170     fee = gtk.HBox()
171     fee_entry = gtk.Entry()
172     fee_label = gtk.Label('Transaction fee:')
173     fee_label.set_size_request(150,10)
174     fee_label.show()
175     fee.pack_start(fee_label,False, False, 10)
176     fee_entry.set_text( str( Decimal(wallet.fee) /100000000 ) )
177     fee_entry.connect('changed', numbify, False)
178     fee_entry.show()
179     fee.pack_start(fee_entry,False,False, 10)
180     add_help_button(fee, 'Fee per kilobyte of transaction. Recommended value:0.0001')
181     fee.show()
182     vbox.pack_start(fee, False,False, 5)
183             
184     nz = gtk.HBox()
185     nz_entry = gtk.Entry()
186     nz_label = gtk.Label('Display zeros:')
187     nz_label.set_size_request(150,10)
188     nz_label.show()
189     nz.pack_start(nz_label,False, False, 10)
190     nz_entry.set_text( str( wallet.num_zeros ))
191     nz_entry.connect('changed', numbify, True)
192     nz_entry.show()
193     nz.pack_start(nz_entry,False,False, 10)
194     add_help_button(nz, "Number of zeros displayed after the decimal point.\nFor example, if this number is 2, then '5.' is displayed as '5.00'")
195     nz.show()
196     vbox.pack_start(nz, False,False, 5)
197             
198     # gui setting
199     gui_box = gtk.HBox()
200     gui_label = gtk.Label('Default GUI:')
201     gui_label.set_size_request(150,10)
202     gui_label.show()
203     gui_box.pack_start(gui_label,False, False, 10)
204     gui_combo = gtk.combo_box_new_text()
205     gui_names = ['lite', 'classic', 'gtk', 'text']
206     for name in gui_names: gui_combo.append_text(name.capitalize())
207     gui_combo.show()
208     gui_box.pack_start(gui_combo,False, False, 10)
209     gui_combo.set_active( gui_names.index( wallet.config.get("gui","lite")) )
210     gui_box.show()
211     add_help_button(gui_box, "Select which GUI mode to use at start up.")
212
213     vbox.pack_start(gui_box, False,False, 5)
214
215     dialog.show()
216     r = dialog.run()
217     fee = fee_entry.get_text()
218     nz = nz_entry.get_text()
219     gui = gui_names[ gui_combo.get_active()]
220         
221     dialog.destroy()
222     if r==gtk.RESPONSE_CANCEL:
223         return
224
225     try:
226         fee = int( 100000000 * Decimal(fee) )
227     except:
228         show_message("error")
229         return
230     wallet.set_fee(fee)
231
232     try:
233         nz = int( nz )
234         if nz>8: nz = 8
235     except:
236         show_message("error")
237         return
238     if wallet.num_zeros != nz:
239         wallet.num_zeros = nz
240         wallet.save()
241
242     wallet.config.set_key('gui',gui,True)
243
244
245
246
247 def run_network_dialog( network, parent ):
248     image = gtk.Image()
249     image.set_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_DIALOG)
250     interface = network.interface
251     if parent:
252         if interface.is_connected:
253             status = "Connected to %s:%d\n%d blocks"%(interface.host, interface.port, network.blockchain.height)
254         else:
255             status = "Not connected"
256     else:
257         import random
258         status = "Please choose a server.\nSelect cancel if you are offline."
259
260     server = interface.server
261     servers = network.get_servers()
262
263     dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
264                                     gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, status)
265     dialog.set_title("Server")
266     dialog.set_image(image)
267     image.show()
268     
269     vbox = dialog.vbox
270     host_box = gtk.HBox()
271     host_label = gtk.Label('Connect to:')
272     host_label.set_size_request(100,-1)
273     host_label.show()
274     host_box.pack_start(host_label, False, False, 10)
275     host_entry = gtk.Entry()
276     host_entry.set_size_request(200,-1)
277     host_entry.set_text(server)
278     host_entry.show()
279     host_box.pack_start(host_entry, False, False, 10)
280     add_help_button(host_box, 'The name and port number of your Electrum server, separated by a colon. Example: "ecdsa.org:50000". If no port number is provided, port 50000 will be tried. Some servers allow you to connect through http (port 80) or https (port 443)')
281     host_box.show()
282
283
284     p_box = gtk.HBox(False, 10)
285     p_box.show()
286
287     p_label = gtk.Label('Protocol:')
288     p_label.set_size_request(100,-1)
289     p_label.show()
290     p_box.pack_start(p_label, False, False, 10)
291
292     radio1 = gtk.RadioButton(None, "tcp")
293     p_box.pack_start(radio1, True, True, 0)
294     radio1.show()
295     radio2 = gtk.RadioButton(radio1, "http")
296     p_box.pack_start(radio2, True, True, 0)
297     radio2.show()
298
299     def current_line():
300         return unicode(host_entry.get_text()).split(':')
301     
302     def set_button(protocol):
303         if protocol == 't':
304             radio1.set_active(1)
305         elif protocol == 'h':
306             radio2.set_active(1)
307
308     def set_protocol(protocol):
309         host = current_line()[0]
310         pp = servers[host]
311         if protocol not in pp.keys():
312             protocol = pp.keys()[0]
313             set_button(protocol)
314         port = pp[protocol]
315         host_entry.set_text( host + ':' + port + ':' + protocol)
316
317     radio1.connect("toggled", lambda x,y:set_protocol('t'), "radio button 1")
318     radio2.connect("toggled", lambda x,y:set_protocol('h'), "radio button 1")
319         
320     server_list = gtk.ListStore(str)
321     for host in servers.keys():
322         server_list.append([host])
323     
324     treeview = gtk.TreeView(model=server_list)
325     treeview.show()
326
327     if interface.servers:
328         label = 'Active Servers'
329     else:
330         label = 'Default Servers'
331         
332     tvcolumn = gtk.TreeViewColumn(label)
333     treeview.append_column(tvcolumn)
334     cell = gtk.CellRendererText()
335     tvcolumn.pack_start(cell, False)
336     tvcolumn.add_attribute(cell, 'text', 0)
337
338     scroll = gtk.ScrolledWindow()
339     scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
340     scroll.add(treeview)
341     scroll.show()
342
343     vbox.pack_start(host_box, False,False, 5)
344     vbox.pack_start(p_box, True, True, 0)
345     vbox.pack_start(scroll)
346
347     def my_treeview_cb(treeview):
348         path, view_column = treeview.get_cursor()
349         host = server_list.get_value( server_list.get_iter(path), 0)
350
351         pp = servers[host]
352         if 't' in pp.keys():
353             protocol = 't'
354         else:
355             protocol = pp.keys()[0]
356         port = pp[protocol]
357         host_entry.set_text( host + ':' + port + ':' + protocol)
358         set_button(protocol)
359
360     treeview.connect('cursor-changed', my_treeview_cb)
361
362     dialog.show()
363     r = dialog.run()
364     server = host_entry.get_text()
365     dialog.destroy()
366
367     if r==gtk.RESPONSE_CANCEL:
368         return False
369
370     try:
371         interface.set_server(server)
372     except:
373         show_message("error:" + server)
374         return False
375
376     if parent:
377         wallet.config.set_key("server", server, True)
378     return True
379
380
381
382 def show_message(message, parent=None):
383     dialog = gtk.MessageDialog(
384         parent = parent,
385         flags = gtk.DIALOG_MODAL, 
386         buttons = gtk.BUTTONS_CLOSE, 
387         message_format = message )
388     dialog.show()
389     dialog.run()
390     dialog.destroy()
391
392 def password_line(label):
393     password = gtk.HBox()
394     password_label = gtk.Label(label)
395     password_label.set_size_request(120,10)
396     password_label.show()
397     password.pack_start(password_label,False, False, 10)
398     password_entry = gtk.Entry()
399     password_entry.set_size_request(300,-1)
400     password_entry.set_visibility(False)
401     password_entry.show()
402     password.pack_start(password_entry,False,False, 10)
403     password.show()
404     return password, password_entry
405
406 def password_dialog(parent):
407     dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
408                                 gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL,  "Please enter your password.")
409     dialog.get_image().set_visible(False)
410     current_pw, current_pw_entry = password_line('Password:')
411     current_pw_entry.connect("activate", lambda entry, dialog, response: dialog.response(response), dialog, gtk.RESPONSE_OK)
412     dialog.vbox.pack_start(current_pw, False, True, 0)
413     dialog.show()
414     result = dialog.run()
415     pw = current_pw_entry.get_text()
416     dialog.destroy()
417     if result != gtk.RESPONSE_CANCEL: return pw
418
419 def change_password_dialog(wallet, parent, icon):
420     if not wallet.seed:
421         show_message("No seed")
422         return
423
424     if parent:
425         msg = 'Your wallet is encrypted. Use this dialog to change the password. To disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted'
426     else:
427         msg = "Please choose a password to encrypt your wallet keys"
428
429     dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
430     dialog.set_title("Change password")
431     image = gtk.Image()
432     image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_DIALOG)
433     image.show()
434     dialog.set_image(image)
435
436     if wallet.use_encryption:
437         current_pw, current_pw_entry = password_line('Current password:')
438         dialog.vbox.pack_start(current_pw, False, True, 0)
439
440     password, password_entry = password_line('New password:')
441     dialog.vbox.pack_start(password, False, True, 5)
442     password2, password2_entry = password_line('Confirm password:')
443     dialog.vbox.pack_start(password2, False, True, 5)
444
445     dialog.show()
446     result = dialog.run()
447     password = current_pw_entry.get_text() if wallet.use_encryption else None
448     new_password = password_entry.get_text()
449     new_password2 = password2_entry.get_text()
450     dialog.destroy()
451     if result == gtk.RESPONSE_CANCEL: 
452         return
453
454     try:
455         seed = wallet.decode_seed(password)
456     except:
457         show_message("Incorrect password")
458         return
459
460     if new_password != new_password2:
461         show_message("passwords do not match")
462         return
463
464     wallet.update_password(seed, password, new_password)
465
466     if icon:
467         if wallet.use_encryption:
468             icon.set_tooltip_text('wallet is encrypted')
469         else:
470             icon.set_tooltip_text('wallet is unencrypted')
471
472
473 def add_help_button(hbox, message):
474     button = gtk.Button('?')
475     button.connect("clicked", lambda x: show_message(message))
476     button.show()
477     hbox.pack_start(button,False, False)
478
479
480 class MyWindow(gtk.Window): __gsignals__ = dict( mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, None, (str,)) )
481
482 gobject.type_register(MyWindow)
483 gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.W, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+W')
484 gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.Q, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+Q')
485
486
487 class ElectrumWindow:
488
489     def show_message(self, msg):
490         show_message(msg, self.window)
491
492     def __init__(self, wallet, config, network):
493         self.config = config
494         self.wallet = wallet
495         self.network = network
496         self.funds_error = False # True if not enough funds
497         self.num_zeros     = int(self.config.get('num_zeros',0))
498
499         self.window = MyWindow(gtk.WINDOW_TOPLEVEL)
500         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.config.path
501         if not self.wallet.seed: title += ' [seedless]'
502         self.window.set_title(title)
503         self.window.connect("destroy", gtk.main_quit)
504         self.window.set_border_width(0)
505         self.window.connect('mykeypress', gtk.main_quit)
506         self.window.set_default_size(720, 350)
507         self.wallet_updated = False
508
509         vbox = gtk.VBox()
510
511         self.notebook = gtk.Notebook()
512         self.create_history_tab()
513         if self.wallet.seed:
514             self.create_send_tab()
515         self.create_recv_tab()
516         self.create_book_tab()
517         self.create_about_tab()
518         self.notebook.show()
519         vbox.pack_start(self.notebook, True, True, 2)
520         
521         self.status_bar = gtk.Statusbar()
522         vbox.pack_start(self.status_bar, False, False, 0)
523
524         self.status_image = gtk.Image()
525         self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
526         self.status_image.set_alignment(True, 0.5  )
527         self.status_image.show()
528
529         self.network_button = gtk.Button()
530         self.network_button.connect("clicked", lambda x: run_network_dialog(self.network, self.window) )
531         self.network_button.add(self.status_image)
532         self.network_button.set_relief(gtk.RELIEF_NONE)
533         self.network_button.show()
534         self.status_bar.pack_end(self.network_button, False, False)
535
536         if self.wallet.seed:
537             def seedb(w, wallet):
538                 if wallet.use_encryption:
539                     password = password_dialog(self.window)
540                     if not password: return
541                 else: password = None
542                 show_seed_dialog(wallet, password, self.window)
543             button = gtk.Button('S')
544             button.connect("clicked", seedb, wallet )
545             button.set_relief(gtk.RELIEF_NONE)
546             button.show()
547             self.status_bar.pack_end(button,False, False)
548
549         settings_icon = gtk.Image()
550         settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
551         settings_icon.set_alignment(0.5, 0.5)
552         settings_icon.set_size_request(16,16 )
553         settings_icon.show()
554
555         prefs_button = gtk.Button()
556         prefs_button.connect("clicked", lambda x: run_settings_dialog(self.wallet, self.window) )
557         prefs_button.add(settings_icon)
558         prefs_button.set_tooltip_text("Settings")
559         prefs_button.set_relief(gtk.RELIEF_NONE)
560         prefs_button.show()
561         self.status_bar.pack_end(prefs_button,False,False)
562
563         pw_icon = gtk.Image()
564         pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU)
565         pw_icon.set_alignment(0.5, 0.5)
566         pw_icon.set_size_request(16,16 )
567         pw_icon.show()
568
569         if self.wallet.seed:
570             password_button = gtk.Button()
571             password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon))
572             password_button.add(pw_icon)
573             password_button.set_relief(gtk.RELIEF_NONE)
574             password_button.show()
575             self.status_bar.pack_end(password_button,False,False)
576
577         self.window.add(vbox)
578         self.window.show_all()
579         #self.fee_box.hide()
580
581         self.context_id = self.status_bar.get_context_id("statusbar")
582         self.update_status_bar()
583
584         self.network.register_callback('updated', self.update_callback)
585
586
587         def update_status_bar_thread():
588             while True:
589                 gobject.idle_add( self.update_status_bar )
590                 time.sleep(0.5)
591
592
593         def check_recipient_thread():
594             old_r = ''
595             while True:
596                 time.sleep(0.5)
597                 if self.payto_entry.is_focus():
598                     continue
599                 r = self.payto_entry.get_text()
600                 if r != old_r:
601                     old_r = r
602                     r = r.strip()
603                     if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
604                         try:
605                             to_address = self.wallet.get_alias(r, interactive=False)
606                         except:
607                             continue
608                         if to_address:
609                             s = r + ' <' + to_address + '>'
610                             gobject.idle_add( lambda: self.payto_entry.set_text(s) )
611                 
612
613         thread.start_new_thread(update_status_bar_thread, ())
614         if self.wallet.seed:
615             thread.start_new_thread(check_recipient_thread, ())
616         self.notebook.set_current_page(0)
617
618     def update_callback(self):
619         self.wallet_updated = True
620
621
622     def add_tab(self, page, name):
623         tab_label = gtk.Label(name)
624         tab_label.show()
625         self.notebook.append_page(page, tab_label)
626
627
628     def create_send_tab(self):
629         
630         page = vbox = gtk.VBox()
631         page.show()
632
633         payto = gtk.HBox()
634         payto_label = gtk.Label('Pay to:')
635         payto_label.set_size_request(100,-1)
636         payto.pack_start(payto_label, False)
637         payto_entry = gtk.Entry()
638         payto_entry.set_size_request(450, 26)
639         payto.pack_start(payto_entry, False)
640         vbox.pack_start(payto, False, False, 5)
641
642         message = gtk.HBox()
643         message_label = gtk.Label('Description:')
644         message_label.set_size_request(100,-1)
645         message.pack_start(message_label, False)
646         message_entry = gtk.Entry()
647         message_entry.set_size_request(450, 26)
648         message.pack_start(message_entry, False)
649         vbox.pack_start(message, False, False, 5)
650
651         amount_box = gtk.HBox()
652         amount_label = gtk.Label('Amount:')
653         amount_label.set_size_request(100,-1)
654         amount_box.pack_start(amount_label, False)
655         amount_entry = gtk.Entry()
656         amount_entry.set_size_request(120, -1)
657         amount_box.pack_start(amount_entry, False)
658         vbox.pack_start(amount_box, False, False, 5)
659
660         self.fee_box = fee_box = gtk.HBox()
661         fee_label = gtk.Label('Fee:')
662         fee_label.set_size_request(100,-1)
663         fee_box.pack_start(fee_label, False)
664         fee_entry = gtk.Entry()
665         fee_entry.set_size_request(60, 26)
666         fee_box.pack_start(fee_entry, False)
667         vbox.pack_start(fee_box, False, False, 5)
668
669         end_box = gtk.HBox()
670         empty_label = gtk.Label('')
671         empty_label.set_size_request(100,-1)
672         end_box.pack_start(empty_label, False)
673         send_button = gtk.Button("Send")
674         send_button.show()
675         end_box.pack_start(send_button, False, False, 0)
676         clear_button = gtk.Button("Clear")
677         clear_button.show()
678         end_box.pack_start(clear_button, False, False, 15)
679         send_button.connect("clicked", self.do_send, (payto_entry, message_entry, amount_entry, fee_entry))
680         clear_button.connect("clicked", self.do_clear, (payto_entry, message_entry, amount_entry, fee_entry))
681
682         vbox.pack_start(end_box, False, False, 5)
683
684         # display this line only if there is a signature
685         payto_sig = gtk.HBox()
686         payto_sig_id = gtk.Label('')
687         payto_sig.pack_start(payto_sig_id, False)
688         vbox.pack_start(payto_sig, True, True, 5)
689         
690
691         self.user_fee = False
692
693         def entry_changed( entry, is_fee ):
694             self.funds_error = False
695             amount = numbify(amount_entry)
696             fee = numbify(fee_entry)
697             if not is_fee: fee = None
698             if amount is None:
699                 return
700             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
701             if not is_fee:
702                 fee_entry.set_text( str( Decimal( fee ) / 100000000 ) )
703                 self.fee_box.show()
704             if inputs:
705                 amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
706                 fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
707                 send_button.set_sensitive(True)
708             else:
709                 send_button.set_sensitive(False)
710                 amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000"))
711                 fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000"))
712                 self.funds_error = True
713
714         amount_entry.connect('changed', entry_changed, False)
715         fee_entry.connect('changed', entry_changed, True)        
716
717         self.payto_entry = payto_entry
718         self.payto_fee_entry = fee_entry
719         self.payto_sig_id = payto_sig_id
720         self.payto_sig = payto_sig
721         self.amount_entry = amount_entry
722         self.message_entry = message_entry
723         self.add_tab(page, 'Send')
724
725     def set_frozen(self,entry,frozen):
726         if frozen:
727             entry.set_editable(False)
728             entry.set_has_frame(False)
729             entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee"))
730         else:
731             entry.set_editable(True)
732             entry.set_has_frame(True)
733             entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#ffffff"))
734
735     def set_url(self, url):
736         payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
737         self.notebook.set_current_page(1)
738         self.payto_entry.set_text(payto)
739         self.message_entry.set_text(message)
740         self.amount_entry.set_text(amount)
741         if identity:
742             self.set_frozen(self.payto_entry,True)
743             self.set_frozen(self.amount_entry,True)
744             self.set_frozen(self.message_entry,True)
745             self.payto_sig_id.set_text( '      The bitcoin URI was signed by ' + identity )
746         else:
747             self.payto_sig.set_visible(False)
748
749     def create_about_tab(self):
750         import pango
751         page = gtk.VBox()
752         page.show()
753         tv = gtk.TextView()
754         tv.set_editable(False)
755         tv.set_cursor_visible(False)
756         tv.modify_font(pango.FontDescription(MONOSPACE_FONT))
757         scroll = gtk.ScrolledWindow()
758         scroll.add(tv)
759         page.pack_start(scroll)
760         self.info = tv.get_buffer()
761         self.add_tab(page, 'Wall')
762
763     def do_clear(self, w, data):
764         self.payto_sig.set_visible(False)
765         self.payto_fee_entry.set_text('')
766         for entry in [self.payto_entry,self.amount_entry,self.message_entry]:
767             self.set_frozen(entry,False)
768             entry.set_text('')
769
770     def question(self,msg):
771         dialog = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
772         dialog.show()
773         result = dialog.run()
774         dialog.destroy()
775         return result == gtk.RESPONSE_OK
776
777     def do_send(self, w, data):
778         payto_entry, label_entry, amount_entry, fee_entry = data
779         label = label_entry.get_text()
780         r = payto_entry.get_text()
781         r = r.strip()
782
783         m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r)
784         m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
785         
786         if m1:
787             to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
788             if not to_address:
789                 return
790             else:
791                 self.update_sending_tab()
792
793         elif m2:
794             to_address = m2.group(5)
795         else:
796             to_address = r
797
798         if not is_valid(to_address):
799             self.show_message( "invalid bitcoin address:\n"+to_address)
800             return
801
802         try:
803             amount = int( Decimal(amount_entry.get_text()) * 100000000 )
804         except:
805             self.show_message( "invalid amount")
806             return
807         try:
808             fee = int( Decimal(fee_entry.get_text()) * 100000000 )
809         except:
810             self.show_message( "invalid fee")
811             return
812
813         if self.wallet.use_encryption:
814             password = password_dialog(self.window)
815             if not password:
816                 return
817         else:
818             password = None
819
820         try:
821             tx = self.wallet.mktx( [(to_address, amount)], password, fee )
822         except BaseException, e:
823             self.show_message(str(e))
824             return
825
826         if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
827             self.show_message( "This transaction requires a higher fee, or it will not be propagated by the network." )
828             return
829
830             
831         if label: 
832             self.wallet.labels[tx.hash()] = label
833
834         status, msg = self.wallet.sendtx( tx )
835         if status:
836             self.show_message( "payment sent.\n" + msg )
837             payto_entry.set_text("")
838             label_entry.set_text("")
839             amount_entry.set_text("")
840             fee_entry.set_text("")
841             #self.fee_box.hide()
842             self.update_sending_tab()
843         else:
844             self.show_message( msg )
845
846
847     def treeview_button_press(self, treeview, event):
848         if event.type == gtk.gdk._2BUTTON_PRESS:
849             c = treeview.get_cursor()[0]
850             if treeview == self.history_treeview:
851                 tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
852                 self.show_message(tx_details)
853             elif treeview == self.contacts_treeview:
854                 m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
855                 #a = self.wallet.aliases.get(m)
856                 #if a:
857                 #    if a[0] in self.wallet.authorities.keys():
858                 #        s = self.wallet.authorities.get(a[0])
859                 #    else:
860                 #        s = "self-signed"
861                 #    msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
862                 #    self.show_message(msg)
863             
864
865     def treeview_key_press(self, treeview, event):
866         c = treeview.get_cursor()[0]
867         if event.keyval == gtk.keysyms.Up:
868             if c and c[0] == 0:
869                 treeview.parent.grab_focus()
870                 treeview.set_cursor((0,))
871         elif event.keyval == gtk.keysyms.Return:
872             if treeview == self.history_treeview:
873                 tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
874                 self.show_message(tx_details)
875             elif treeview == self.contacts_treeview:
876                 m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
877                 #a = self.wallet.aliases.get(m)
878                 #if a:
879                 #    if a[0] in self.wallet.authorities.keys():
880                 #        s = self.wallet.authorities.get(a[0])
881                 #    else:
882                 #        s = "self"
883                 #    msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
884                 #    self.show_message(msg)
885
886         return False
887
888     def create_history_tab(self):
889
890         self.history_list = gtk.ListStore(str, str, str, str, 'gboolean',  str, str, str, str)
891         treeview = gtk.TreeView(model=self.history_list)
892         self.history_treeview = treeview
893         treeview.set_tooltip_column(7)
894         treeview.show()
895         treeview.connect('key-press-event', self.treeview_key_press)
896         treeview.connect('button-press-event', self.treeview_button_press)
897
898         tvcolumn = gtk.TreeViewColumn('')
899         treeview.append_column(tvcolumn)
900         cell = gtk.CellRendererPixbuf()
901         tvcolumn.pack_start(cell, False)
902         tvcolumn.set_attributes(cell, stock_id=1)
903
904         tvcolumn = gtk.TreeViewColumn('Date')
905         treeview.append_column(tvcolumn)
906         cell = gtk.CellRendererText()
907         tvcolumn.pack_start(cell, False)
908         tvcolumn.add_attribute(cell, 'text', 2)
909
910         tvcolumn = gtk.TreeViewColumn('Description')
911         treeview.append_column(tvcolumn)
912         cell = gtk.CellRendererText()
913         cell.set_property('foreground', 'grey')
914         cell.set_property('family', MONOSPACE_FONT)
915         cell.set_property('editable', True)
916         def edited_cb(cell, path, new_text, h_list):
917             tx = h_list.get_value( h_list.get_iter(path), 0)
918             self.wallet.labels[tx] = new_text
919             self.wallet.save() 
920             self.update_history_tab()
921         cell.connect('edited', edited_cb, self.history_list)
922         def editing_started(cell, entry, path, h_list):
923             tx = h_list.get_value( h_list.get_iter(path), 0)
924             if not self.wallet.labels.get(tx): entry.set_text('')
925         cell.connect('editing-started', editing_started, self.history_list)
926         tvcolumn.set_expand(True)
927         tvcolumn.pack_start(cell, True)
928         tvcolumn.set_attributes(cell, text=3, foreground_set = 4)
929
930         tvcolumn = gtk.TreeViewColumn('Amount')
931         treeview.append_column(tvcolumn)
932         cell = gtk.CellRendererText()
933         cell.set_alignment(1, 0.5)
934         cell.set_property('family', MONOSPACE_FONT)
935         tvcolumn.pack_start(cell, False)
936         tvcolumn.add_attribute(cell, 'text', 5)
937
938         tvcolumn = gtk.TreeViewColumn('Balance')
939         treeview.append_column(tvcolumn)
940         cell = gtk.CellRendererText()
941         cell.set_alignment(1, 0.5)
942         cell.set_property('family', MONOSPACE_FONT)
943         tvcolumn.pack_start(cell, False)
944         tvcolumn.add_attribute(cell, 'text', 6)
945
946         tvcolumn = gtk.TreeViewColumn('Tooltip')
947         treeview.append_column(tvcolumn)
948         cell = gtk.CellRendererText()
949         tvcolumn.pack_start(cell, False)
950         tvcolumn.add_attribute(cell, 'text', 7)
951         tvcolumn.set_visible(False)
952
953         scroll = gtk.ScrolledWindow()
954         scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
955         scroll.add(treeview)
956
957         self.add_tab(scroll, 'History')
958         self.update_history_tab()
959
960
961     def create_recv_tab(self):
962         self.recv_list = gtk.ListStore(str, str, str)
963         self.add_tab( self.make_address_list(True), 'Receive')
964         self.update_receiving_tab()
965
966     def create_book_tab(self):
967         self.addressbook_list = gtk.ListStore(str, str, str)
968         self.add_tab( self.make_address_list(False), 'Contacts')
969         self.update_sending_tab()
970
971     def make_address_list(self, is_recv):
972         liststore = self.recv_list if is_recv else self.addressbook_list
973         treeview = gtk.TreeView(model= liststore)
974         treeview.connect('key-press-event', self.treeview_key_press)
975         treeview.connect('button-press-event', self.treeview_button_press)
976         treeview.show()
977         if not is_recv:
978             self.contacts_treeview = treeview
979
980         tvcolumn = gtk.TreeViewColumn('Address')
981         treeview.append_column(tvcolumn)
982         cell = gtk.CellRendererText()
983         cell.set_property('family', MONOSPACE_FONT)
984         tvcolumn.pack_start(cell, True)
985         tvcolumn.add_attribute(cell, 'text', 0)
986
987         tvcolumn = gtk.TreeViewColumn('Label')
988         tvcolumn.set_expand(True)
989         treeview.append_column(tvcolumn)
990         cell = gtk.CellRendererText()
991         cell.set_property('editable', True)
992         def edited_cb2(cell, path, new_text, liststore):
993             address = liststore.get_value( liststore.get_iter(path), 0)
994             self.wallet.labels[address] = new_text
995             self.wallet.save() 
996             self.update_receiving_tab()
997             self.update_sending_tab()
998             self.update_history_tab()
999         cell.connect('edited', edited_cb2, liststore)
1000         tvcolumn.pack_start(cell, True)
1001         tvcolumn.add_attribute(cell, 'text', 1)
1002
1003         tvcolumn = gtk.TreeViewColumn('Tx')
1004         treeview.append_column(tvcolumn)
1005         cell = gtk.CellRendererText()
1006         tvcolumn.pack_start(cell, True)
1007         tvcolumn.add_attribute(cell, 'text', 2)
1008
1009         scroll = gtk.ScrolledWindow()
1010         scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
1011         scroll.add(treeview)
1012
1013         hbox = gtk.HBox()
1014         if not is_recv:
1015             button = gtk.Button("New")
1016             button.connect("clicked", self.newaddress_dialog)
1017             button.show()
1018             hbox.pack_start(button,False)
1019
1020         def showqrcode(w, treeview, liststore):
1021             path, col = treeview.get_cursor()
1022             if not path: return
1023             address = liststore.get_value(liststore.get_iter(path), 0)
1024             qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H)
1025             qr.addData(address)
1026             qr.make()
1027             boxsize = 7
1028             size = qr.getModuleCount()*boxsize
1029             def area_expose_cb(area, event):
1030                 style = area.get_style()
1031                 k = qr.getModuleCount()
1032                 for r in range(k):
1033                     for c in range(k):
1034                         gc = style.black_gc if qr.isDark(r, c) else style.white_gc
1035                         area.window.draw_rectangle(gc, True, c*boxsize, r*boxsize, boxsize, boxsize)
1036             area = gtk.DrawingArea()
1037             area.set_size_request(size, size)
1038             area.connect("expose-event", area_expose_cb)
1039             area.show()
1040             dialog = gtk.Dialog(address, parent=self.window, flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, buttons = ("ok",1))
1041             dialog.vbox.add(area)
1042             dialog.run()
1043             dialog.destroy()
1044
1045         button = gtk.Button("QR")
1046         button.connect("clicked", showqrcode, treeview, liststore)
1047         button.show()
1048         hbox.pack_start(button,False)
1049
1050         button = gtk.Button("Copy to clipboard")
1051         def copy2clipboard(w, treeview, liststore):
1052             import platform
1053             path, col =  treeview.get_cursor()
1054             if path:
1055                 address =  liststore.get_value( liststore.get_iter(path), 0)
1056                 if platform.system() == 'Windows':
1057                     from Tkinter import Tk
1058                     r = Tk()
1059                     r.withdraw()
1060                     r.clipboard_clear()
1061                     r.clipboard_append( address )
1062                     r.destroy()
1063                 else:
1064                     c = gtk.clipboard_get()
1065                     c.set_text( address )
1066         button.connect("clicked", copy2clipboard, treeview, liststore)
1067         button.show()
1068         hbox.pack_start(button,False)
1069
1070         if not is_recv:
1071             button = gtk.Button("Pay to")
1072             def payto(w, treeview, liststore):
1073                 path, col =  treeview.get_cursor()
1074                 if path:
1075                     address =  liststore.get_value( liststore.get_iter(path), 0)
1076                     self.payto_entry.set_text( address )
1077                     self.notebook.set_current_page(1)
1078                     self.amount_entry.grab_focus()
1079
1080             button.connect("clicked", payto, treeview, liststore)
1081             button.show()
1082             hbox.pack_start(button,False)
1083
1084         vbox = gtk.VBox()
1085         vbox.pack_start(scroll,True)
1086         vbox.pack_start(hbox, False)
1087         return vbox
1088
1089     def update_status_bar(self):
1090         interface = self.network.interface
1091         if self.funds_error:
1092             text = "Not enough funds"
1093         elif interface and interface.is_connected:
1094             self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.network.blockchain.height))
1095             if not self.wallet.up_to_date:
1096                 self.status_image.set_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_MENU)
1097                 text = "Synchronizing..."
1098             else:
1099                 self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
1100                 self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.network.blockchain.height))
1101                 c, u = self.wallet.get_balance()
1102                 text =  "Balance: %s "%( format_satoshis(c,False,self.num_zeros) )
1103                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.num_zeros).strip() )
1104         else:
1105             self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
1106             self.network_button.set_tooltip_text("Not connected.")
1107             text = "Not connected"
1108
1109         self.status_bar.pop(self.context_id) 
1110         self.status_bar.push(self.context_id, text)
1111
1112         if self.wallet.up_to_date and self.wallet_updated:
1113             self.update_history_tab()
1114             self.update_receiving_tab()
1115             # addressbook too...
1116             self.info.set_text( self.network.banner )
1117             self.wallet_updated = False
1118
1119     def update_receiving_tab(self):
1120         self.recv_list.clear()
1121         for address in self.wallet.addresses(True):
1122             if self.wallet.is_change(address):continue
1123             label = self.wallet.labels.get(address)
1124             h = self.wallet.history.get(address,[])
1125             n = len(h)
1126             tx = "None" if n==0 else "%d"%n
1127             self.recv_list.append((address, label, tx ))
1128
1129     def update_sending_tab(self):
1130         # detect addresses that are not mine in history, add them here...
1131         self.addressbook_list.clear()
1132         #for alias, v in self.wallet.aliases.items():
1133         #    s, target = v
1134         #    label = self.wallet.labels.get(alias)
1135         #    self.addressbook_list.append((alias, label, '-'))
1136             
1137         for address in self.wallet.addressbook:
1138             label = self.wallet.labels.get(address)
1139             n = self.wallet.get_num_tx(address)
1140             self.addressbook_list.append((address, label, "%d"%n))
1141
1142     def update_history_tab(self):
1143         cursor = self.history_treeview.get_cursor()[0]
1144         self.history_list.clear()
1145
1146         for item in self.wallet.get_tx_history():
1147             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
1148             if conf > 0:
1149                 try:
1150                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
1151                 except:
1152                     time_str = "------"
1153                 conf_icon = gtk.STOCK_APPLY
1154             elif conf == -1:
1155                 time_str = 'unverified'
1156                 conf_icon = None
1157             else:
1158                 time_str = 'pending'
1159                 conf_icon = gtk.STOCK_EXECUTE
1160
1161             label, is_default_label = self.wallet.get_label(tx_hash)
1162             tooltip = tx_hash + "\n%d confirmations"%conf if tx_hash else ''
1163             details = self.get_tx_details(tx_hash)
1164
1165             self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
1166                                         format_satoshis(value,True,self.num_zeros, whitespaces=True),
1167                                         format_satoshis(balance,False,self.num_zeros, whitespaces=True), tooltip, details] )
1168         if cursor: self.history_treeview.set_cursor( cursor )
1169
1170
1171     def get_tx_details(self, tx_hash):
1172         import datetime
1173         if not tx_hash: return ''
1174         tx = self.wallet.transactions.get(tx_hash)
1175         is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
1176         conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
1177
1178         if timestamp:
1179             time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
1180         else:
1181             time_str = 'pending'
1182
1183         inputs = map(lambda x: x.get('address'), tx.inputs)
1184         outputs = map(lambda x: x.get('address'), tx.d['outputs'])
1185         tx_details = "Transaction Details" +"\n\n" \
1186             + "Transaction ID:\n" + tx_hash + "\n\n" \
1187             + "Status: %d confirmations\n"%conf
1188         if is_mine:
1189             if fee: 
1190                 tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
1191                               + "Transaction fee: %s\n"% format_satoshis(fee, False)
1192             else:
1193                 tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
1194                               + "Transaction fee: unknown\n"
1195         else:
1196             tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
1197
1198         tx_details += "Date: %s\n\n"%time_str \
1199             + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
1200             + "Outputs:\n-"+ '\n-'.join(outputs)
1201
1202         return tx_details
1203
1204
1205
1206     def newaddress_dialog(self, w):
1207
1208         title = "New Contact" 
1209         dialog = gtk.Dialog(title, parent=self.window, 
1210                             flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, 
1211                             buttons= ("cancel", 0, "ok",1)  )
1212         dialog.show()
1213
1214         label = gtk.HBox()
1215         label_label = gtk.Label('Label:')
1216         label_label.set_size_request(120,10)
1217         label_label.show()
1218         label.pack_start(label_label)
1219         label_entry = gtk.Entry()
1220         label_entry.show()
1221         label.pack_start(label_entry)
1222         label.show()
1223         dialog.vbox.pack_start(label, False, True, 5)
1224
1225         address = gtk.HBox()
1226         address_label = gtk.Label('Address:')
1227         address_label.set_size_request(120,10)
1228         address_label.show()
1229         address.pack_start(address_label)
1230         address_entry = gtk.Entry()
1231         address_entry.show()
1232         address.pack_start(address_entry)
1233         address.show()
1234         dialog.vbox.pack_start(address, False, True, 5)
1235         
1236         result = dialog.run()
1237         address = address_entry.get_text()
1238         label = label_entry.get_text()
1239         dialog.destroy()
1240
1241         if result == 1:
1242             if is_valid(address):
1243                 self.wallet.add_contact(address,label)
1244                 self.update_sending_tab()
1245             else:
1246                 errorDialog = gtk.MessageDialog(
1247                     parent=self.window,
1248                     flags=gtk.DIALOG_MODAL, 
1249                     buttons= gtk.BUTTONS_CLOSE, 
1250                     message_format = "Invalid address")
1251                 errorDialog.show()
1252                 errorDialog.run()
1253                 errorDialog.destroy()
1254
1255     
1256
1257 class ElectrumGui():
1258
1259     def __init__(self, config, network):
1260         self.network = network
1261         self.config = config
1262
1263
1264     def main(self, url=None):
1265
1266         storage = WalletStorage(self.config)
1267         if not storage.file_exists:
1268             action = self.restore_or_create()
1269             if not action:
1270                 exit()
1271             self.wallet = wallet = Wallet(storage)
1272             gap = self.config.get('gap_limit', 5)
1273             if gap != 5:
1274                 wallet.gap_limit = gap
1275                 wallet.storage.put('gap_limit', gap, True)
1276
1277             self.wallet.start_threads(self.network)
1278
1279             if action == 'create':
1280                 wallet.init_seed(None)
1281                 wallet.save_seed()
1282                 wallet.create_accounts()
1283                 wallet.synchronize()  # generate first addresses offline
1284             elif action == 'restore':
1285                 seed = self.seed_dialog()
1286                 wallet.init_seed(seed)
1287                 wallet.save_seed()
1288                 self.restore_wallet(wallet)
1289                 
1290             else:
1291                 exit()
1292         else:
1293             self.wallet = Wallet(storage)
1294             self.wallet.start_threads(self.network)
1295
1296         w = ElectrumWindow(self.wallet, self.config, self.network)
1297         if url: w.set_url(url)
1298         gtk.main()
1299
1300     def restore_or_create(self):
1301         return restore_create_dialog()
1302
1303     def seed_dialog(self):
1304         return run_recovery_dialog()
1305
1306     def verify_seed(self):
1307         self.wallet.save_seed()
1308         return True
1309
1310     def network_dialog(self):
1311         return run_network_dialog( self.network, parent=None )
1312
1313     def show_seed(self):
1314         show_seed_dialog(self.wallet, None, None)
1315
1316     def password_dialog(self):
1317         change_password_dialog(self.wallet, None, None)
1318
1319     def restore_wallet(self, wallet):
1320
1321         dialog = gtk.MessageDialog(
1322             parent = None,
1323             flags = gtk.DIALOG_MODAL, 
1324             buttons = gtk.BUTTONS_CANCEL, 
1325             message_format = "Please wait..."  )
1326         dialog.show()
1327
1328         def recover_thread( wallet, dialog ):
1329             wallet.restore(lambda x:x)
1330             gobject.idle_add( dialog.destroy )
1331
1332         thread.start_new_thread( recover_thread, ( wallet, dialog ) )
1333         r = dialog.run()
1334         dialog.destroy()
1335         if r==gtk.RESPONSE_CANCEL: return False
1336         if not wallet.is_found():
1337             show_message("No transactions found for this seed")
1338
1339         return True