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