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