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