56be0dccd670080585f798c438831468446a103f
[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.decode_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     return seed, gap
168
169
170
171 def run_settings_dialog(wallet, parent):
172
173     message = "Here are the settings of your wallet. For more explanations, click on the question mark buttons next to each input field."
174         
175     dialog = gtk.MessageDialog(
176         parent = parent,
177         flags = gtk.DIALOG_MODAL, 
178         buttons = gtk.BUTTONS_OK_CANCEL,
179         message_format = message)
180
181     image = gtk.Image()
182     image.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG)
183     image.show()
184     dialog.set_image(image)
185     dialog.set_title("Settings")
186
187     vbox = dialog.vbox
188     dialog.set_default_response(gtk.RESPONSE_OK)
189
190     fee = gtk.HBox()
191     fee_entry = gtk.Entry()
192     fee_label = gtk.Label('Transaction fee:')
193     fee_label.set_size_request(150,10)
194     fee_label.show()
195     fee.pack_start(fee_label,False, False, 10)
196     fee_entry.set_text( str( Decimal(wallet.fee) /100000000 ) )
197     fee_entry.connect('changed', numbify, False)
198     fee_entry.show()
199     fee.pack_start(fee_entry,False,False, 10)
200     add_help_button(fee, 'Fee per transaction input. Transactions involving multiple inputs tend to have a higher fee. Recommended value:0.0005')
201     fee.show()
202     vbox.pack_start(fee, False,False, 5)
203             
204     nz = gtk.HBox()
205     nz_entry = gtk.Entry()
206     nz_label = gtk.Label('Display zeros:')
207     nz_label.set_size_request(150,10)
208     nz_label.show()
209     nz.pack_start(nz_label,False, False, 10)
210     nz_entry.set_text( str( wallet.num_zeros ))
211     nz_entry.connect('changed', numbify, True)
212     nz_entry.show()
213     nz.pack_start(nz_entry,False,False, 10)
214     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'")
215     nz.show()
216     vbox.pack_start(nz, False,False, 5)
217             
218     # gui setting
219     gui_box = gtk.HBox()
220     gui_label = gtk.Label('Default GUI:')
221     gui_label.set_size_request(150,10)
222     gui_label.show()
223     gui_box.pack_start(gui_label,False, False, 10)
224     gui_combo = gtk.combo_box_new_text()
225     gui_names = ['lite', 'classic', 'gtk', 'text']
226     for name in gui_names: gui_combo.append_text(name.capitalize())
227     gui_combo.show()
228     gui_box.pack_start(gui_combo,False, False, 10)
229     gui_combo.set_active( gui_names.index( wallet.config.get("gui","lite")) )
230     gui_box.show()
231     add_help_button(gui_box, "Select which GUI mode to use at start up.")
232
233     vbox.pack_start(gui_box, False,False, 5)
234
235     dialog.show()
236     r = dialog.run()
237     fee = fee_entry.get_text()
238     nz = nz_entry.get_text()
239     gui = gui_names[ gui_combo.get_active()]
240         
241     dialog.destroy()
242     if r==gtk.RESPONSE_CANCEL:
243         return
244
245     try:
246         fee = int( 100000000 * Decimal(fee) )
247     except:
248         show_message("error")
249         return
250     if wallet.fee != fee:
251         wallet.fee = fee
252         wallet.save()
253
254     try:
255         nz = int( nz )
256         if nz>8: nz = 8
257     except:
258         show_message("error")
259         return
260     if wallet.num_zeros != nz:
261         wallet.num_zeros = nz
262         wallet.save()
263
264     wallet.config.set_key('gui',gui,True)
265
266
267
268
269 def run_network_dialog( wallet, parent ):
270     image = gtk.Image()
271     image.set_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_DIALOG)
272     interface = wallet.interface
273     if parent:
274         if interface.is_connected:
275             status = "Connected to %s:%d\n%d blocks"%(interface.host, interface.port, wallet.verifier.height)
276         else:
277             status = "Not connected"
278     else:
279         import random
280         status = "Please choose a server.\nSelect cancel if you are offline."
281
282     server = interface.server
283     plist, servers_list = interface.get_servers_list()
284
285     dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
286                                     gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, status)
287     dialog.set_title("Server")
288     dialog.set_image(image)
289     image.show()
290     
291     vbox = dialog.vbox
292     host_box = gtk.HBox()
293     host_label = gtk.Label('Connect to:')
294     host_label.set_size_request(100,-1)
295     host_label.show()
296     host_box.pack_start(host_label, False, False, 10)
297     host_entry = gtk.Entry()
298     host_entry.set_size_request(200,-1)
299     host_entry.set_text(server)
300     host_entry.show()
301     host_box.pack_start(host_entry, False, False, 10)
302     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)')
303     host_box.show()
304
305
306     p_box = gtk.HBox(False, 10)
307     p_box.show()
308
309     p_label = gtk.Label('Protocol:')
310     p_label.set_size_request(100,-1)
311     p_label.show()
312     p_box.pack_start(p_label, False, False, 10)
313
314     radio1 = gtk.RadioButton(None, "tcp")
315     p_box.pack_start(radio1, True, True, 0)
316     radio1.show()
317     radio2 = gtk.RadioButton(radio1, "http")
318     p_box.pack_start(radio2, True, True, 0)
319     radio2.show()
320
321     def current_line():
322         return unicode(host_entry.get_text()).split(':')
323     
324     def set_button(protocol):
325         if protocol == 't':
326             radio1.set_active(1)
327         elif protocol == 'h':
328             radio2.set_active(1)
329
330     def set_protocol(protocol):
331         host = current_line()[0]
332         pp = plist[host]
333         if protocol not in pp.keys():
334             protocol = pp.keys()[0]
335             set_button(protocol)
336         port = pp[protocol]
337         host_entry.set_text( host + ':' + port + ':' + protocol)
338
339     radio1.connect("toggled", lambda x,y:set_protocol('t'), "radio button 1")
340     radio2.connect("toggled", lambda x,y:set_protocol('h'), "radio button 1")
341         
342     server_list = gtk.ListStore(str)
343     for host in plist.keys():
344         server_list.append([host])
345     
346     treeview = gtk.TreeView(model=server_list)
347     treeview.show()
348
349     if wallet.interface.servers:
350         label = 'Active Servers'
351     else:
352         label = 'Default Servers'
353         
354     tvcolumn = gtk.TreeViewColumn(label)
355     treeview.append_column(tvcolumn)
356     cell = gtk.CellRendererText()
357     tvcolumn.pack_start(cell, False)
358     tvcolumn.add_attribute(cell, 'text', 0)
359
360     scroll = gtk.ScrolledWindow()
361     scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
362     scroll.add(treeview)
363     scroll.show()
364
365     vbox.pack_start(host_box, False,False, 5)
366     vbox.pack_start(p_box, True, True, 0)
367     vbox.pack_start(scroll)
368
369     def my_treeview_cb(treeview):
370         path, view_column = treeview.get_cursor()
371         host = server_list.get_value( server_list.get_iter(path), 0)
372
373         pp = plist[host]
374         if 't' in pp.keys():
375             protocol = 't'
376         else:
377             protocol = pp.keys()[0]
378         port = pp[protocol]
379         host_entry.set_text( host + ':' + port + ':' + protocol)
380         set_button(protocol)
381
382     treeview.connect('cursor-changed', my_treeview_cb)
383
384     dialog.show()
385     r = dialog.run()
386     server = host_entry.get_text()
387     dialog.destroy()
388
389     if r==gtk.RESPONSE_CANCEL:
390         return False
391
392     try:
393         interface.set_server(server)
394     except:
395         show_message("error:" + server)
396         return False
397
398     if parent:
399         wallet.config.set_key("server", server, True)
400     return True
401
402
403
404 def show_message(message, parent=None):
405     dialog = gtk.MessageDialog(
406         parent = parent,
407         flags = gtk.DIALOG_MODAL, 
408         buttons = gtk.BUTTONS_CLOSE, 
409         message_format = message )
410     dialog.show()
411     dialog.run()
412     dialog.destroy()
413
414 def password_line(label):
415     password = gtk.HBox()
416     password_label = gtk.Label(label)
417     password_label.set_size_request(120,10)
418     password_label.show()
419     password.pack_start(password_label,False, False, 10)
420     password_entry = gtk.Entry()
421     password_entry.set_size_request(300,-1)
422     password_entry.set_visibility(False)
423     password_entry.show()
424     password.pack_start(password_entry,False,False, 10)
425     password.show()
426     return password, password_entry
427
428 def password_dialog(parent):
429     dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
430                                 gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL,  "Please enter your password.")
431     dialog.get_image().set_visible(False)
432     current_pw, current_pw_entry = password_line('Password:')
433     current_pw_entry.connect("activate", lambda entry, dialog, response: dialog.response(response), dialog, gtk.RESPONSE_OK)
434     dialog.vbox.pack_start(current_pw, False, True, 0)
435     dialog.show()
436     result = dialog.run()
437     pw = current_pw_entry.get_text()
438     dialog.destroy()
439     if result != gtk.RESPONSE_CANCEL: return pw
440
441 def change_password_dialog(wallet, parent, icon):
442     if not wallet.seed:
443         show_message("No seed")
444         return
445
446     if parent:
447         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'
448     else:
449         msg = "Please choose a password to encrypt your wallet keys"
450
451     dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
452     dialog.set_title("Change password")
453     image = gtk.Image()
454     image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_DIALOG)
455     image.show()
456     dialog.set_image(image)
457
458     if wallet.use_encryption:
459         current_pw, current_pw_entry = password_line('Current password:')
460         dialog.vbox.pack_start(current_pw, False, True, 0)
461
462     password, password_entry = password_line('New password:')
463     dialog.vbox.pack_start(password, False, True, 5)
464     password2, password2_entry = password_line('Confirm password:')
465     dialog.vbox.pack_start(password2, False, True, 5)
466
467     dialog.show()
468     result = dialog.run()
469     password = current_pw_entry.get_text() if wallet.use_encryption else None
470     new_password = password_entry.get_text()
471     new_password2 = password2_entry.get_text()
472     dialog.destroy()
473     if result == gtk.RESPONSE_CANCEL: 
474         return
475
476     try:
477         seed = wallet.decode_seed(password)
478     except:
479         show_message("Incorrect password")
480         return
481
482     if new_password != new_password2:
483         show_message("passwords do not match")
484         return
485
486     wallet.update_password(seed, password, new_password)
487
488     if icon:
489         if wallet.use_encryption:
490             icon.set_tooltip_text('wallet is encrypted')
491         else:
492             icon.set_tooltip_text('wallet is unencrypted')
493
494
495 def add_help_button(hbox, message):
496     button = gtk.Button('?')
497     button.connect("clicked", lambda x: show_message(message))
498     button.show()
499     hbox.pack_start(button,False, False)
500
501
502 class MyWindow(gtk.Window): __gsignals__ = dict( mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, None, (str,)) )
503
504 gobject.type_register(MyWindow)
505 gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.W, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+W')
506 gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.Q, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+Q')
507
508
509 class ElectrumWindow:
510
511     def show_message(self, msg):
512         show_message(msg, self.window)
513
514     def __init__(self, wallet, config):
515         self.config = config
516         self.wallet = wallet
517         self.funds_error = False # True if not enough funds
518
519         self.window = MyWindow(gtk.WINDOW_TOPLEVEL)
520         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.config.path
521         if not self.wallet.seed: title += ' [seedless]'
522         self.window.set_title(title)
523         self.window.connect("destroy", gtk.main_quit)
524         self.window.set_border_width(0)
525         self.window.connect('mykeypress', gtk.main_quit)
526         self.window.set_default_size(720, 350)
527         self.wallet_updated = False
528
529         vbox = gtk.VBox()
530
531         self.notebook = gtk.Notebook()
532         self.create_history_tab()
533         if self.wallet.seed:
534             self.create_send_tab()
535         self.create_recv_tab()
536         self.create_book_tab()
537         self.create_about_tab()
538         self.notebook.show()
539         vbox.pack_start(self.notebook, True, True, 2)
540         
541         self.status_bar = gtk.Statusbar()
542         vbox.pack_start(self.status_bar, False, False, 0)
543
544         self.status_image = gtk.Image()
545         self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
546         self.status_image.set_alignment(True, 0.5  )
547         self.status_image.show()
548
549         self.network_button = gtk.Button()
550         self.network_button.connect("clicked", lambda x: run_network_dialog(self.wallet, self.window) )
551         self.network_button.add(self.status_image)
552         self.network_button.set_relief(gtk.RELIEF_NONE)
553         self.network_button.show()
554         self.status_bar.pack_end(self.network_button, False, False)
555
556         if self.wallet.seed:
557             def seedb(w, wallet):
558                 if wallet.use_encryption:
559                     password = password_dialog(self.window)
560                     if not password: return
561                 else: password = None
562                 show_seed_dialog(wallet, password, self.window)
563             button = gtk.Button('S')
564             button.connect("clicked", seedb, wallet )
565             button.set_relief(gtk.RELIEF_NONE)
566             button.show()
567             self.status_bar.pack_end(button,False, False)
568
569         settings_icon = gtk.Image()
570         settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
571         settings_icon.set_alignment(0.5, 0.5)
572         settings_icon.set_size_request(16,16 )
573         settings_icon.show()
574
575         prefs_button = gtk.Button()
576         prefs_button.connect("clicked", lambda x: run_settings_dialog(self.wallet, self.window) )
577         prefs_button.add(settings_icon)
578         prefs_button.set_tooltip_text("Settings")
579         prefs_button.set_relief(gtk.RELIEF_NONE)
580         prefs_button.show()
581         self.status_bar.pack_end(prefs_button,False,False)
582
583         pw_icon = gtk.Image()
584         pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU)
585         pw_icon.set_alignment(0.5, 0.5)
586         pw_icon.set_size_request(16,16 )
587         pw_icon.show()
588
589         if self.wallet.seed:
590             password_button = gtk.Button()
591             password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon))
592             password_button.add(pw_icon)
593             password_button.set_relief(gtk.RELIEF_NONE)
594             password_button.show()
595             self.status_bar.pack_end(password_button,False,False)
596
597         self.window.add(vbox)
598         self.window.show_all()
599         #self.fee_box.hide()
600
601         self.context_id = self.status_bar.get_context_id("statusbar")
602         self.update_status_bar()
603
604         self.wallet.interface.register_callback('updated', self.update_callback)
605
606
607         def update_status_bar_thread():
608             while True:
609                 gobject.idle_add( self.update_status_bar )
610                 time.sleep(0.5)
611
612
613         def check_recipient_thread():
614             old_r = ''
615             while True:
616                 time.sleep(0.5)
617                 if self.payto_entry.is_focus():
618                     continue
619                 r = self.payto_entry.get_text()
620                 if r != old_r:
621                     old_r = r
622                     r = r.strip()
623                     if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
624                         try:
625                             to_address = self.wallet.get_alias(r, interactive=False)
626                         except:
627                             continue
628                         if to_address:
629                             s = r + ' <' + to_address + '>'
630                             gobject.idle_add( lambda: self.payto_entry.set_text(s) )
631                 
632
633         thread.start_new_thread(update_status_bar_thread, ())
634         if self.wallet.seed:
635             thread.start_new_thread(check_recipient_thread, ())
636         self.notebook.set_current_page(0)
637
638     def update_callback(self):
639         self.wallet_updated = True
640
641
642     def add_tab(self, page, name):
643         tab_label = gtk.Label(name)
644         tab_label.show()
645         self.notebook.append_page(page, tab_label)
646
647
648     def create_send_tab(self):
649         
650         page = vbox = gtk.VBox()
651         page.show()
652
653         payto = gtk.HBox()
654         payto_label = gtk.Label('Pay to:')
655         payto_label.set_size_request(100,-1)
656         payto.pack_start(payto_label, False)
657         payto_entry = gtk.Entry()
658         payto_entry.set_size_request(450, 26)
659         payto.pack_start(payto_entry, False)
660         vbox.pack_start(payto, False, False, 5)
661
662         message = gtk.HBox()
663         message_label = gtk.Label('Description:')
664         message_label.set_size_request(100,-1)
665         message.pack_start(message_label, False)
666         message_entry = gtk.Entry()
667         message_entry.set_size_request(450, 26)
668         message.pack_start(message_entry, False)
669         vbox.pack_start(message, False, False, 5)
670
671         amount_box = gtk.HBox()
672         amount_label = gtk.Label('Amount:')
673         amount_label.set_size_request(100,-1)
674         amount_box.pack_start(amount_label, False)
675         amount_entry = gtk.Entry()
676         amount_entry.set_size_request(120, -1)
677         amount_box.pack_start(amount_entry, False)
678         vbox.pack_start(amount_box, False, False, 5)
679
680         self.fee_box = fee_box = gtk.HBox()
681         fee_label = gtk.Label('Fee:')
682         fee_label.set_size_request(100,-1)
683         fee_box.pack_start(fee_label, False)
684         fee_entry = gtk.Entry()
685         fee_entry.set_size_request(60, 26)
686         fee_box.pack_start(fee_entry, False)
687         vbox.pack_start(fee_box, False, False, 5)
688
689         end_box = gtk.HBox()
690         empty_label = gtk.Label('')
691         empty_label.set_size_request(100,-1)
692         end_box.pack_start(empty_label, False)
693         send_button = gtk.Button("Send")
694         send_button.show()
695         end_box.pack_start(send_button, False, False, 0)
696         clear_button = gtk.Button("Clear")
697         clear_button.show()
698         end_box.pack_start(clear_button, False, False, 15)
699         send_button.connect("clicked", self.do_send, (payto_entry, message_entry, amount_entry, fee_entry))
700         clear_button.connect("clicked", self.do_clear, (payto_entry, message_entry, amount_entry, fee_entry))
701
702         vbox.pack_start(end_box, False, False, 5)
703
704         # display this line only if there is a signature
705         payto_sig = gtk.HBox()
706         payto_sig_id = gtk.Label('')
707         payto_sig.pack_start(payto_sig_id, False)
708         vbox.pack_start(payto_sig, True, True, 5)
709         
710
711         self.user_fee = False
712
713         def entry_changed( entry, is_fee ):
714             self.funds_error = False
715             amount = numbify(amount_entry)
716             fee = numbify(fee_entry)
717             if not is_fee: fee = None
718             if amount is None:
719                 return
720             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
721             if not is_fee:
722                 fee_entry.set_text( str( Decimal( fee ) / 100000000 ) )
723                 self.fee_box.show()
724             if inputs:
725                 amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
726                 fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
727                 send_button.set_sensitive(True)
728             else:
729                 send_button.set_sensitive(False)
730                 amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000"))
731                 fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000"))
732                 self.funds_error = True
733
734         amount_entry.connect('changed', entry_changed, False)
735         fee_entry.connect('changed', entry_changed, True)        
736
737         self.payto_entry = payto_entry
738         self.payto_fee_entry = fee_entry
739         self.payto_sig_id = payto_sig_id
740         self.payto_sig = payto_sig
741         self.amount_entry = amount_entry
742         self.message_entry = message_entry
743         self.add_tab(page, 'Send')
744
745     def set_frozen(self,entry,frozen):
746         if frozen:
747             entry.set_editable(False)
748             entry.set_has_frame(False)
749             entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee"))
750         else:
751             entry.set_editable(True)
752             entry.set_has_frame(True)
753             entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#ffffff"))
754
755     def set_url(self, url):
756         payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
757         self.notebook.set_current_page(1)
758         self.payto_entry.set_text(payto)
759         self.message_entry.set_text(message)
760         self.amount_entry.set_text(amount)
761         if identity:
762             self.set_frozen(self.payto_entry,True)
763             self.set_frozen(self.amount_entry,True)
764             self.set_frozen(self.message_entry,True)
765             self.payto_sig_id.set_text( '      The bitcoin URI was signed by ' + identity )
766         else:
767             self.payto_sig.set_visible(False)
768
769     def create_about_tab(self):
770         import pango
771         page = gtk.VBox()
772         page.show()
773         tv = gtk.TextView()
774         tv.set_editable(False)
775         tv.set_cursor_visible(False)
776         tv.modify_font(pango.FontDescription(MONOSPACE_FONT))
777         scroll = gtk.ScrolledWindow()
778         scroll.add(tv)
779         page.pack_start(scroll)
780         self.info = tv.get_buffer()
781         self.add_tab(page, 'Wall')
782
783     def do_clear(self, w, data):
784         self.payto_sig.set_visible(False)
785         self.payto_fee_entry.set_text('')
786         for entry in [self.payto_entry,self.amount_entry,self.message_entry]:
787             self.set_frozen(entry,False)
788             entry.set_text('')
789
790     def question(self,msg):
791         dialog = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
792         dialog.show()
793         result = dialog.run()
794         dialog.destroy()
795         return result == gtk.RESPONSE_OK
796
797     def do_send(self, w, data):
798         payto_entry, label_entry, amount_entry, fee_entry = data
799         label = label_entry.get_text()
800         r = payto_entry.get_text()
801         r = r.strip()
802
803         m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r)
804         m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
805         
806         if m1:
807             to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
808             if not to_address:
809                 return
810             else:
811                 self.update_sending_tab()
812
813         elif m2:
814             to_address = m2.group(5)
815         else:
816             to_address = r
817
818         if not self.wallet.is_valid(to_address):
819             self.show_message( "invalid bitcoin address:\n"+to_address)
820             return
821
822         try:
823             amount = int( Decimal(amount_entry.get_text()) * 100000000 )
824         except:
825             self.show_message( "invalid amount")
826             return
827         try:
828             fee = int( Decimal(fee_entry.get_text()) * 100000000 )
829         except:
830             self.show_message( "invalid fee")
831             return
832
833         if self.wallet.use_encryption:
834             password = password_dialog(self.window)
835             if not password:
836                 return
837         else:
838             password = None
839
840         try:
841             tx = self.wallet.mktx( [(to_address, amount)], password, fee )
842         except BaseException, e:
843             self.show_message(str(e))
844             return
845             
846         if label: 
847             self.wallet.labels[tx.hash()] = label
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.update_receiving_tab()
1012             self.update_sending_tab()
1013             self.update_history_tab()
1014         cell.connect('edited', edited_cb2, liststore)
1015         tvcolumn.pack_start(cell, True)
1016         tvcolumn.add_attribute(cell, 'text', 1)
1017
1018         tvcolumn = gtk.TreeViewColumn('Tx')
1019         treeview.append_column(tvcolumn)
1020         cell = gtk.CellRendererText()
1021         tvcolumn.pack_start(cell, True)
1022         tvcolumn.add_attribute(cell, 'text', 2)
1023
1024         scroll = gtk.ScrolledWindow()
1025         scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
1026         scroll.add(treeview)
1027
1028         hbox = gtk.HBox()
1029         if not is_recv:
1030             button = gtk.Button("New")
1031             button.connect("clicked", self.newaddress_dialog)
1032             button.show()
1033             hbox.pack_start(button,False)
1034
1035         def showqrcode(w, treeview, liststore):
1036             path, col = treeview.get_cursor()
1037             if not path: return
1038             address = liststore.get_value(liststore.get_iter(path), 0)
1039             qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H)
1040             qr.addData(address)
1041             qr.make()
1042             boxsize = 7
1043             size = qr.getModuleCount()*boxsize
1044             def area_expose_cb(area, event):
1045                 style = area.get_style()
1046                 k = qr.getModuleCount()
1047                 for r in range(k):
1048                     for c in range(k):
1049                         gc = style.black_gc if qr.isDark(r, c) else style.white_gc
1050                         area.window.draw_rectangle(gc, True, c*boxsize, r*boxsize, boxsize, boxsize)
1051             area = gtk.DrawingArea()
1052             area.set_size_request(size, size)
1053             area.connect("expose-event", area_expose_cb)
1054             area.show()
1055             dialog = gtk.Dialog(address, parent=self.window, flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, buttons = ("ok",1))
1056             dialog.vbox.add(area)
1057             dialog.run()
1058             dialog.destroy()
1059
1060         button = gtk.Button("QR")
1061         button.connect("clicked", showqrcode, treeview, liststore)
1062         button.show()
1063         hbox.pack_start(button,False)
1064
1065         button = gtk.Button("Copy to clipboard")
1066         def copy2clipboard(w, treeview, liststore):
1067             import platform
1068             path, col =  treeview.get_cursor()
1069             if path:
1070                 address =  liststore.get_value( liststore.get_iter(path), 0)
1071                 if platform.system() == 'Windows':
1072                     from Tkinter import Tk
1073                     r = Tk()
1074                     r.withdraw()
1075                     r.clipboard_clear()
1076                     r.clipboard_append( address )
1077                     r.destroy()
1078                 else:
1079                     c = gtk.clipboard_get()
1080                     c.set_text( address )
1081         button.connect("clicked", copy2clipboard, treeview, liststore)
1082         button.show()
1083         hbox.pack_start(button,False)
1084
1085         if not is_recv:
1086             button = gtk.Button("Pay to")
1087             def payto(w, treeview, liststore):
1088                 path, col =  treeview.get_cursor()
1089                 if path:
1090                     address =  liststore.get_value( liststore.get_iter(path), 0)
1091                     self.payto_entry.set_text( address )
1092                     self.notebook.set_current_page(1)
1093                     self.amount_entry.grab_focus()
1094
1095             button.connect("clicked", payto, treeview, liststore)
1096             button.show()
1097             hbox.pack_start(button,False)
1098
1099         vbox = gtk.VBox()
1100         vbox.pack_start(scroll,True)
1101         vbox.pack_start(hbox, False)
1102         return vbox
1103
1104     def update_status_bar(self):
1105         interface = self.wallet.interface
1106         if self.funds_error:
1107             text = "Not enough funds"
1108         elif interface and interface.is_connected:
1109             self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.wallet.verifier.height))
1110             if not self.wallet.up_to_date:
1111                 self.status_image.set_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_MENU)
1112                 text = "Synchronizing..."
1113             else:
1114                 self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
1115                 self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.wallet.verifier.height))
1116                 c, u = self.wallet.get_balance()
1117                 text =  "Balance: %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
1118                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
1119         else:
1120             self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
1121             self.network_button.set_tooltip_text("Not connected.")
1122             text = "Not connected"
1123
1124         self.status_bar.pop(self.context_id) 
1125         self.status_bar.push(self.context_id, text)
1126
1127         if self.wallet.up_to_date and self.wallet_updated:
1128             self.update_history_tab()
1129             self.update_receiving_tab()
1130             # addressbook too...
1131             self.info.set_text( self.wallet.banner )
1132             self.wallet_updated = False
1133
1134     def update_receiving_tab(self):
1135         self.recv_list.clear()
1136         for address in self.wallet.all_addresses():
1137             if self.wallet.is_change(address):continue
1138             label = self.wallet.labels.get(address)
1139             h = self.wallet.history.get(address,[])
1140             n = len(h)
1141             tx = "None" if n==0 else "%d"%n
1142             self.recv_list.append((address, label, tx ))
1143
1144     def update_sending_tab(self):
1145         # detect addresses that are not mine in history, add them here...
1146         self.addressbook_list.clear()
1147         for alias, v in self.wallet.aliases.items():
1148             s, target = v
1149             label = self.wallet.labels.get(alias)
1150             self.addressbook_list.append((alias, label, '-'))
1151             
1152         for address in self.wallet.addressbook:
1153             label = self.wallet.labels.get(address)
1154             n = 0 
1155             for tx in self.wallet.transactions.values():
1156                 if address in map(lambda x:x[0], tx.outputs): n += 1
1157
1158             self.addressbook_list.append((address, label, "%d"%n))
1159
1160     def update_history_tab(self):
1161         cursor = self.history_treeview.get_cursor()[0]
1162         self.history_list.clear()
1163
1164         for item in self.wallet.get_tx_history():
1165             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
1166             if conf:
1167                 try:
1168                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
1169                 except:
1170                     time_str = "------"
1171                 conf_icon = gtk.STOCK_APPLY
1172             else:
1173                 time_str = 'pending'
1174                 conf_icon = gtk.STOCK_EXECUTE
1175
1176             label, is_default_label = self.wallet.get_label(tx_hash)
1177             tooltip = tx_hash + "\n%d confirmations"%conf if tx_hash else ''
1178             details = self.wallet.get_tx_details(tx_hash)
1179
1180             self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
1181                                         format_satoshis(value,True,self.wallet.num_zeros),
1182                                         format_satoshis(balance,False,self.wallet.num_zeros), tooltip, details] )
1183         if cursor: self.history_treeview.set_cursor( cursor )
1184
1185
1186
1187     def newaddress_dialog(self, w):
1188
1189         title = "New Contact" 
1190         dialog = gtk.Dialog(title, parent=self.window, 
1191                             flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, 
1192                             buttons= ("cancel", 0, "ok",1)  )
1193         dialog.show()
1194
1195         label = gtk.HBox()
1196         label_label = gtk.Label('Label:')
1197         label_label.set_size_request(120,10)
1198         label_label.show()
1199         label.pack_start(label_label)
1200         label_entry = gtk.Entry()
1201         label_entry.show()
1202         label.pack_start(label_entry)
1203         label.show()
1204         dialog.vbox.pack_start(label, False, True, 5)
1205
1206         address = gtk.HBox()
1207         address_label = gtk.Label('Address:')
1208         address_label.set_size_request(120,10)
1209         address_label.show()
1210         address.pack_start(address_label)
1211         address_entry = gtk.Entry()
1212         address_entry.show()
1213         address.pack_start(address_entry)
1214         address.show()
1215         dialog.vbox.pack_start(address, False, True, 5)
1216         
1217         result = dialog.run()
1218         address = address_entry.get_text()
1219         label = label_entry.get_text()
1220         dialog.destroy()
1221
1222         if result == 1:
1223             if self.wallet.is_valid(address):
1224                 self.wallet.addressbook.append(address)
1225                 if label:  self.wallet.labels[address] = label
1226                 self.wallet.save()
1227                 self.update_sending_tab()
1228             else:
1229                 errorDialog = gtk.MessageDialog(
1230                     parent=self.window,
1231                     flags=gtk.DIALOG_MODAL, 
1232                     buttons= gtk.BUTTONS_CLOSE, 
1233                     message_format = "Invalid address")
1234                 errorDialog.show()
1235                 errorDialog.run()
1236                 errorDialog.destroy()
1237
1238     
1239
1240 class ElectrumGui():
1241
1242     def __init__(self, wallet, config):
1243         self.wallet = wallet
1244         self.config = config
1245
1246     def main(self, url=None):
1247         ew = ElectrumWindow(self.wallet, self.config)
1248         if url: ew.set_url(url)
1249         gtk.main()
1250
1251     def restore_or_create(self):
1252         return restore_create_dialog(self.wallet)
1253
1254     def seed_dialog(self):
1255         return run_recovery_dialog( self.wallet )
1256
1257     def network_dialog(self):
1258         return run_network_dialog( self.wallet, parent=None )
1259
1260     def show_seed(self):
1261         show_seed_dialog(self.wallet, None, None)
1262
1263     def password_dialog(self):
1264         change_password_dialog(self.wallet, None, None)
1265
1266     def restore_wallet(self):
1267         wallet = self.wallet
1268
1269         dialog = gtk.MessageDialog(
1270             parent = None,
1271             flags = gtk.DIALOG_MODAL, 
1272             buttons = gtk.BUTTONS_CANCEL, 
1273             message_format = "Please wait..."  )
1274         dialog.show()
1275
1276         def recover_thread( wallet, dialog ):
1277             while not wallet.is_up_to_date(): 
1278                 time.sleep(0.1)
1279             gobject.idle_add( dialog.destroy )
1280
1281         thread.start_new_thread( recover_thread, ( wallet, dialog ) )
1282         r = dialog.run()
1283         dialog.destroy()
1284         if r==gtk.RESPONSE_CANCEL: return False
1285         if not wallet.is_found():
1286             show_message("No transactions found for this seed")
1287
1288         wallet.save()
1289         return True