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