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