Freeze button in GTK receive tab.
[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 BaseException, 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 is_recv:
1064             button = gtk.Button("Freeze")
1065             def freeze_address(w, treeview, liststore, wallet):
1066                 path, col = treeview.get_cursor()
1067                 if path:
1068                     address = liststore.get_value( liststore.get_iter(path), 0)
1069                     if address in wallet.frozen_addresses:
1070                         wallet.unfreeze(address)
1071                     else:
1072                         wallet.freeze(address)
1073                     self.update_receiving_tab()
1074             button.connect("clicked", freeze_address, treeview, liststore, self.wallet)
1075             button.show()
1076             hbox.pack_start(button,False)
1077
1078         if not is_recv:
1079             button = gtk.Button("Pay to")
1080             def payto(w, treeview, liststore):
1081                 path, col =  treeview.get_cursor()
1082                 if path:
1083                     address =  liststore.get_value( liststore.get_iter(path), 0)
1084                     self.payto_entry.set_text( address )
1085                     self.notebook.set_current_page(1)
1086                     self.amount_entry.grab_focus()
1087
1088             button.connect("clicked", payto, treeview, liststore)
1089             button.show()
1090             hbox.pack_start(button,False)
1091
1092         vbox = gtk.VBox()
1093         vbox.pack_start(scroll,True)
1094         vbox.pack_start(hbox, False)
1095         return vbox
1096
1097     def update_status_bar(self):
1098         interface = self.network.interface
1099         if self.funds_error:
1100             text = "Not enough funds"
1101         elif interface and interface.is_connected:
1102             self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.network.blockchain.height()))
1103             if not self.wallet.up_to_date:
1104                 self.status_image.set_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_MENU)
1105                 text = "Synchronizing..."
1106             else:
1107                 self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
1108                 self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.network.blockchain.height()))
1109                 c, u = self.wallet.get_balance()
1110                 text =  "Balance: %s "%( format_satoshis(c,False,self.num_zeros) )
1111                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.num_zeros).strip() )
1112         else:
1113             self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
1114             self.network_button.set_tooltip_text("Not connected.")
1115             text = "Not connected"
1116
1117         self.status_bar.pop(self.context_id) 
1118         self.status_bar.push(self.context_id, text)
1119
1120         if self.wallet.up_to_date and self.wallet_updated:
1121             self.update_history_tab()
1122             self.update_receiving_tab()
1123             # addressbook too...
1124             self.info.set_text( self.network.banner )
1125             self.wallet_updated = False
1126
1127     def update_receiving_tab(self):
1128         self.recv_list.clear()
1129         for address in self.wallet.addresses(True):
1130             Type = "R"
1131             if self.wallet.is_change(address): Type = "C"
1132             if address in self.wallet.imported_keys.keys(): Type = "I"
1133             if address in self.wallet.frozen_addresses: Type = Type + "F"
1134             if address in self.wallet.prioritized_addresses: Type = Type + "P"
1135             label = self.wallet.labels.get(address)
1136             h = self.wallet.history.get(address,[])
1137             n = len(h)
1138             tx = "0" if n==0 else "%d"%n
1139             self.recv_list.append((address, label, tx, Type ))
1140
1141     def update_sending_tab(self):
1142         # detect addresses that are not mine in history, add them here...
1143         self.addressbook_list.clear()
1144         #for alias, v in self.wallet.aliases.items():
1145         #    s, target = v
1146         #    label = self.wallet.labels.get(alias)
1147         #    self.addressbook_list.append((alias, label, '-'))
1148             
1149         for address in self.wallet.addressbook:
1150             label = self.wallet.labels.get(address)
1151             n = self.wallet.get_num_tx(address)
1152             self.addressbook_list.append((address, label, "%d"%n))
1153
1154     def update_history_tab(self):
1155         cursor = self.history_treeview.get_cursor()[0]
1156         self.history_list.clear()
1157
1158         for item in self.wallet.get_tx_history():
1159             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
1160             if conf > 0:
1161                 try:
1162                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
1163                 except:
1164                     time_str = "------"
1165                 conf_icon = gtk.STOCK_APPLY
1166             elif conf == -1:
1167                 time_str = 'unverified'
1168                 conf_icon = None
1169             else:
1170                 time_str = 'pending'
1171                 conf_icon = gtk.STOCK_EXECUTE
1172
1173             label, is_default_label = self.wallet.get_label(tx_hash)
1174             tooltip = tx_hash + "\n%d confirmations"%conf if tx_hash else ''
1175             details = self.get_tx_details(tx_hash)
1176
1177             self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
1178                                         format_satoshis(value,True,self.num_zeros, whitespaces=True),
1179                                         format_satoshis(balance,False,self.num_zeros, whitespaces=True), tooltip, details] )
1180         if cursor: self.history_treeview.set_cursor( cursor )
1181
1182
1183     def get_tx_details(self, tx_hash):
1184         import datetime
1185         if not tx_hash: return ''
1186         tx = self.wallet.transactions.get(tx_hash)
1187         is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
1188         conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
1189
1190         if timestamp:
1191             time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
1192         else:
1193             time_str = 'pending'
1194
1195         inputs = map(lambda x: x.get('address'), tx.inputs)
1196         outputs = map(lambda x: x.get('address'), tx.d['outputs'])
1197         tx_details = "Transaction Details" +"\n\n" \
1198             + "Transaction ID:\n" + tx_hash + "\n\n" \
1199             + "Status: %d confirmations\n"%conf
1200         if is_mine:
1201             if fee: 
1202                 tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
1203                               + "Transaction fee: %s\n"% format_satoshis(fee, False)
1204             else:
1205                 tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
1206                               + "Transaction fee: unknown\n"
1207         else:
1208             tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
1209
1210         tx_details += "Date: %s\n\n"%time_str \
1211             + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
1212             + "Outputs:\n-"+ '\n-'.join(outputs)
1213
1214         return tx_details
1215
1216
1217
1218     def newaddress_dialog(self, w):
1219
1220         title = "New Contact" 
1221         dialog = gtk.Dialog(title, parent=self.window, 
1222                             flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, 
1223                             buttons= ("cancel", 0, "ok",1)  )
1224         dialog.show()
1225
1226         label = gtk.HBox()
1227         label_label = gtk.Label('Label:')
1228         label_label.set_size_request(120,10)
1229         label_label.show()
1230         label.pack_start(label_label)
1231         label_entry = gtk.Entry()
1232         label_entry.show()
1233         label.pack_start(label_entry)
1234         label.show()
1235         dialog.vbox.pack_start(label, False, True, 5)
1236
1237         address = gtk.HBox()
1238         address_label = gtk.Label('Address:')
1239         address_label.set_size_request(120,10)
1240         address_label.show()
1241         address.pack_start(address_label)
1242         address_entry = gtk.Entry()
1243         address_entry.show()
1244         address.pack_start(address_entry)
1245         address.show()
1246         dialog.vbox.pack_start(address, False, True, 5)
1247         
1248         result = dialog.run()
1249         address = address_entry.get_text()
1250         label = label_entry.get_text()
1251         dialog.destroy()
1252
1253         if result == 1:
1254             if is_valid(address):
1255                 self.wallet.add_contact(address,label)
1256                 self.update_sending_tab()
1257             else:
1258                 errorDialog = gtk.MessageDialog(
1259                     parent=self.window,
1260                     flags=gtk.DIALOG_MODAL, 
1261                     buttons= gtk.BUTTONS_CLOSE, 
1262                     message_format = "Invalid address")
1263                 errorDialog.show()
1264                 errorDialog.run()
1265                 errorDialog.destroy()
1266
1267     
1268
1269 class ElectrumGui():
1270
1271     def __init__(self, config, network):
1272         self.network = network
1273         self.config = config
1274
1275
1276     def main(self, url=None):
1277
1278         storage = WalletStorage(self.config)
1279         if not storage.file_exists:
1280             action = self.restore_or_create()
1281             if not action:
1282                 exit()
1283             self.wallet = wallet = Wallet(storage)
1284             gap = self.config.get('gap_limit', 5)
1285             if gap != 5:
1286                 wallet.gap_limit = gap
1287                 wallet.storage.put('gap_limit', gap, True)
1288
1289             self.wallet.start_threads(self.network)
1290
1291             if action == 'create':
1292                 wallet.init_seed(None)
1293                 wallet.save_seed()
1294                 wallet.create_accounts()
1295                 wallet.synchronize()  # generate first addresses offline
1296             elif action == 'restore':
1297                 seed = self.seed_dialog()
1298                 wallet.init_seed(seed)
1299                 wallet.save_seed()
1300                 self.restore_wallet(wallet)
1301                 
1302             else:
1303                 exit()
1304         else:
1305             self.wallet = Wallet(storage)
1306             self.wallet.start_threads(self.network)
1307
1308         w = ElectrumWindow(self.wallet, self.config, self.network)
1309         if url: w.set_url(url)
1310         gtk.main()
1311
1312     def restore_or_create(self):
1313         return restore_create_dialog()
1314
1315     def seed_dialog(self):
1316         return run_recovery_dialog()
1317
1318     def verify_seed(self):
1319         self.wallet.save_seed()
1320         return True
1321
1322     def network_dialog(self):
1323         return run_network_dialog( self.network, parent=None )
1324
1325     def show_seed(self):
1326         show_seed_dialog(self.wallet, None, None)
1327
1328     def password_dialog(self):
1329         change_password_dialog(self.wallet, None, None)
1330
1331     def restore_wallet(self, wallet):
1332
1333         dialog = gtk.MessageDialog(
1334             parent = None,
1335             flags = gtk.DIALOG_MODAL, 
1336             buttons = gtk.BUTTONS_CANCEL, 
1337             message_format = "Please wait..."  )
1338         dialog.show()
1339
1340         def recover_thread( wallet, dialog ):
1341             wallet.restore(lambda x:x)
1342             gobject.idle_add( dialog.destroy )
1343
1344         thread.start_new_thread( recover_thread, ( wallet, dialog ) )
1345         r = dialog.run()
1346         dialog.destroy()
1347         if r==gtk.RESPONSE_CANCEL: return False
1348         if not wallet.is_found():
1349             show_message("No transactions found for this seed")
1350
1351         return True