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