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