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