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