add address type to receive tab in GTK GUI
[electrum-nvc.git] / gui / gtk.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 # use this because this file is named gtk.py
20 from __future__ import absolute_import
21
22 import datetime
23 import thread, time, ast, sys, re
24 import socket, traceback
25 import pygtk
26 pygtk.require('2.0')
27 import gtk, gobject
28 from decimal import Decimal
29 from electrum.util import print_error
30 from electrum.bitcoin import is_valid
31 from electrum import mnemonic, pyqrnative, WalletStorage, Wallet
32
33 gtk.gdk.threads_init()
34 APP_NAME = "Electrum"
35 import platform
36 MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace'
37
38 from electrum.util import format_satoshis
39 from electrum.network import DEFAULT_SERVERS
40 from electrum.bitcoin import MIN_RELAY_TX_FEE
41
42 def numbify(entry, is_int = False):
43     text = entry.get_text().strip()
44     chars = '0123456789'
45     if not is_int: chars +='.'
46     s = ''.join([i for i in text if i in chars])
47     if not is_int:
48         if '.' in s:
49             p = s.find('.')
50             s = s.replace('.','')
51             s = s[:p] + '.' + s[p:p+8]
52         try:
53             amount = int( Decimal(s) * 100000000 )
54         except:
55             amount = None
56     else:
57         try:
58             amount = int( s )
59         except:
60             amount = None
61     entry.set_text(s)
62     return amount
63
64
65
66
67 def show_seed_dialog(wallet, password, parent):
68     if not wallet.seed:
69         show_message("No seed")
70         return
71     try:
72         seed = wallet.get_seed(password)
73     except:
74         show_message("Incorrect password")
75         return
76     dialog = gtk.MessageDialog(
77         parent = parent,
78         flags = gtk.DIALOG_MODAL, 
79         buttons = gtk.BUTTONS_OK, 
80         message_format = "Your wallet generation seed is:\n\n" + seed \
81             + "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \
82             + "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" + ' '.join(mnemonic.mn_encode(seed)) + "\"" )
83     dialog.set_title("Seed")
84     dialog.show()
85     dialog.run()
86     dialog.destroy()
87
88 def restore_create_dialog():
89
90     # ask if the user wants to create a new wallet, or recover from a seed. 
91     # if he wants to recover, and nothing is found, do not create wallet
92     dialog = gtk.Dialog("electrum", parent=None, 
93                         flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, 
94                         buttons= ("create", 0, "restore",1, "cancel",2)  )
95
96     label = gtk.Label("Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?"  )
97     label.show()
98     dialog.vbox.pack_start(label)
99     dialog.show()
100     r = dialog.run()
101     dialog.destroy()
102
103     if r==2: return False
104     return 'restore' if r==1 else 'create'
105
106
107
108 def run_recovery_dialog():
109     message = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet."
110     dialog = gtk.MessageDialog(
111         parent = None,
112         flags = gtk.DIALOG_MODAL, 
113         buttons = gtk.BUTTONS_OK_CANCEL,
114         message_format = message)
115
116     vbox = dialog.vbox
117     dialog.set_default_response(gtk.RESPONSE_OK)
118
119     # ask seed, server and gap in the same dialog
120     seed_box = gtk.HBox()
121     seed_label = gtk.Label('Seed or mnemonic:')
122     seed_label.set_size_request(150,-1)
123     seed_box.pack_start(seed_label, False, False, 10)
124     seed_label.show()
125     seed_entry = gtk.Entry()
126     seed_entry.show()
127     seed_entry.set_size_request(450,-1)
128     seed_box.pack_start(seed_entry, False, False, 10)
129     add_help_button(seed_box, '.')
130     seed_box.show()
131     vbox.pack_start(seed_box, False, False, 5)    
132
133     dialog.show()
134     r = dialog.run()
135     seed = seed_entry.get_text()
136     dialog.destroy()
137
138     if r==gtk.RESPONSE_CANCEL:
139         return False
140
141     try:
142         seed.decode('hex')
143     except:
144         print_error("Warning: Not hex, trying decode")
145         seed = mnemonic.mn_decode( seed.split(' ') )
146     if not seed:
147         show_message("no seed")
148         return False
149         
150     return seed
151
152
153
154 def run_settings_dialog(self):
155
156     message = "Here are the settings of your wallet. For more explanations, click on the question mark buttons next to each input field."
157         
158     dialog = gtk.MessageDialog(
159         parent = self.window,
160         flags = gtk.DIALOG_MODAL, 
161         buttons = gtk.BUTTONS_OK_CANCEL,
162         message_format = message)
163
164     image = gtk.Image()
165     image.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG)
166     image.show()
167     dialog.set_image(image)
168     dialog.set_title("Settings")
169
170     vbox = dialog.vbox
171     dialog.set_default_response(gtk.RESPONSE_OK)
172
173     fee = gtk.HBox()
174     fee_entry = gtk.Entry()
175     fee_label = gtk.Label('Transaction fee:')
176     fee_label.set_size_request(150,10)
177     fee_label.show()
178     fee.pack_start(fee_label,False, False, 10)
179     fee_entry.set_text( str( Decimal(self.wallet.fee) /100000000 ) )
180     fee_entry.connect('changed', numbify, False)
181     fee_entry.show()
182     fee.pack_start(fee_entry,False,False, 10)
183     add_help_button(fee, 'Fee per kilobyte of transaction. Recommended value:0.0001')
184     fee.show()
185     vbox.pack_start(fee, False,False, 5)
186             
187     nz = gtk.HBox()
188     nz_entry = gtk.Entry()
189     nz_label = gtk.Label('Display zeros:')
190     nz_label.set_size_request(150,10)
191     nz_label.show()
192     nz.pack_start(nz_label,False, False, 10)
193     nz_entry.set_text( str( self.num_zeros ))
194     nz_entry.connect('changed', numbify, True)
195     nz_entry.show()
196     nz.pack_start(nz_entry,False,False, 10)
197     add_help_button(nz, "Number of zeros displayed after the decimal point.\nFor example, if this number is 2, then '5.' is displayed as '5.00'")
198     nz.show()
199     vbox.pack_start(nz, False,False, 5)
200             
201     dialog.show()
202     r = dialog.run()
203     fee = fee_entry.get_text()
204     nz = nz_entry.get_text()
205         
206     dialog.destroy()
207     if r==gtk.RESPONSE_CANCEL:
208         return
209
210     try:
211         fee = int( 100000000 * Decimal(fee) )
212     except:
213         show_message("error")
214         return
215     self.wallet.set_fee(fee)
216
217     try:
218         nz = int( nz )
219         if nz>8: nz = 8
220     except:
221         show_message("error")
222         return
223
224     if self.num_zeros != nz:
225         self.num_zeros = nz
226         self.config.set_key('num_zeros',nz,True)
227         self.update_history_tab()
228
229
230
231
232 def run_network_dialog( network, parent ):
233     image = gtk.Image()
234     image.set_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_DIALOG)
235     if parent:
236         if network.is_connected():
237             interface = network.interface
238             status = "Connected to %s:%d\n%d blocks"%(interface.host, interface.port, network.blockchain.height())
239         else:
240             status = "Not connected"
241     else:
242         import random
243         status = "Please choose a server.\nSelect cancel if you are offline."
244
245     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         wallet.get_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(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.set_label(tx,new_text)
902             self.update_history_tab()
903         cell.connect('edited', edited_cb, self.history_list)
904         def editing_started(cell, entry, path, h_list):
905             tx = h_list.get_value( h_list.get_iter(path), 0)
906             if not self.wallet.labels.get(tx): entry.set_text('')
907         cell.connect('editing-started', editing_started, self.history_list)
908         tvcolumn.set_expand(True)
909         tvcolumn.pack_start(cell, True)
910         tvcolumn.set_attributes(cell, text=3, foreground_set = 4)
911
912         tvcolumn = gtk.TreeViewColumn('Amount')
913         treeview.append_column(tvcolumn)
914         cell = gtk.CellRendererText()
915         cell.set_alignment(1, 0.5)
916         cell.set_property('family', MONOSPACE_FONT)
917         tvcolumn.pack_start(cell, False)
918         tvcolumn.add_attribute(cell, 'text', 5)
919
920         tvcolumn = gtk.TreeViewColumn('Balance')
921         treeview.append_column(tvcolumn)
922         cell = gtk.CellRendererText()
923         cell.set_alignment(1, 0.5)
924         cell.set_property('family', MONOSPACE_FONT)
925         tvcolumn.pack_start(cell, False)
926         tvcolumn.add_attribute(cell, 'text', 6)
927
928         tvcolumn = gtk.TreeViewColumn('Tooltip')
929         treeview.append_column(tvcolumn)
930         cell = gtk.CellRendererText()
931         tvcolumn.pack_start(cell, False)
932         tvcolumn.add_attribute(cell, 'text', 7)
933         tvcolumn.set_visible(False)
934
935         scroll = gtk.ScrolledWindow()
936         scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
937         scroll.add(treeview)
938
939         self.add_tab(scroll, 'History')
940         self.update_history_tab()
941
942
943     def create_recv_tab(self):
944         self.recv_list = gtk.ListStore(str, str, str, str)
945         self.add_tab( self.make_address_list(True), 'Receive')
946         self.update_receiving_tab()
947
948     def create_book_tab(self):
949         self.addressbook_list = gtk.ListStore(str, str, str)
950         self.add_tab( self.make_address_list(False), 'Contacts')
951         self.update_sending_tab()
952
953     def make_address_list(self, is_recv):
954         liststore = self.recv_list if is_recv else self.addressbook_list
955         treeview = gtk.TreeView(model= liststore)
956         treeview.connect('key-press-event', self.treeview_key_press)
957         treeview.connect('button-press-event', self.treeview_button_press)
958         treeview.show()
959         if not is_recv:
960             self.contacts_treeview = treeview
961
962         tvcolumn = gtk.TreeViewColumn('Address')
963         treeview.append_column(tvcolumn)
964         cell = gtk.CellRendererText()
965         cell.set_property('family', MONOSPACE_FONT)
966         tvcolumn.pack_start(cell, True)
967         tvcolumn.add_attribute(cell, 'text', 0)
968
969         tvcolumn = gtk.TreeViewColumn('Label')
970         tvcolumn.set_expand(True)
971         treeview.append_column(tvcolumn)
972         cell = gtk.CellRendererText()
973         cell.set_property('editable', True)
974         def edited_cb2(cell, path, new_text, liststore):
975             address = liststore.get_value( liststore.get_iter(path), 0)
976             self.wallet.set_label(address, new_text)
977             self.update_receiving_tab()
978             self.update_sending_tab()
979             self.update_history_tab()
980         cell.connect('edited', edited_cb2, liststore)
981         tvcolumn.pack_start(cell, True)
982         tvcolumn.add_attribute(cell, 'text', 1)
983
984         tvcolumn = gtk.TreeViewColumn('Tx')
985         treeview.append_column(tvcolumn)
986         cell = gtk.CellRendererText()
987         tvcolumn.pack_start(cell, True)
988         tvcolumn.add_attribute(cell, 'text', 2)
989
990         if is_recv:
991             tvcolumn = gtk.TreeViewColumn('Type')
992             treeview.append_column(tvcolumn)
993             cell = gtk.CellRendererText()
994             tvcolumn.pack_start(cell, True)
995             tvcolumn.add_attribute(cell, 'text', 3)
996
997         scroll = gtk.ScrolledWindow()
998         scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
999         scroll.add(treeview)
1000
1001         hbox = gtk.HBox()
1002         if not is_recv:
1003             button = gtk.Button("New")
1004             button.connect("clicked", self.newaddress_dialog)
1005             button.show()
1006             hbox.pack_start(button,False)
1007
1008         def showqrcode(w, treeview, liststore):
1009             path, col = treeview.get_cursor()
1010             if not path: return
1011             address = liststore.get_value(liststore.get_iter(path), 0)
1012             qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H)
1013             qr.addData(address)
1014             qr.make()
1015             boxsize = 7
1016             size = qr.getModuleCount()*boxsize
1017             def area_expose_cb(area, event):
1018                 style = area.get_style()
1019                 k = qr.getModuleCount()
1020                 for r in range(k):
1021                     for c in range(k):
1022                         gc = style.black_gc if qr.isDark(r, c) else style.white_gc
1023                         area.window.draw_rectangle(gc, True, c*boxsize, r*boxsize, boxsize, boxsize)
1024             area = gtk.DrawingArea()
1025             area.set_size_request(size, size)
1026             area.connect("expose-event", area_expose_cb)
1027             area.show()
1028             dialog = gtk.Dialog(address, parent=self.window, flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, buttons = ("ok",1))
1029             dialog.vbox.add(area)
1030             dialog.run()
1031             dialog.destroy()
1032
1033         button = gtk.Button("QR")
1034         button.connect("clicked", showqrcode, treeview, liststore)
1035         button.show()
1036         hbox.pack_start(button,False)
1037
1038         button = gtk.Button("Copy to clipboard")
1039         def copy2clipboard(w, treeview, liststore):
1040             import platform
1041             path, col =  treeview.get_cursor()
1042             if path:
1043                 address =  liststore.get_value( liststore.get_iter(path), 0)
1044                 if platform.system() == 'Windows':
1045                     from Tkinter import Tk
1046                     r = Tk()
1047                     r.withdraw()
1048                     r.clipboard_clear()
1049                     r.clipboard_append( address )
1050                     r.destroy()
1051                 else:
1052                     c = gtk.clipboard_get()
1053                     c.set_text( address )
1054         button.connect("clicked", copy2clipboard, treeview, liststore)
1055         button.show()
1056         hbox.pack_start(button,False)
1057
1058         if not is_recv:
1059             button = gtk.Button("Pay to")
1060             def payto(w, treeview, liststore):
1061                 path, col =  treeview.get_cursor()
1062                 if path:
1063                     address =  liststore.get_value( liststore.get_iter(path), 0)
1064                     self.payto_entry.set_text( address )
1065                     self.notebook.set_current_page(1)
1066                     self.amount_entry.grab_focus()
1067
1068             button.connect("clicked", payto, treeview, liststore)
1069             button.show()
1070             hbox.pack_start(button,False)
1071
1072         vbox = gtk.VBox()
1073         vbox.pack_start(scroll,True)
1074         vbox.pack_start(hbox, False)
1075         return vbox
1076
1077     def update_status_bar(self):
1078         interface = self.network.interface
1079         if self.funds_error:
1080             text = "Not enough funds"
1081         elif interface and interface.is_connected:
1082             self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.network.blockchain.height()))
1083             if not self.wallet.up_to_date:
1084                 self.status_image.set_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_MENU)
1085                 text = "Synchronizing..."
1086             else:
1087                 self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
1088                 self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.network.blockchain.height()))
1089                 c, u = self.wallet.get_balance()
1090                 text =  "Balance: %s "%( format_satoshis(c,False,self.num_zeros) )
1091                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.num_zeros).strip() )
1092         else:
1093             self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
1094             self.network_button.set_tooltip_text("Not connected.")
1095             text = "Not connected"
1096
1097         self.status_bar.pop(self.context_id) 
1098         self.status_bar.push(self.context_id, text)
1099
1100         if self.wallet.up_to_date and self.wallet_updated:
1101             self.update_history_tab()
1102             self.update_receiving_tab()
1103             # addressbook too...
1104             self.info.set_text( self.network.banner )
1105             self.wallet_updated = False
1106
1107     def update_receiving_tab(self):
1108         self.recv_list.clear()
1109         for address in self.wallet.addresses(True):
1110             Type = "R"
1111             if self.wallet.is_change(address): Type = "C"
1112             if address in self.wallet.imported_keys.keys(): Type = "I"
1113             label = self.wallet.labels.get(address)
1114             h = self.wallet.history.get(address,[])
1115             n = len(h)
1116             tx = "0" if n==0 else "%d"%n
1117             self.recv_list.append((address, label, tx, Type ))
1118
1119     def update_sending_tab(self):
1120         # detect addresses that are not mine in history, add them here...
1121         self.addressbook_list.clear()
1122         #for alias, v in self.wallet.aliases.items():
1123         #    s, target = v
1124         #    label = self.wallet.labels.get(alias)
1125         #    self.addressbook_list.append((alias, label, '-'))
1126             
1127         for address in self.wallet.addressbook:
1128             label = self.wallet.labels.get(address)
1129             n = self.wallet.get_num_tx(address)
1130             self.addressbook_list.append((address, label, "%d"%n))
1131
1132     def update_history_tab(self):
1133         cursor = self.history_treeview.get_cursor()[0]
1134         self.history_list.clear()
1135
1136         for item in self.wallet.get_tx_history():
1137             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
1138             if conf > 0:
1139                 try:
1140                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
1141                 except:
1142                     time_str = "------"
1143                 conf_icon = gtk.STOCK_APPLY
1144             elif conf == -1:
1145                 time_str = 'unverified'
1146                 conf_icon = None
1147             else:
1148                 time_str = 'pending'
1149                 conf_icon = gtk.STOCK_EXECUTE
1150
1151             label, is_default_label = self.wallet.get_label(tx_hash)
1152             tooltip = tx_hash + "\n%d confirmations"%conf if tx_hash else ''
1153             details = self.get_tx_details(tx_hash)
1154
1155             self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
1156                                         format_satoshis(value,True,self.num_zeros, whitespaces=True),
1157                                         format_satoshis(balance,False,self.num_zeros, whitespaces=True), tooltip, details] )
1158         if cursor: self.history_treeview.set_cursor( cursor )
1159
1160
1161     def get_tx_details(self, tx_hash):
1162         import datetime
1163         if not tx_hash: return ''
1164         tx = self.wallet.transactions.get(tx_hash)
1165         is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
1166         conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
1167
1168         if timestamp:
1169             time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
1170         else:
1171             time_str = 'pending'
1172
1173         inputs = map(lambda x: x.get('address'), tx.inputs)
1174         outputs = map(lambda x: x.get('address'), tx.d['outputs'])
1175         tx_details = "Transaction Details" +"\n\n" \
1176             + "Transaction ID:\n" + tx_hash + "\n\n" \
1177             + "Status: %d confirmations\n"%conf
1178         if is_mine:
1179             if fee: 
1180                 tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
1181                               + "Transaction fee: %s\n"% format_satoshis(fee, False)
1182             else:
1183                 tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
1184                               + "Transaction fee: unknown\n"
1185         else:
1186             tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
1187
1188         tx_details += "Date: %s\n\n"%time_str \
1189             + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
1190             + "Outputs:\n-"+ '\n-'.join(outputs)
1191
1192         return tx_details
1193
1194
1195
1196     def newaddress_dialog(self, w):
1197
1198         title = "New Contact" 
1199         dialog = gtk.Dialog(title, parent=self.window, 
1200                             flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, 
1201                             buttons= ("cancel", 0, "ok",1)  )
1202         dialog.show()
1203
1204         label = gtk.HBox()
1205         label_label = gtk.Label('Label:')
1206         label_label.set_size_request(120,10)
1207         label_label.show()
1208         label.pack_start(label_label)
1209         label_entry = gtk.Entry()
1210         label_entry.show()
1211         label.pack_start(label_entry)
1212         label.show()
1213         dialog.vbox.pack_start(label, False, True, 5)
1214
1215         address = gtk.HBox()
1216         address_label = gtk.Label('Address:')
1217         address_label.set_size_request(120,10)
1218         address_label.show()
1219         address.pack_start(address_label)
1220         address_entry = gtk.Entry()
1221         address_entry.show()
1222         address.pack_start(address_entry)
1223         address.show()
1224         dialog.vbox.pack_start(address, False, True, 5)
1225         
1226         result = dialog.run()
1227         address = address_entry.get_text()
1228         label = label_entry.get_text()
1229         dialog.destroy()
1230
1231         if result == 1:
1232             if is_valid(address):
1233                 self.wallet.add_contact(address,label)
1234                 self.update_sending_tab()
1235             else:
1236                 errorDialog = gtk.MessageDialog(
1237                     parent=self.window,
1238                     flags=gtk.DIALOG_MODAL, 
1239                     buttons= gtk.BUTTONS_CLOSE, 
1240                     message_format = "Invalid address")
1241                 errorDialog.show()
1242                 errorDialog.run()
1243                 errorDialog.destroy()
1244
1245     
1246
1247 class ElectrumGui():
1248
1249     def __init__(self, config, network):
1250         self.network = network
1251         self.config = config
1252
1253
1254     def main(self, url=None):
1255
1256         storage = WalletStorage(self.config)
1257         if not storage.file_exists:
1258             action = self.restore_or_create()
1259             if not action:
1260                 exit()
1261             self.wallet = wallet = Wallet(storage)
1262             gap = self.config.get('gap_limit', 5)
1263             if gap != 5:
1264                 wallet.gap_limit = gap
1265                 wallet.storage.put('gap_limit', gap, True)
1266
1267             self.wallet.start_threads(self.network)
1268
1269             if action == 'create':
1270                 wallet.init_seed(None)
1271                 wallet.save_seed()
1272                 wallet.create_accounts()
1273                 wallet.synchronize()  # generate first addresses offline
1274             elif action == 'restore':
1275                 seed = self.seed_dialog()
1276                 wallet.init_seed(seed)
1277                 wallet.save_seed()
1278                 self.restore_wallet(wallet)
1279                 
1280             else:
1281                 exit()
1282         else:
1283             self.wallet = Wallet(storage)
1284             self.wallet.start_threads(self.network)
1285
1286         w = ElectrumWindow(self.wallet, self.config, self.network)
1287         if url: w.set_url(url)
1288         gtk.main()
1289
1290     def restore_or_create(self):
1291         return restore_create_dialog()
1292
1293     def seed_dialog(self):
1294         return run_recovery_dialog()
1295
1296     def verify_seed(self):
1297         self.wallet.save_seed()
1298         return True
1299
1300     def network_dialog(self):
1301         return run_network_dialog( self.network, parent=None )
1302
1303     def show_seed(self):
1304         show_seed_dialog(self.wallet, None, None)
1305
1306     def password_dialog(self):
1307         change_password_dialog(self.wallet, None, None)
1308
1309     def restore_wallet(self, wallet):
1310
1311         dialog = gtk.MessageDialog(
1312             parent = None,
1313             flags = gtk.DIALOG_MODAL, 
1314             buttons = gtk.BUTTONS_CANCEL, 
1315             message_format = "Please wait..."  )
1316         dialog.show()
1317
1318         def recover_thread( wallet, dialog ):
1319             wallet.restore(lambda x:x)
1320             gobject.idle_add( dialog.destroy )
1321
1322         thread.start_new_thread( recover_thread, ( wallet, dialog ) )
1323         r = dialog.run()
1324         dialog.destroy()
1325         if r==gtk.RESPONSE_CANCEL: return False
1326         if not wallet.is_found():
1327             show_message("No transactions found for this seed")
1328
1329         return True