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