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