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