wallet.get_label method
[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_names = ['lite', 'classic', 'gtk', 'text']
277     for name in gui_names: gui_combo.append_text(name.capitalize())
278     gui_combo.show()
279     gui_box.pack_start(gui_combo,False, False, 10)
280     gui_combo.set_active( gui_names.index( wallet.config.get("gui","lite")) )
281     gui_box.show()
282     add_help_button(gui_box, "Select which GUI mode to use at start up.")
283
284     vbox.pack_start(gui_box, False,False, 5)
285
286     dialog.show()
287     r = dialog.run()
288     fee = fee_entry.get_text()
289     nz = nz_entry.get_text()
290     gui = gui_names[ gui_combo.get_active()]
291         
292     dialog.destroy()
293     if r==gtk.RESPONSE_CANCEL:
294         return
295
296     try:
297         fee = int( 100000000 * Decimal(fee) )
298     except:
299         show_message("error")
300         return
301     if wallet.fee != fee:
302         wallet.fee = fee
303         wallet.save()
304
305     try:
306         nz = int( nz )
307         if nz>8: nz = 8
308     except:
309         show_message("error")
310         return
311     if wallet.num_zeros != nz:
312         wallet.num_zeros = nz
313         wallet.save()
314
315     wallet.config.set_key('gui',gui,True)
316
317
318
319
320 def run_network_dialog( wallet, parent ):
321     image = gtk.Image()
322     image.set_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_DIALOG)
323     interface = wallet.interface
324     if parent:
325         if interface.is_connected:
326             status = "Connected to %s:%d\n%d blocks"%(interface.host, interface.port, wallet.verifier.height)
327         else:
328             status = "Not connected"
329     else:
330         import random
331         status = "Please choose a server."
332
333     server = interface.server
334
335     if not wallet.interface.servers:
336         servers_list = []
337         for x in DEFAULT_SERVERS:
338             h,port,protocol = x.split(':')
339             servers_list.append( (h,[(protocol,port)] ) )
340     else:
341         servers_list = wallet.interface.servers
342
343     plist = {}
344     for item in servers_list:
345         host, pp = item
346         z = {}
347         for item2 in pp:
348             protocol, port = item2
349             z[protocol] = port
350         plist[host] = z
351
352     dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
353                                     gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, status)
354     dialog.set_title("Server")
355     dialog.set_image(image)
356     image.show()
357     
358     vbox = dialog.vbox
359     host_box = gtk.HBox()
360     host_label = gtk.Label('Connect to:')
361     host_label.set_size_request(100,-1)
362     host_label.show()
363     host_box.pack_start(host_label, False, False, 10)
364     host_entry = gtk.Entry()
365     host_entry.set_size_request(200,-1)
366     host_entry.set_text(server)
367     host_entry.show()
368     host_box.pack_start(host_entry, False, False, 10)
369     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)')
370     host_box.show()
371
372
373     p_box = gtk.HBox(False, 10)
374     p_box.show()
375
376     p_label = gtk.Label('Protocol:')
377     p_label.set_size_request(100,-1)
378     p_label.show()
379     p_box.pack_start(p_label, False, False, 10)
380
381     radio1 = gtk.RadioButton(None, "tcp")
382     p_box.pack_start(radio1, True, True, 0)
383     radio1.show()
384     radio2 = gtk.RadioButton(radio1, "http")
385     p_box.pack_start(radio2, True, True, 0)
386     radio2.show()
387
388     def current_line():
389         return unicode(host_entry.get_text()).split(':')
390     
391     def set_button(protocol):
392         if protocol == 't':
393             radio1.set_active(1)
394         elif protocol == 'h':
395             radio2.set_active(1)
396
397     def set_protocol(protocol):
398         host = current_line()[0]
399         pp = plist[host]
400         if protocol not in pp.keys():
401             protocol = pp.keys()[0]
402             set_button(protocol)
403         port = pp[protocol]
404         host_entry.set_text( host + ':' + port + ':' + protocol)
405
406     radio1.connect("toggled", lambda x,y:set_protocol('t'), "radio button 1")
407     radio2.connect("toggled", lambda x,y:set_protocol('h'), "radio button 1")
408         
409     server_list = gtk.ListStore(str)
410     for host in plist.keys():
411         server_list.append([host])
412     
413     treeview = gtk.TreeView(model=server_list)
414     treeview.show()
415
416     if wallet.interface.servers:
417         label = 'Active Servers'
418     else:
419         label = 'Default Servers'
420         
421     tvcolumn = gtk.TreeViewColumn(label)
422     treeview.append_column(tvcolumn)
423     cell = gtk.CellRendererText()
424     tvcolumn.pack_start(cell, False)
425     tvcolumn.add_attribute(cell, 'text', 0)
426
427     scroll = gtk.ScrolledWindow()
428     scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
429     scroll.add(treeview)
430     scroll.show()
431
432     vbox.pack_start(host_box, False,False, 5)
433     vbox.pack_start(p_box, True, True, 0)
434     vbox.pack_start(scroll)
435
436     def my_treeview_cb(treeview):
437         path, view_column = treeview.get_cursor()
438         host = server_list.get_value( server_list.get_iter(path), 0)
439
440         pp = plist[host]
441         if 't' in pp.keys():
442             protocol = 't'
443         else:
444             protocol = pp.keys()[0]
445         port = pp[protocol]
446         host_entry.set_text( host + ':' + port + ':' + protocol)
447         set_button(protocol)
448
449     treeview.connect('cursor-changed', my_treeview_cb)
450
451     dialog.show()
452     r = dialog.run()
453     server = host_entry.get_text()
454     dialog.destroy()
455
456     if r==gtk.RESPONSE_CANCEL:
457         return False
458
459     try:
460         interface.set_server(server)
461     except:
462         show_message("error:" + server)
463         return False
464
465     if parent:
466         wallet.config.set_key("server", server, True)
467     return True
468
469
470
471 def show_message(message, parent=None):
472     dialog = gtk.MessageDialog(
473         parent = parent,
474         flags = gtk.DIALOG_MODAL, 
475         buttons = gtk.BUTTONS_CLOSE, 
476         message_format = message )
477     dialog.show()
478     dialog.run()
479     dialog.destroy()
480
481 def password_line(label):
482     password = gtk.HBox()
483     password_label = gtk.Label(label)
484     password_label.set_size_request(120,10)
485     password_label.show()
486     password.pack_start(password_label,False, False, 10)
487     password_entry = gtk.Entry()
488     password_entry.set_size_request(300,-1)
489     password_entry.set_visibility(False)
490     password_entry.show()
491     password.pack_start(password_entry,False,False, 10)
492     password.show()
493     return password, password_entry
494
495 def password_dialog(parent):
496     dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
497                                 gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL,  "Please enter your password.")
498     dialog.get_image().set_visible(False)
499     current_pw, current_pw_entry = password_line('Password:')
500     current_pw_entry.connect("activate", lambda entry, dialog, response: dialog.response(response), dialog, gtk.RESPONSE_OK)
501     dialog.vbox.pack_start(current_pw, False, True, 0)
502     dialog.show()
503     result = dialog.run()
504     pw = current_pw_entry.get_text()
505     dialog.destroy()
506     if result != gtk.RESPONSE_CANCEL: return pw
507
508 def change_password_dialog(wallet, parent, icon):
509     if not wallet.seed:
510         show_message("No seed")
511         return
512
513     if parent:
514         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'
515     else:
516         msg = "Please choose a password to encrypt your wallet keys"
517
518     dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
519     dialog.set_title("Change password")
520     image = gtk.Image()
521     image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_DIALOG)
522     image.show()
523     dialog.set_image(image)
524
525     if wallet.use_encryption:
526         current_pw, current_pw_entry = password_line('Current password:')
527         dialog.vbox.pack_start(current_pw, False, True, 0)
528
529     password, password_entry = password_line('New password:')
530     dialog.vbox.pack_start(password, False, True, 5)
531     password2, password2_entry = password_line('Confirm password:')
532     dialog.vbox.pack_start(password2, False, True, 5)
533
534     dialog.show()
535     result = dialog.run()
536     password = current_pw_entry.get_text() if wallet.use_encryption else None
537     new_password = password_entry.get_text()
538     new_password2 = password2_entry.get_text()
539     dialog.destroy()
540     if result == gtk.RESPONSE_CANCEL: 
541         return
542
543     try:
544         seed = wallet.pw_decode( wallet.seed, password)
545     except:
546         show_message("Incorrect password")
547         return
548
549     if new_password != new_password2:
550         show_message("passwords do not match")
551         return
552
553     wallet.update_password(seed, password, new_password)
554
555     if icon:
556         if wallet.use_encryption:
557             icon.set_tooltip_text('wallet is encrypted')
558         else:
559             icon.set_tooltip_text('wallet is unencrypted')
560
561
562 def add_help_button(hbox, message):
563     button = gtk.Button('?')
564     button.connect("clicked", lambda x: show_message(message))
565     button.show()
566     hbox.pack_start(button,False, False)
567
568
569 class MyWindow(gtk.Window): __gsignals__ = dict( mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, None, (str,)) )
570
571 gobject.type_register(MyWindow)
572 gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.W, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+W')
573 gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.Q, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+Q')
574
575
576 class ElectrumWindow:
577
578     def show_message(self, msg):
579         show_message(msg, self.window)
580
581     def __init__(self, wallet, config):
582         self.config = config
583         self.wallet = wallet
584         self.funds_error = False # True if not enough funds
585
586         self.window = MyWindow(gtk.WINDOW_TOPLEVEL)
587         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.config.path
588         if not self.wallet.seed: title += ' [seedless]'
589         self.window.set_title(title)
590         self.window.connect("destroy", gtk.main_quit)
591         self.window.set_border_width(0)
592         self.window.connect('mykeypress', gtk.main_quit)
593         self.window.set_default_size(720, 350)
594
595         vbox = gtk.VBox()
596
597         self.notebook = gtk.Notebook()
598         self.create_history_tab()
599         if self.wallet.seed:
600             self.create_send_tab()
601         self.create_recv_tab()
602         self.create_book_tab()
603         self.create_about_tab()
604         self.notebook.show()
605         vbox.pack_start(self.notebook, True, True, 2)
606         
607         self.status_bar = gtk.Statusbar()
608         vbox.pack_start(self.status_bar, False, False, 0)
609
610         self.status_image = gtk.Image()
611         self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
612         self.status_image.set_alignment(True, 0.5  )
613         self.status_image.show()
614
615         self.network_button = gtk.Button()
616         self.network_button.connect("clicked", lambda x: run_network_dialog(self.wallet, self.window) )
617         self.network_button.add(self.status_image)
618         self.network_button.set_relief(gtk.RELIEF_NONE)
619         self.network_button.show()
620         self.status_bar.pack_end(self.network_button, False, False)
621
622         if self.wallet.seed:
623             def seedb(w, wallet):
624                 if wallet.use_encryption:
625                     password = password_dialog(self.window)
626                     if not password: return
627                 else: password = None
628                 show_seed_dialog(wallet, password, self.window)
629             button = gtk.Button('S')
630             button.connect("clicked", seedb, wallet )
631             button.set_relief(gtk.RELIEF_NONE)
632             button.show()
633             self.status_bar.pack_end(button,False, False)
634
635         settings_icon = gtk.Image()
636         settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
637         settings_icon.set_alignment(0.5, 0.5)
638         settings_icon.set_size_request(16,16 )
639         settings_icon.show()
640
641         prefs_button = gtk.Button()
642         prefs_button.connect("clicked", lambda x: run_settings_dialog(self.wallet, self.window) )
643         prefs_button.add(settings_icon)
644         prefs_button.set_tooltip_text("Settings")
645         prefs_button.set_relief(gtk.RELIEF_NONE)
646         prefs_button.show()
647         self.status_bar.pack_end(prefs_button,False,False)
648
649         pw_icon = gtk.Image()
650         pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU)
651         pw_icon.set_alignment(0.5, 0.5)
652         pw_icon.set_size_request(16,16 )
653         pw_icon.show()
654
655         if self.wallet.seed:
656             password_button = gtk.Button()
657             password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon))
658             password_button.add(pw_icon)
659             password_button.set_relief(gtk.RELIEF_NONE)
660             password_button.show()
661             self.status_bar.pack_end(password_button,False,False)
662
663         self.window.add(vbox)
664         self.window.show_all()
665         #self.fee_box.hide()
666
667         self.context_id = self.status_bar.get_context_id("statusbar")
668         self.update_status_bar()
669
670         self.wallet_updated = False
671         self.wallet.interface.register_callback('updated', self.update_callback)
672
673
674         def update_status_bar_thread():
675             while True:
676                 gobject.idle_add( self.update_status_bar )
677                 time.sleep(0.5)
678
679
680         def check_recipient_thread():
681             old_r = ''
682             while True:
683                 time.sleep(0.5)
684                 if self.payto_entry.is_focus():
685                     continue
686                 r = self.payto_entry.get_text()
687                 if r != old_r:
688                     old_r = r
689                     r = r.strip()
690                     if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
691                         try:
692                             to_address = self.wallet.get_alias(r, interactive=False)
693                         except:
694                             continue
695                         if to_address:
696                             s = r + ' <' + to_address + '>'
697                             gobject.idle_add( lambda: self.payto_entry.set_text(s) )
698                 
699
700         thread.start_new_thread(update_status_bar_thread, ())
701         if self.wallet.seed:
702             thread.start_new_thread(check_recipient_thread, ())
703         self.notebook.set_current_page(0)
704
705     def update_callback(self):
706         self.wallet_updated = True
707
708
709     def add_tab(self, page, name):
710         tab_label = gtk.Label(name)
711         tab_label.show()
712         self.notebook.append_page(page, tab_label)
713
714
715     def create_send_tab(self):
716         
717         page = vbox = gtk.VBox()
718         page.show()
719
720         payto = gtk.HBox()
721         payto_label = gtk.Label('Pay to:')
722         payto_label.set_size_request(100,-1)
723         payto.pack_start(payto_label, False)
724         payto_entry = gtk.Entry()
725         payto_entry.set_size_request(450, 26)
726         payto.pack_start(payto_entry, False)
727         vbox.pack_start(payto, False, False, 5)
728
729         message = gtk.HBox()
730         message_label = gtk.Label('Description:')
731         message_label.set_size_request(100,-1)
732         message.pack_start(message_label, False)
733         message_entry = gtk.Entry()
734         message_entry.set_size_request(450, 26)
735         message.pack_start(message_entry, False)
736         vbox.pack_start(message, False, False, 5)
737
738         amount_box = gtk.HBox()
739         amount_label = gtk.Label('Amount:')
740         amount_label.set_size_request(100,-1)
741         amount_box.pack_start(amount_label, False)
742         amount_entry = gtk.Entry()
743         amount_entry.set_size_request(120, -1)
744         amount_box.pack_start(amount_entry, False)
745         vbox.pack_start(amount_box, False, False, 5)
746
747         self.fee_box = fee_box = gtk.HBox()
748         fee_label = gtk.Label('Fee:')
749         fee_label.set_size_request(100,-1)
750         fee_box.pack_start(fee_label, False)
751         fee_entry = gtk.Entry()
752         fee_entry.set_size_request(60, 26)
753         fee_box.pack_start(fee_entry, False)
754         vbox.pack_start(fee_box, False, False, 5)
755
756         end_box = gtk.HBox()
757         empty_label = gtk.Label('')
758         empty_label.set_size_request(100,-1)
759         end_box.pack_start(empty_label, False)
760         send_button = gtk.Button("Send")
761         send_button.show()
762         end_box.pack_start(send_button, False, False, 0)
763         clear_button = gtk.Button("Clear")
764         clear_button.show()
765         end_box.pack_start(clear_button, False, False, 15)
766         send_button.connect("clicked", self.do_send, (payto_entry, message_entry, amount_entry, fee_entry))
767         clear_button.connect("clicked", self.do_clear, (payto_entry, message_entry, amount_entry, fee_entry))
768
769         vbox.pack_start(end_box, False, False, 5)
770
771         # display this line only if there is a signature
772         payto_sig = gtk.HBox()
773         payto_sig_id = gtk.Label('')
774         payto_sig.pack_start(payto_sig_id, False)
775         vbox.pack_start(payto_sig, True, True, 5)
776         
777
778         self.user_fee = False
779
780         def entry_changed( entry, is_fee ):
781             self.funds_error = False
782             amount = numbify(amount_entry)
783             fee = numbify(fee_entry)
784             if not is_fee: fee = None
785             if amount is None:
786                 return
787             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
788             if not is_fee:
789                 fee_entry.set_text( str( Decimal( fee ) / 100000000 ) )
790                 self.fee_box.show()
791             if inputs:
792                 amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
793                 fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
794                 send_button.set_sensitive(True)
795             else:
796                 send_button.set_sensitive(False)
797                 amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000"))
798                 fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000"))
799                 self.funds_error = True
800
801         amount_entry.connect('changed', entry_changed, False)
802         fee_entry.connect('changed', entry_changed, True)        
803
804         self.payto_entry = payto_entry
805         self.payto_fee_entry = fee_entry
806         self.payto_sig_id = payto_sig_id
807         self.payto_sig = payto_sig
808         self.amount_entry = amount_entry
809         self.message_entry = message_entry
810         self.add_tab(page, 'Send')
811
812     def set_frozen(self,entry,frozen):
813         if frozen:
814             entry.set_editable(False)
815             entry.set_has_frame(False)
816             entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee"))
817         else:
818             entry.set_editable(True)
819             entry.set_has_frame(True)
820             entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#ffffff"))
821
822     def set_url(self, url):
823         payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
824         self.notebook.set_current_page(1)
825         self.payto_entry.set_text(payto)
826         self.message_entry.set_text(message)
827         self.amount_entry.set_text(amount)
828         if identity:
829             self.set_frozen(self.payto_entry,True)
830             self.set_frozen(self.amount_entry,True)
831             self.set_frozen(self.message_entry,True)
832             self.payto_sig_id.set_text( '      The bitcoin URI was signed by ' + identity )
833         else:
834             self.payto_sig.set_visible(False)
835
836     def create_about_tab(self):
837         import pango
838         page = gtk.VBox()
839         page.show()
840         tv = gtk.TextView()
841         tv.set_editable(False)
842         tv.set_cursor_visible(False)
843         tv.modify_font(pango.FontDescription(MONOSPACE_FONT))
844         page.pack_start(tv)
845         self.info = tv.get_buffer()
846         self.add_tab(page, 'Wall')
847
848     def do_clear(self, w, data):
849         self.payto_sig.set_visible(False)
850         self.payto_fee_entry.set_text('')
851         for entry in [self.payto_entry,self.amount_entry,self.message_entry]:
852             self.set_frozen(entry,False)
853             entry.set_text('')
854
855     def question(self,msg):
856         dialog = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
857         dialog.show()
858         result = dialog.run()
859         dialog.destroy()
860         return result == gtk.RESPONSE_OK
861
862     def do_send(self, w, data):
863         payto_entry, label_entry, amount_entry, fee_entry = data
864         label = label_entry.get_text()
865         r = payto_entry.get_text()
866         r = r.strip()
867
868         m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r)
869         m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
870         
871         if m1:
872             to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
873             if not to_address:
874                 return
875             else:
876                 self.update_sending_tab()
877
878         elif m2:
879             to_address = m2.group(5)
880         else:
881             to_address = r
882
883         if not self.wallet.is_valid(to_address):
884             self.show_message( "invalid bitcoin address:\n"+to_address)
885             return
886
887         try:
888             amount = int( Decimal(amount_entry.get_text()) * 100000000 )
889         except:
890             self.show_message( "invalid amount")
891             return
892         try:
893             fee = int( Decimal(fee_entry.get_text()) * 100000000 )
894         except:
895             self.show_message( "invalid fee")
896             return
897
898         if self.wallet.use_encryption:
899             password = password_dialog(self.window)
900             if not password:
901                 return
902         else:
903             password = None
904
905         try:
906             tx = self.wallet.mktx( to_address, amount, label, password, fee )
907         except BaseException, e:
908             self.show_message(str(e))
909             return
910             
911         status, msg = self.wallet.sendtx( tx )
912         if status:
913             self.show_message( "payment sent.\n" + msg )
914             payto_entry.set_text("")
915             label_entry.set_text("")
916             amount_entry.set_text("")
917             fee_entry.set_text("")
918             #self.fee_box.hide()
919             self.update_sending_tab()
920         else:
921             self.show_message( msg )
922
923
924     def treeview_button_press(self, treeview, event):
925         if event.type == gtk.gdk._2BUTTON_PRESS:
926             c = treeview.get_cursor()[0]
927             if treeview == self.history_treeview:
928                 tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
929                 self.show_message(tx_details)
930             elif treeview == self.contacts_treeview:
931                 m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
932                 a = self.wallet.aliases.get(m)
933                 if a:
934                     if a[0] in self.wallet.authorities.keys():
935                         s = self.wallet.authorities.get(a[0])
936                     else:
937                         s = "self-signed"
938                     msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
939                     self.show_message(msg)
940             
941
942     def treeview_key_press(self, treeview, event):
943         c = treeview.get_cursor()[0]
944         if event.keyval == gtk.keysyms.Up:
945             if c and c[0] == 0:
946                 treeview.parent.grab_focus()
947                 treeview.set_cursor((0,))
948         elif event.keyval == gtk.keysyms.Return:
949             if treeview == self.history_treeview:
950                 tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
951                 self.show_message(tx_details)
952             elif treeview == self.contacts_treeview:
953                 m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
954                 a = self.wallet.aliases.get(m)
955                 if a:
956                     if a[0] in self.wallet.authorities.keys():
957                         s = self.wallet.authorities.get(a[0])
958                     else:
959                         s = "self"
960                     msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
961                     self.show_message(msg)
962
963         return False
964
965     def create_history_tab(self):
966
967         self.history_list = gtk.ListStore(str, str, str, str, 'gboolean',  str, str, str, str)
968         treeview = gtk.TreeView(model=self.history_list)
969         self.history_treeview = treeview
970         treeview.set_tooltip_column(7)
971         treeview.show()
972         treeview.connect('key-press-event', self.treeview_key_press)
973         treeview.connect('button-press-event', self.treeview_button_press)
974
975         tvcolumn = gtk.TreeViewColumn('')
976         treeview.append_column(tvcolumn)
977         cell = gtk.CellRendererPixbuf()
978         tvcolumn.pack_start(cell, False)
979         tvcolumn.set_attributes(cell, stock_id=1)
980
981         tvcolumn = gtk.TreeViewColumn('Date')
982         treeview.append_column(tvcolumn)
983         cell = gtk.CellRendererText()
984         tvcolumn.pack_start(cell, False)
985         tvcolumn.add_attribute(cell, 'text', 2)
986
987         tvcolumn = gtk.TreeViewColumn('Description')
988         treeview.append_column(tvcolumn)
989         cell = gtk.CellRendererText()
990         cell.set_property('foreground', 'grey')
991         cell.set_property('family', MONOSPACE_FONT)
992         cell.set_property('editable', True)
993         def edited_cb(cell, path, new_text, h_list):
994             tx = h_list.get_value( h_list.get_iter(path), 0)
995             self.wallet.labels[tx] = new_text
996             self.wallet.save() 
997             self.update_history_tab()
998         cell.connect('edited', edited_cb, self.history_list)
999         def editing_started(cell, entry, path, h_list):
1000             tx = h_list.get_value( h_list.get_iter(path), 0)
1001             if not self.wallet.labels.get(tx): entry.set_text('')
1002         cell.connect('editing-started', editing_started, self.history_list)
1003         tvcolumn.set_expand(True)
1004         tvcolumn.pack_start(cell, True)
1005         tvcolumn.set_attributes(cell, text=3, foreground_set = 4)
1006
1007         tvcolumn = gtk.TreeViewColumn('Amount')
1008         treeview.append_column(tvcolumn)
1009         cell = gtk.CellRendererText()
1010         cell.set_alignment(1, 0.5)
1011         cell.set_property('family', MONOSPACE_FONT)
1012         tvcolumn.pack_start(cell, False)
1013         tvcolumn.add_attribute(cell, 'text', 5)
1014
1015         tvcolumn = gtk.TreeViewColumn('Balance')
1016         treeview.append_column(tvcolumn)
1017         cell = gtk.CellRendererText()
1018         cell.set_alignment(1, 0.5)
1019         cell.set_property('family', MONOSPACE_FONT)
1020         tvcolumn.pack_start(cell, False)
1021         tvcolumn.add_attribute(cell, 'text', 6)
1022
1023         tvcolumn = gtk.TreeViewColumn('Tooltip')
1024         treeview.append_column(tvcolumn)
1025         cell = gtk.CellRendererText()
1026         tvcolumn.pack_start(cell, False)
1027         tvcolumn.add_attribute(cell, 'text', 7)
1028         tvcolumn.set_visible(False)
1029
1030         scroll = gtk.ScrolledWindow()
1031         scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
1032         scroll.add(treeview)
1033
1034         self.add_tab(scroll, 'History')
1035         self.update_history_tab()
1036
1037
1038     def create_recv_tab(self):
1039         self.recv_list = gtk.ListStore(str, str, str)
1040         self.add_tab( self.make_address_list(True), 'Receive')
1041         self.update_receiving_tab()
1042
1043     def create_book_tab(self):
1044         self.addressbook_list = gtk.ListStore(str, str, str)
1045         self.add_tab( self.make_address_list(False), 'Contacts')
1046         self.update_sending_tab()
1047
1048     def make_address_list(self, is_recv):
1049         liststore = self.recv_list if is_recv else self.addressbook_list
1050         treeview = gtk.TreeView(model= liststore)
1051         treeview.connect('key-press-event', self.treeview_key_press)
1052         treeview.connect('button-press-event', self.treeview_button_press)
1053         treeview.show()
1054         if not is_recv:
1055             self.contacts_treeview = treeview
1056
1057         tvcolumn = gtk.TreeViewColumn('Address')
1058         treeview.append_column(tvcolumn)
1059         cell = gtk.CellRendererText()
1060         cell.set_property('family', MONOSPACE_FONT)
1061         tvcolumn.pack_start(cell, True)
1062         tvcolumn.add_attribute(cell, 'text', 0)
1063
1064         tvcolumn = gtk.TreeViewColumn('Label')
1065         tvcolumn.set_expand(True)
1066         treeview.append_column(tvcolumn)
1067         cell = gtk.CellRendererText()
1068         cell.set_property('editable', True)
1069         def edited_cb2(cell, path, new_text, liststore):
1070             address = liststore.get_value( liststore.get_iter(path), 0)
1071             self.wallet.labels[address] = new_text
1072             self.wallet.save() 
1073             self.wallet.update_tx_labels()
1074             self.update_receiving_tab()
1075             self.update_sending_tab()
1076             self.update_history_tab()
1077         cell.connect('edited', edited_cb2, liststore)
1078         tvcolumn.pack_start(cell, True)
1079         tvcolumn.add_attribute(cell, 'text', 1)
1080
1081         tvcolumn = gtk.TreeViewColumn('Tx')
1082         treeview.append_column(tvcolumn)
1083         cell = gtk.CellRendererText()
1084         tvcolumn.pack_start(cell, True)
1085         tvcolumn.add_attribute(cell, 'text', 2)
1086
1087         scroll = gtk.ScrolledWindow()
1088         scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
1089         scroll.add(treeview)
1090
1091         hbox = gtk.HBox()
1092         if not is_recv:
1093             button = gtk.Button("New")
1094             button.connect("clicked", self.newaddress_dialog)
1095             button.show()
1096             hbox.pack_start(button,False)
1097
1098         def showqrcode(w, treeview, liststore):
1099             path, col = treeview.get_cursor()
1100             if not path: return
1101             address = liststore.get_value(liststore.get_iter(path), 0)
1102             qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H)
1103             qr.addData(address)
1104             qr.make()
1105             boxsize = 7
1106             size = qr.getModuleCount()*boxsize
1107             def area_expose_cb(area, event):
1108                 style = area.get_style()
1109                 k = qr.getModuleCount()
1110                 for r in range(k):
1111                     for c in range(k):
1112                         gc = style.black_gc if qr.isDark(r, c) else style.white_gc
1113                         area.window.draw_rectangle(gc, True, c*boxsize, r*boxsize, boxsize, boxsize)
1114             area = gtk.DrawingArea()
1115             area.set_size_request(size, size)
1116             area.connect("expose-event", area_expose_cb)
1117             area.show()
1118             dialog = gtk.Dialog(address, parent=self.window, flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, buttons = ("ok",1))
1119             dialog.vbox.add(area)
1120             dialog.run()
1121             dialog.destroy()
1122
1123         button = gtk.Button("QR")
1124         button.connect("clicked", showqrcode, treeview, liststore)
1125         button.show()
1126         hbox.pack_start(button,False)
1127
1128         button = gtk.Button("Copy to clipboard")
1129         def copy2clipboard(w, treeview, liststore):
1130             import platform
1131             path, col =  treeview.get_cursor()
1132             if path:
1133                 address =  liststore.get_value( liststore.get_iter(path), 0)
1134                 if platform.system() == 'Windows':
1135                     from Tkinter import Tk
1136                     r = Tk()
1137                     r.withdraw()
1138                     r.clipboard_clear()
1139                     r.clipboard_append( address )
1140                     r.destroy()
1141                 else:
1142                     c = gtk.clipboard_get()
1143                     c.set_text( address )
1144         button.connect("clicked", copy2clipboard, treeview, liststore)
1145         button.show()
1146         hbox.pack_start(button,False)
1147
1148         if not is_recv:
1149             button = gtk.Button("Pay to")
1150             def payto(w, treeview, liststore):
1151                 path, col =  treeview.get_cursor()
1152                 if path:
1153                     address =  liststore.get_value( liststore.get_iter(path), 0)
1154                     self.payto_entry.set_text( address )
1155                     self.notebook.set_current_page(1)
1156                     self.amount_entry.grab_focus()
1157
1158             button.connect("clicked", payto, treeview, liststore)
1159             button.show()
1160             hbox.pack_start(button,False)
1161
1162         vbox = gtk.VBox()
1163         vbox.pack_start(scroll,True)
1164         vbox.pack_start(hbox, False)
1165         return vbox
1166
1167     def update_status_bar(self):
1168         interface = self.wallet.interface
1169         if self.funds_error:
1170             text = "Not enough funds"
1171         elif interface and interface.is_connected:
1172             self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.wallet.verifier.height))
1173             if not self.wallet.up_to_date:
1174                 self.status_image.set_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_MENU)
1175                 text = "Synchronizing..."
1176             else:
1177                 self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
1178                 self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.wallet.verifier.height))
1179                 c, u = self.wallet.get_balance()
1180                 text =  "Balance: %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
1181                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
1182         else:
1183             self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
1184             self.network_button.set_tooltip_text("Not connected.")
1185             text = "Not connected"
1186
1187         self.status_bar.pop(self.context_id) 
1188         self.status_bar.push(self.context_id, text)
1189
1190         if self.wallet.up_to_date and self.wallet_updated:
1191             self.update_history_tab()
1192             self.update_receiving_tab()
1193             # addressbook too...
1194             self.info.set_text( self.wallet.banner )
1195             self.wallet_updated = False
1196
1197     def update_receiving_tab(self):
1198         self.recv_list.clear()
1199         for address in self.wallet.all_addresses():
1200             if self.wallet.is_change(address):continue
1201             label = self.wallet.labels.get(address)
1202             h = self.wallet.history.get(address,[])
1203             n = len(h)
1204             tx = "None" if n==0 else "%d"%n
1205             self.recv_list.append((address, label, tx ))
1206
1207     def update_sending_tab(self):
1208         # detect addresses that are not mine in history, add them here...
1209         self.addressbook_list.clear()
1210         for alias, v in self.wallet.aliases.items():
1211             s, target = v
1212             label = self.wallet.labels.get(alias)
1213             self.addressbook_list.append((alias, label, '-'))
1214             
1215         for address in self.wallet.addressbook:
1216             label = self.wallet.labels.get(address)
1217             n = 0 
1218             for item in self.wallet.transactions.values():
1219                 if address in item['outputs'] : n=n+1
1220             tx = "None" if n==0 else "%d"%n
1221             self.addressbook_list.append((address, label, tx))
1222
1223     def update_history_tab(self):
1224         cursor = self.history_treeview.get_cursor()[0]
1225         self.history_list.clear()
1226         balance = 0 
1227         for tx in self.wallet.get_tx_history():
1228             tx_hash = tx['tx_hash']
1229             conf = self.wallet.verifier.get_confirmations(tx_hash)
1230             if conf:
1231                 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
1232                 conf_icon = gtk.STOCK_APPLY
1233             else:
1234                 time_str = 'pending'
1235                 conf_icon = gtk.STOCK_EXECUTE
1236             v = self.wallet.get_tx_value(tx_hash)
1237             balance += v 
1238             label, is_default_label = self.wallet.get_label(tx_hash)
1239             tooltip = tx_hash + "\n%d confirmations"%conf 
1240
1241             inputs = map(lambda x: x.get('address'), tx['inputs'])
1242             outputs = map(lambda x: x.get('address'), tx['outputs'])
1243             details = "Transaction Details:\n\n" \
1244                       + "Transaction ID:\n" + tx_hash + "\n\n" \
1245                       + "Status: %d confirmations\n\n"%conf  \
1246                       + "Date: %s\n\n"%time_str \
1247                       + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
1248                       + "Outputs:\n-"+ '\n-'.join(outputs)
1249             r = self.wallet.receipts.get(tx_hash)
1250             if r:
1251                 details += "\n_______________________________________" \
1252                            + '\n\nSigned URI: ' + r[2] \
1253                            + "\n\nSigned by: " + r[0] \
1254                            + '\n\nSignature: ' + r[1]
1255                 
1256
1257             self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
1258                                         format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros), tooltip, details] )
1259         if cursor: self.history_treeview.set_cursor( cursor )
1260
1261
1262
1263     def newaddress_dialog(self, w):
1264
1265         title = "New Contact" 
1266         dialog = gtk.Dialog(title, parent=self.window, 
1267                             flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, 
1268                             buttons= ("cancel", 0, "ok",1)  )
1269         dialog.show()
1270
1271         label = gtk.HBox()
1272         label_label = gtk.Label('Label:')
1273         label_label.set_size_request(120,10)
1274         label_label.show()
1275         label.pack_start(label_label)
1276         label_entry = gtk.Entry()
1277         label_entry.show()
1278         label.pack_start(label_entry)
1279         label.show()
1280         dialog.vbox.pack_start(label, False, True, 5)
1281
1282         address = gtk.HBox()
1283         address_label = gtk.Label('Address:')
1284         address_label.set_size_request(120,10)
1285         address_label.show()
1286         address.pack_start(address_label)
1287         address_entry = gtk.Entry()
1288         address_entry.show()
1289         address.pack_start(address_entry)
1290         address.show()
1291         dialog.vbox.pack_start(address, False, True, 5)
1292         
1293         result = dialog.run()
1294         address = address_entry.get_text()
1295         label = label_entry.get_text()
1296         dialog.destroy()
1297
1298         if result == 1:
1299             if self.wallet.is_valid(address):
1300                 self.wallet.addressbook.append(address)
1301                 if label:  self.wallet.labels[address] = label
1302                 self.wallet.save()
1303                 self.update_sending_tab()
1304             else:
1305                 errorDialog = gtk.MessageDialog(
1306                     parent=self.window,
1307                     flags=gtk.DIALOG_MODAL, 
1308                     buttons= gtk.BUTTONS_CLOSE, 
1309                     message_format = "Invalid address")
1310                 errorDialog.show()
1311                 errorDialog.run()
1312                 errorDialog.destroy()
1313
1314     
1315
1316 class ElectrumGui():
1317
1318     def __init__(self, wallet, config):
1319         self.wallet = wallet
1320         self.config = config
1321
1322     def main(self, url=None):
1323         ew = ElectrumWindow(self.wallet, self.config)
1324         if url: ew.set_url(url)
1325         gtk.main()
1326
1327     def restore_or_create(self):
1328         return restore_create_dialog(self.wallet)
1329
1330     def server_list_changed(self):
1331         pass