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