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