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