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