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