Standardizing message format and implementing stderr for errors
[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
27 import pyqrnative, mnemonic
28
29 gtk.gdk.threads_init()
30 APP_NAME = "Electrum"
31 import platform
32 MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace'
33
34 from wallet import format_satoshis
35 from interface import DEFAULT_SERVERS
36
37 def numbify(entry, is_int = False):
38     text = entry.get_text().strip()
39     chars = '0123456789'
40     if not is_int: chars +='.'
41     s = ''.join([i for i in text if i in chars])
42     if not is_int:
43         if '.' in s:
44             p = s.find('.')
45             s = s.replace('.','')
46             s = s[:p] + '.' + s[p:p+8]
47         try:
48             amount = int( Decimal(s) * 100000000 )
49         except:
50             amount = None
51     else:
52         try:
53             amount = int( s )
54         except:
55             amount = None
56     entry.set_text(s)
57     return amount
58
59
60
61
62 def show_seed_dialog(wallet, password, parent):
63     if not wallet.seed:
64         show_message("No seed")
65         return
66     try:
67         seed = wallet.pw_decode( wallet.seed, password)
68     except:
69         show_message("Incorrect password")
70         return
71     dialog = gtk.MessageDialog(
72         parent = parent,
73         flags = gtk.DIALOG_MODAL, 
74         buttons = gtk.BUTTONS_OK, 
75         message_format = "Your wallet generation seed is:\n\n" + seed \
76             + "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \
77             + "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" + ' '.join(mnemonic.mn_encode(seed)) + "\"" )
78     dialog.set_title("Seed")
79     dialog.show()
80     dialog.run()
81     dialog.destroy()
82
83 def restore_create_dialog(wallet):
84
85     # ask if the user wants to create a new wallet, or recover from a seed. 
86     # if he wants to recover, and nothing is found, do not create wallet
87     dialog = gtk.Dialog("electrum", parent=None, 
88                         flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, 
89                         buttons= ("create", 0, "restore",1, "cancel",2)  )
90
91     label = gtk.Label("Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?"  )
92     label.show()
93     dialog.vbox.pack_start(label)
94     dialog.show()
95     r = dialog.run()
96     dialog.destroy()
97
98     if r==2: return False
99         
100     is_recovery = (r==1)
101
102     # ask for the server.
103     if not run_network_dialog( wallet, parent=None ): return False
104
105     if not is_recovery:
106
107         wallet.new_seed(None)
108         # generate first key
109         wallet.init_mpk( wallet.seed )
110         wallet.up_to_date_event.clear()
111         wallet.update()
112
113         # run a dialog indicating the seed, ask the user to remember it
114         show_seed_dialog(wallet, None, None)
115             
116         #ask for password
117         change_password_dialog(wallet, None, None)
118     else:
119         # ask for seed and gap.
120         run_recovery_dialog( wallet )
121
122         dialog = gtk.MessageDialog(
123             parent = None,
124             flags = gtk.DIALOG_MODAL, 
125             buttons = gtk.BUTTONS_CANCEL, 
126             message_format = "Please wait..."  )
127         dialog.show()
128
129         def recover_thread( wallet, dialog ):
130             wallet.init_mpk( wallet.seed ) # not encrypted at this point
131             wallet.up_to_date_event.clear()
132             wallet.update()
133
134             if wallet.is_found():
135                 # history and addressbook
136                 wallet.update_tx_history()
137                 wallet.fill_addressbook()
138                 print "Recovery successful"
139
140             gobject.idle_add( dialog.destroy )
141
142         thread.start_new_thread( recover_thread, ( wallet, dialog ) )
143         r = dialog.run()
144         dialog.destroy()
145         if r==gtk.RESPONSE_CANCEL: return False
146         if not wallet.is_found:
147             show_message("No transactions found for this seed")
148
149     wallet.save()
150     return True
151
152
153 def run_recovery_dialog(wallet):
154     message = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet."
155     dialog = gtk.MessageDialog(
156         parent = None,
157         flags = gtk.DIALOG_MODAL, 
158         buttons = gtk.BUTTONS_OK_CANCEL,
159         message_format = message)
160
161     vbox = dialog.vbox
162     dialog.set_default_response(gtk.RESPONSE_OK)
163
164     # ask seed, server and gap in the same dialog
165     seed_box = gtk.HBox()
166     seed_label = gtk.Label('Seed or mnemonic:')
167     seed_label.set_size_request(150,-1)
168     seed_box.pack_start(seed_label, False, False, 10)
169     seed_label.show()
170     seed_entry = gtk.Entry()
171     seed_entry.show()
172     seed_entry.set_size_request(450,-1)
173     seed_box.pack_start(seed_entry, False, False, 10)
174     add_help_button(seed_box, '.')
175     seed_box.show()
176     vbox.pack_start(seed_box, False, False, 5)    
177
178     gap = gtk.HBox()
179     gap_label = gtk.Label('Gap limit:')
180     gap_label.set_size_request(150,10)
181     gap_label.show()
182     gap.pack_start(gap_label,False, False, 10)
183     gap_entry = gtk.Entry()
184     gap_entry.set_text("%d"%wallet.gap_limit)
185     gap_entry.connect('changed', numbify, True)
186     gap_entry.show()
187     gap.pack_start(gap_entry,False,False, 10)
188     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.')
189     gap.show()
190     vbox.pack_start(gap, False,False, 5)
191
192     dialog.show()
193     r = dialog.run()
194     gap = gap_entry.get_text()        
195     seed = seed_entry.get_text()
196     dialog.destroy()
197
198     if r==gtk.RESPONSE_CANCEL:
199         sys.exit(1)
200     try:
201         gap = int(gap)
202     except:
203         show_message("error")
204         sys.exit(1)
205
206     try:
207         seed.decode('hex')
208     except:
209         sys.stderr.write("Warning: Not hex, trying decode\n")
210         sys.stderr.flush()
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):
562         self.wallet = wallet
563         self.funds_error = False # True if not enough funds
564
565         self.window = MyWindow(gtk.WINDOW_TOPLEVEL)
566         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.wallet.path
567         if not self.wallet.seed: title += ' [seedless]'
568         self.window.set_title(title)
569         self.window.connect("destroy", gtk.main_quit)
570         self.window.set_border_width(0)
571         self.window.connect('mykeypress', gtk.main_quit)
572         self.window.set_default_size(720, 350)
573
574         vbox = gtk.VBox()
575
576         self.notebook = gtk.Notebook()
577         self.create_history_tab()
578         if self.wallet.seed:
579             self.create_send_tab()
580         self.create_recv_tab()
581         self.create_book_tab()
582         self.create_about_tab()
583         self.notebook.show()
584         vbox.pack_start(self.notebook, True, True, 2)
585         
586         self.status_bar = gtk.Statusbar()
587         vbox.pack_start(self.status_bar, False, False, 0)
588
589         self.status_image = gtk.Image()
590         self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
591         self.status_image.set_alignment(True, 0.5  )
592         self.status_image.show()
593
594         self.network_button = gtk.Button()
595         self.network_button.connect("clicked", lambda x: run_network_dialog(self.wallet, self.window) )
596         self.network_button.add(self.status_image)
597         self.network_button.set_relief(gtk.RELIEF_NONE)
598         self.network_button.show()
599         self.status_bar.pack_end(self.network_button, False, False)
600
601         if self.wallet.seed:
602             def seedb(w, wallet):
603                 if wallet.use_encryption:
604                     password = password_dialog(self.window)
605                     if not password: return
606                 else: password = None
607                 show_seed_dialog(wallet, password, self.window)
608             button = gtk.Button('S')
609             button.connect("clicked", seedb, wallet )
610             button.set_relief(gtk.RELIEF_NONE)
611             button.show()
612             self.status_bar.pack_end(button,False, False)
613
614         settings_icon = gtk.Image()
615         settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
616         settings_icon.set_alignment(0.5, 0.5)
617         settings_icon.set_size_request(16,16 )
618         settings_icon.show()
619
620         prefs_button = gtk.Button()
621         prefs_button.connect("clicked", lambda x: run_settings_dialog(self.wallet, self.window) )
622         prefs_button.add(settings_icon)
623         prefs_button.set_tooltip_text("Settings")
624         prefs_button.set_relief(gtk.RELIEF_NONE)
625         prefs_button.show()
626         self.status_bar.pack_end(prefs_button,False,False)
627
628         pw_icon = gtk.Image()
629         pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU)
630         pw_icon.set_alignment(0.5, 0.5)
631         pw_icon.set_size_request(16,16 )
632         pw_icon.show()
633
634         if self.wallet.seed:
635             password_button = gtk.Button()
636             password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon))
637             password_button.add(pw_icon)
638             password_button.set_relief(gtk.RELIEF_NONE)
639             password_button.show()
640             self.status_bar.pack_end(password_button,False,False)
641
642         self.window.add(vbox)
643         self.window.show_all()
644         #self.fee_box.hide()
645
646         self.context_id = self.status_bar.get_context_id("statusbar")
647         self.update_status_bar()
648
649         def update_status_bar_thread():
650             while True:
651                 gobject.idle_add( self.update_status_bar )
652                 time.sleep(0.5)
653
654
655         def check_recipient_thread():
656             old_r = ''
657             while True:
658                 time.sleep(0.5)
659                 if self.payto_entry.is_focus():
660                     continue
661                 r = self.payto_entry.get_text()
662                 if r != old_r:
663                     old_r = r
664                     r = r.strip()
665                     if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
666                         try:
667                             to_address = self.wallet.get_alias(r, interactive=False)
668                         except:
669                             continue
670                         if to_address:
671                             s = r + ' <' + to_address + '>'
672                             gobject.idle_add( lambda: self.payto_entry.set_text(s) )
673                 
674
675         thread.start_new_thread(update_status_bar_thread, ())
676         if self.wallet.seed:
677             thread.start_new_thread(check_recipient_thread, ())
678         self.notebook.set_current_page(0)
679
680
681     def add_tab(self, page, name):
682         tab_label = gtk.Label(name)
683         tab_label.show()
684         self.notebook.append_page(page, tab_label)
685
686
687     def create_send_tab(self):
688         
689         page = vbox = gtk.VBox()
690         page.show()
691
692         payto = gtk.HBox()
693         payto_label = gtk.Label('Pay to:')
694         payto_label.set_size_request(100,-1)
695         payto.pack_start(payto_label, False)
696         payto_entry = gtk.Entry()
697         payto_entry.set_size_request(450, 26)
698         payto.pack_start(payto_entry, False)
699         vbox.pack_start(payto, False, False, 5)
700
701         message = gtk.HBox()
702         message_label = gtk.Label('Description:')
703         message_label.set_size_request(100,-1)
704         message.pack_start(message_label, False)
705         message_entry = gtk.Entry()
706         message_entry.set_size_request(450, 26)
707         message.pack_start(message_entry, False)
708         vbox.pack_start(message, False, False, 5)
709
710         amount_box = gtk.HBox()
711         amount_label = gtk.Label('Amount:')
712         amount_label.set_size_request(100,-1)
713         amount_box.pack_start(amount_label, False)
714         amount_entry = gtk.Entry()
715         amount_entry.set_size_request(120, -1)
716         amount_box.pack_start(amount_entry, False)
717         vbox.pack_start(amount_box, False, False, 5)
718
719         self.fee_box = fee_box = gtk.HBox()
720         fee_label = gtk.Label('Fee:')
721         fee_label.set_size_request(100,-1)
722         fee_box.pack_start(fee_label, False)
723         fee_entry = gtk.Entry()
724         fee_entry.set_size_request(60, 26)
725         fee_box.pack_start(fee_entry, False)
726         vbox.pack_start(fee_box, False, False, 5)
727
728         end_box = gtk.HBox()
729         empty_label = gtk.Label('')
730         empty_label.set_size_request(100,-1)
731         end_box.pack_start(empty_label, False)
732         send_button = gtk.Button("Send")
733         send_button.show()
734         end_box.pack_start(send_button, False, False, 0)
735         clear_button = gtk.Button("Clear")
736         clear_button.show()
737         end_box.pack_start(clear_button, False, False, 15)
738         send_button.connect("clicked", self.do_send, (payto_entry, message_entry, amount_entry, fee_entry))
739         clear_button.connect("clicked", self.do_clear, (payto_entry, message_entry, amount_entry, fee_entry))
740
741         vbox.pack_start(end_box, False, False, 5)
742
743         # display this line only if there is a signature
744         payto_sig = gtk.HBox()
745         payto_sig_id = gtk.Label('')
746         payto_sig.pack_start(payto_sig_id, False)
747         vbox.pack_start(payto_sig, True, True, 5)
748         
749
750         self.user_fee = False
751
752         def entry_changed( entry, is_fee ):
753             self.funds_error = False
754             amount = numbify(amount_entry)
755             fee = numbify(fee_entry)
756             if not is_fee: fee = None
757             if amount is None:
758                 return
759             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
760             if not is_fee:
761                 fee_entry.set_text( str( Decimal( fee ) / 100000000 ) )
762                 self.fee_box.show()
763             if inputs:
764                 amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
765                 fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
766                 send_button.set_sensitive(True)
767             else:
768                 send_button.set_sensitive(False)
769                 amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000"))
770                 fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000"))
771                 self.funds_error = True
772
773         amount_entry.connect('changed', entry_changed, False)
774         fee_entry.connect('changed', entry_changed, True)        
775
776         self.payto_entry = payto_entry
777         self.payto_fee_entry = fee_entry
778         self.payto_sig_id = payto_sig_id
779         self.payto_sig = payto_sig
780         self.amount_entry = amount_entry
781         self.message_entry = message_entry
782         self.add_tab(page, 'Send')
783
784     def set_frozen(self,entry,frozen):
785         if frozen:
786             entry.set_editable(False)
787             entry.set_has_frame(False)
788             entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee"))
789         else:
790             entry.set_editable(True)
791             entry.set_has_frame(True)
792             entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#ffffff"))
793
794     def set_url(self, url):
795         payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
796         self.notebook.set_current_page(1)
797         self.payto_entry.set_text(payto)
798         self.message_entry.set_text(message)
799         self.amount_entry.set_text(amount)
800         if identity:
801             self.set_frozen(self.payto_entry,True)
802             self.set_frozen(self.amount_entry,True)
803             self.set_frozen(self.message_entry,True)
804             self.payto_sig_id.set_text( '      The bitcoin URI was signed by ' + identity )
805         else:
806             self.payto_sig.set_visible(False)
807
808     def create_about_tab(self):
809         import pango
810         page = gtk.VBox()
811         page.show()
812         tv = gtk.TextView()
813         tv.set_editable(False)
814         tv.set_cursor_visible(False)
815         tv.modify_font(pango.FontDescription(MONOSPACE_FONT))
816         page.pack_start(tv)
817         self.info = tv.get_buffer()
818         self.add_tab(page, 'Wall')
819
820     def do_clear(self, w, data):
821         self.payto_sig.set_visible(False)
822         self.payto_fee_entry.set_text('')
823         for entry in [self.payto_entry,self.amount_entry,self.message_entry]:
824             self.set_frozen(entry,False)
825             entry.set_text('')
826
827     def question(self,msg):
828         dialog = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
829         dialog.show()
830         result = dialog.run()
831         dialog.destroy()
832         return result == gtk.RESPONSE_OK
833
834     def do_send(self, w, data):
835         payto_entry, label_entry, amount_entry, fee_entry = data
836         label = label_entry.get_text()
837         r = payto_entry.get_text()
838         r = r.strip()
839
840         m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r)
841         m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
842         
843         if m1:
844             to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
845             if not to_address:
846                 return
847             else:
848                 self.update_sending_tab()
849
850         elif m2:
851             to_address = m2.group(5)
852         else:
853             to_address = r
854
855         if not self.wallet.is_valid(to_address):
856             self.show_message( "invalid bitcoin address:\n"+to_address)
857             return
858
859         try:
860             amount = int( Decimal(amount_entry.get_text()) * 100000000 )
861         except:
862             self.show_message( "invalid amount")
863             return
864         try:
865             fee = int( Decimal(fee_entry.get_text()) * 100000000 )
866         except:
867             self.show_message( "invalid fee")
868             return
869
870         if self.wallet.use_encryption:
871             password = password_dialog(self.window)
872             if not password:
873                 return
874         else:
875             password = None
876
877         try:
878             tx = self.wallet.mktx( to_address, amount, label, password, fee )
879         except BaseException, e:
880             self.show_message(str(e))
881             return
882             
883         status, msg = self.wallet.sendtx( tx )
884         if status:
885             self.show_message( "payment sent.\n" + msg )
886             payto_entry.set_text("")
887             label_entry.set_text("")
888             amount_entry.set_text("")
889             fee_entry.set_text("")
890             #self.fee_box.hide()
891             self.update_sending_tab()
892         else:
893             self.show_message( msg )
894
895
896     def treeview_button_press(self, treeview, event):
897         if event.type == gtk.gdk._2BUTTON_PRESS:
898             c = treeview.get_cursor()[0]
899             if treeview == self.history_treeview:
900                 tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
901                 self.show_message(tx_details)
902             elif treeview == self.contacts_treeview:
903                 m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
904                 a = self.wallet.aliases.get(m)
905                 if a:
906                     if a[0] in self.wallet.authorities.keys():
907                         s = self.wallet.authorities.get(a[0])
908                     else:
909                         s = "self-signed"
910                     msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
911                     self.show_message(msg)
912             
913
914     def treeview_key_press(self, treeview, event):
915         c = treeview.get_cursor()[0]
916         if event.keyval == gtk.keysyms.Up:
917             if c and c[0] == 0:
918                 treeview.parent.grab_focus()
919                 treeview.set_cursor((0,))
920         elif event.keyval == gtk.keysyms.Return:
921             if treeview == self.history_treeview:
922                 tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
923                 self.show_message(tx_details)
924             elif treeview == self.contacts_treeview:
925                 m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
926                 a = self.wallet.aliases.get(m)
927                 if a:
928                     if a[0] in self.wallet.authorities.keys():
929                         s = self.wallet.authorities.get(a[0])
930                     else:
931                         s = "self"
932                     msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
933                     self.show_message(msg)
934
935         return False
936
937     def create_history_tab(self):
938
939         self.history_list = gtk.ListStore(str, str, str, str, 'gboolean',  str, str, str, str)
940         treeview = gtk.TreeView(model=self.history_list)
941         self.history_treeview = treeview
942         treeview.set_tooltip_column(7)
943         treeview.show()
944         treeview.connect('key-press-event', self.treeview_key_press)
945         treeview.connect('button-press-event', self.treeview_button_press)
946
947         tvcolumn = gtk.TreeViewColumn('')
948         treeview.append_column(tvcolumn)
949         cell = gtk.CellRendererPixbuf()
950         tvcolumn.pack_start(cell, False)
951         tvcolumn.set_attributes(cell, stock_id=1)
952
953         tvcolumn = gtk.TreeViewColumn('Date')
954         treeview.append_column(tvcolumn)
955         cell = gtk.CellRendererText()
956         tvcolumn.pack_start(cell, False)
957         tvcolumn.add_attribute(cell, 'text', 2)
958
959         tvcolumn = gtk.TreeViewColumn('Description')
960         treeview.append_column(tvcolumn)
961         cell = gtk.CellRendererText()
962         cell.set_property('foreground', 'grey')
963         cell.set_property('family', MONOSPACE_FONT)
964         cell.set_property('editable', True)
965         def edited_cb(cell, path, new_text, h_list):
966             tx = h_list.get_value( h_list.get_iter(path), 0)
967             self.wallet.labels[tx] = new_text
968             self.wallet.save() 
969             self.update_history_tab()
970         cell.connect('edited', edited_cb, self.history_list)
971         def editing_started(cell, entry, path, h_list):
972             tx = h_list.get_value( h_list.get_iter(path), 0)
973             if not self.wallet.labels.get(tx): entry.set_text('')
974         cell.connect('editing-started', editing_started, self.history_list)
975         tvcolumn.set_expand(True)
976         tvcolumn.pack_start(cell, True)
977         tvcolumn.set_attributes(cell, text=3, foreground_set = 4)
978
979         tvcolumn = gtk.TreeViewColumn('Amount')
980         treeview.append_column(tvcolumn)
981         cell = gtk.CellRendererText()
982         cell.set_alignment(1, 0.5)
983         cell.set_property('family', MONOSPACE_FONT)
984         tvcolumn.pack_start(cell, False)
985         tvcolumn.add_attribute(cell, 'text', 5)
986
987         tvcolumn = gtk.TreeViewColumn('Balance')
988         treeview.append_column(tvcolumn)
989         cell = gtk.CellRendererText()
990         cell.set_alignment(1, 0.5)
991         cell.set_property('family', MONOSPACE_FONT)
992         tvcolumn.pack_start(cell, False)
993         tvcolumn.add_attribute(cell, 'text', 6)
994
995         tvcolumn = gtk.TreeViewColumn('Tooltip')
996         treeview.append_column(tvcolumn)
997         cell = gtk.CellRendererText()
998         tvcolumn.pack_start(cell, False)
999         tvcolumn.add_attribute(cell, 'text', 7)
1000         tvcolumn.set_visible(False)
1001
1002         scroll = gtk.ScrolledWindow()
1003         scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
1004         scroll.add(treeview)
1005
1006         self.add_tab(scroll, 'History')
1007         self.update_history_tab()
1008
1009
1010     def create_recv_tab(self):
1011         self.recv_list = gtk.ListStore(str, str, str)
1012         self.add_tab( self.make_address_list(True), 'Receive')
1013         self.update_receiving_tab()
1014
1015     def create_book_tab(self):
1016         self.addressbook_list = gtk.ListStore(str, str, str)
1017         self.add_tab( self.make_address_list(False), 'Contacts')
1018         self.update_sending_tab()
1019
1020     def make_address_list(self, is_recv):
1021         liststore = self.recv_list if is_recv else self.addressbook_list
1022         treeview = gtk.TreeView(model= liststore)
1023         treeview.connect('key-press-event', self.treeview_key_press)
1024         treeview.connect('button-press-event', self.treeview_button_press)
1025         treeview.show()
1026         if not is_recv:
1027             self.contacts_treeview = treeview
1028
1029         tvcolumn = gtk.TreeViewColumn('Address')
1030         treeview.append_column(tvcolumn)
1031         cell = gtk.CellRendererText()
1032         cell.set_property('family', MONOSPACE_FONT)
1033         tvcolumn.pack_start(cell, True)
1034         tvcolumn.add_attribute(cell, 'text', 0)
1035
1036         tvcolumn = gtk.TreeViewColumn('Label')
1037         tvcolumn.set_expand(True)
1038         treeview.append_column(tvcolumn)
1039         cell = gtk.CellRendererText()
1040         cell.set_property('editable', True)
1041         def edited_cb2(cell, path, new_text, liststore):
1042             address = liststore.get_value( liststore.get_iter(path), 0)
1043             self.wallet.labels[address] = new_text
1044             self.wallet.save() 
1045             self.wallet.update_tx_labels()
1046             self.update_receiving_tab()
1047             self.update_sending_tab()
1048             self.update_history_tab()
1049         cell.connect('edited', edited_cb2, liststore)
1050         tvcolumn.pack_start(cell, True)
1051         tvcolumn.add_attribute(cell, 'text', 1)
1052
1053         tvcolumn = gtk.TreeViewColumn('Tx')
1054         treeview.append_column(tvcolumn)
1055         cell = gtk.CellRendererText()
1056         tvcolumn.pack_start(cell, True)
1057         tvcolumn.add_attribute(cell, 'text', 2)
1058
1059         scroll = gtk.ScrolledWindow()
1060         scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
1061         scroll.add(treeview)
1062
1063         hbox = gtk.HBox()
1064         if not is_recv:
1065             button = gtk.Button("New")
1066             button.connect("clicked", self.newaddress_dialog)
1067             button.show()
1068             hbox.pack_start(button,False)
1069
1070         def showqrcode(w, treeview, liststore):
1071             path, col = treeview.get_cursor()
1072             if not path: return
1073             address = liststore.get_value(liststore.get_iter(path), 0)
1074             qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H)
1075             qr.addData(address)
1076             qr.make()
1077             boxsize = 7
1078             size = qr.getModuleCount()*boxsize
1079             def area_expose_cb(area, event):
1080                 style = area.get_style()
1081                 k = qr.getModuleCount()
1082                 for r in range(k):
1083                     for c in range(k):
1084                         gc = style.black_gc if qr.isDark(r, c) else style.white_gc
1085                         area.window.draw_rectangle(gc, True, c*boxsize, r*boxsize, boxsize, boxsize)
1086             area = gtk.DrawingArea()
1087             area.set_size_request(size, size)
1088             area.connect("expose-event", area_expose_cb)
1089             area.show()
1090             dialog = gtk.Dialog(address, parent=self.window, flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, buttons = ("ok",1))
1091             dialog.vbox.add(area)
1092             dialog.run()
1093             dialog.destroy()
1094
1095         button = gtk.Button("QR")
1096         button.connect("clicked", showqrcode, treeview, liststore)
1097         button.show()
1098         hbox.pack_start(button,False)
1099
1100         button = gtk.Button("Copy to clipboard")
1101         def copy2clipboard(w, treeview, liststore):
1102             import platform
1103             path, col =  treeview.get_cursor()
1104             if path:
1105                 address =  liststore.get_value( liststore.get_iter(path), 0)
1106                 if platform.system() == 'Windows':
1107                     from Tkinter import Tk
1108                     r = Tk()
1109                     r.withdraw()
1110                     r.clipboard_clear()
1111                     r.clipboard_append( address )
1112                     r.destroy()
1113                 else:
1114                     c = gtk.clipboard_get()
1115                     c.set_text( address )
1116         button.connect("clicked", copy2clipboard, treeview, liststore)
1117         button.show()
1118         hbox.pack_start(button,False)
1119
1120         if not is_recv:
1121             button = gtk.Button("Pay to")
1122             def payto(w, treeview, liststore):
1123                 path, col =  treeview.get_cursor()
1124                 if path:
1125                     address =  liststore.get_value( liststore.get_iter(path), 0)
1126                     self.payto_entry.set_text( address )
1127                     self.notebook.set_current_page(1)
1128                     self.amount_entry.grab_focus()
1129
1130             button.connect("clicked", payto, treeview, liststore)
1131             button.show()
1132             hbox.pack_start(button,False)
1133
1134         vbox = gtk.VBox()
1135         vbox.pack_start(scroll,True)
1136         vbox.pack_start(hbox, False)
1137         return vbox
1138
1139     def update_status_bar(self):
1140         interface = self.wallet.interface
1141         if self.funds_error:
1142             text = "Not enough funds"
1143         elif interface and interface.is_connected:
1144             self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.wallet.blocks))
1145             if self.wallet.blocks == -1:
1146                 self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
1147                 text = "Connecting..."
1148             elif self.wallet.blocks == 0:
1149                 self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
1150                 text = "Server not ready"
1151             elif not self.wallet.up_to_date:
1152                 self.status_image.set_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_MENU)
1153                 text = "Synchronizing..."
1154             else:
1155                 self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
1156                 self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.wallet.blocks))
1157                 c, u = self.wallet.get_balance()
1158                 text =  "Balance: %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
1159                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
1160         else:
1161             self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
1162             self.network_button.set_tooltip_text("Trying to contact %s.\n%d blocks"%(self.wallet.server, self.wallet.blocks))
1163             text = "Not connected"
1164
1165         self.status_bar.pop(self.context_id) 
1166         self.status_bar.push(self.context_id, text)
1167
1168         if self.wallet.was_updated and self.wallet.up_to_date:
1169             self.update_history_tab()
1170             self.update_receiving_tab()
1171             # addressbook too...
1172             self.info.set_text( self.wallet.banner )
1173             self.wallet.was_updated = False
1174         
1175
1176     def update_receiving_tab(self):
1177         self.recv_list.clear()
1178         for address in self.wallet.all_addresses():
1179             if self.wallet.is_change(address):continue
1180             label = self.wallet.labels.get(address)
1181             n = 0 
1182             h = self.wallet.history.get(address,[])
1183             for item in h:
1184                 if not item['is_input'] : n=n+1
1185             tx = "None" if n==0 else "%d"%n
1186             self.recv_list.append((address, label, tx ))
1187
1188     def update_sending_tab(self):
1189         # detect addresses that are not mine in history, add them here...
1190         self.addressbook_list.clear()
1191         for alias, v in self.wallet.aliases.items():
1192             s, target = v
1193             label = self.wallet.labels.get(alias)
1194             self.addressbook_list.append((alias, label, '-'))
1195             
1196         for address in self.wallet.addressbook:
1197             label = self.wallet.labels.get(address)
1198             n = 0 
1199             for item in self.wallet.tx_history.values():
1200                 if address in item['outputs'] : n=n+1
1201             tx = "None" if n==0 else "%d"%n
1202             self.addressbook_list.append((address, label, tx))
1203
1204     def update_history_tab(self):
1205         cursor = self.history_treeview.get_cursor()[0]
1206         self.history_list.clear()
1207         balance = 0 
1208         for tx in self.wallet.get_tx_history():
1209             tx_hash = tx['tx_hash']
1210             if tx['height']:
1211                 conf = self.wallet.blocks - tx['height'] + 1
1212                 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
1213                 conf_icon = gtk.STOCK_APPLY
1214             else:
1215                 conf = 0
1216                 time_str = 'pending'
1217                 conf_icon = gtk.STOCK_EXECUTE
1218             v = tx['value']
1219             balance += v 
1220             label = self.wallet.labels.get(tx_hash)
1221             is_default_label = (label == '') or (label is None)
1222             if is_default_label: label = tx['default_label']
1223             tooltip = tx_hash + "\n%d confirmations"%conf 
1224
1225             # tx = self.wallet.tx_history.get(tx_hash)
1226             details = "Transaction Details:\n\n" \
1227                       + "Transaction ID:\n" + tx_hash + "\n\n" \
1228                       + "Status: %d confirmations\n\n"%conf  \
1229                       + "Date: %s\n\n"%time_str \
1230                       + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
1231                       + "Outputs:\n-"+ '\n-'.join(tx['outputs'])
1232             r = self.wallet.receipts.get(tx_hash)
1233             if r:
1234                 details += "\n_______________________________________" \
1235                            + '\n\nSigned URI: ' + r[2] \
1236                            + "\n\nSigned by: " + r[0] \
1237                            + '\n\nSignature: ' + r[1]
1238                 
1239
1240             self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
1241                                         format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros), tooltip, details] )
1242         if cursor: self.history_treeview.set_cursor( cursor )
1243
1244
1245
1246     def newaddress_dialog(self, w):
1247
1248         title = "New Contact" 
1249         dialog = gtk.Dialog(title, parent=self.window, 
1250                             flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, 
1251                             buttons= ("cancel", 0, "ok",1)  )
1252         dialog.show()
1253
1254         label = gtk.HBox()
1255         label_label = gtk.Label('Label:')
1256         label_label.set_size_request(120,10)
1257         label_label.show()
1258         label.pack_start(label_label)
1259         label_entry = gtk.Entry()
1260         label_entry.show()
1261         label.pack_start(label_entry)
1262         label.show()
1263         dialog.vbox.pack_start(label, False, True, 5)
1264
1265         address = gtk.HBox()
1266         address_label = gtk.Label('Address:')
1267         address_label.set_size_request(120,10)
1268         address_label.show()
1269         address.pack_start(address_label)
1270         address_entry = gtk.Entry()
1271         address_entry.show()
1272         address.pack_start(address_entry)
1273         address.show()
1274         dialog.vbox.pack_start(address, False, True, 5)
1275         
1276         result = dialog.run()
1277         address = address_entry.get_text()
1278         label = label_entry.get_text()
1279         dialog.destroy()
1280
1281         if result == 1:
1282             if self.wallet.is_valid(address):
1283                 self.wallet.addressbook.append(address)
1284                 if label:  self.wallet.labels[address] = label
1285                 self.wallet.save()
1286                 self.update_sending_tab()
1287             else:
1288                 errorDialog = gtk.MessageDialog(
1289                     parent=self.window,
1290                     flags=gtk.DIALOG_MODAL, 
1291                     buttons= gtk.BUTTONS_CLOSE, 
1292                     message_format = "Invalid address")
1293                 errorDialog.show()
1294                 errorDialog.run()
1295                 errorDialog.destroy()
1296
1297     
1298
1299 class ElectrumGui():
1300
1301     def __init__(self, wallet):
1302         self.wallet = wallet
1303
1304     def main(self, url=None):
1305         ew = ElectrumWindow(self.wallet)
1306         if url: ew.set_url(url)
1307         gtk.main()
1308
1309     def restore_or_create(self):
1310         return restore_create_dialog(self.wallet)