initial
[electrum-nvc.git] / client / gui.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 import datetime
20 import thread, time, ast
21 import pygtk
22 pygtk.require('2.0')
23 import gtk, gobject
24
25 gtk.gdk.threads_init()
26 APP_NAME = "Electrum"
27
28 def format_satoshis(x):
29     xx = ("%f"%(x*1e-8)).rstrip('0')
30     if xx[-1] =='.': xx+="00"
31     if xx[-2] =='.': xx+="0"
32     return xx
33
34 def numbify(entry, is_int = False):
35     text = entry.get_text().strip()
36     s = ''.join([i for i in text if i in '0123456789.'])
37     entry.set_text(s)
38
39 def init_wallet(wallet):
40
41     if not wallet.read():
42         passphrase = None
43         while not passphrase:
44             dialog = gtk.MessageDialog(
45                 parent = None,
46                 flags = gtk.DIALOG_MODAL, 
47                 buttons = gtk.BUTTONS_OK_CANCEL, 
48                 message_format = "Wallet not found. Please enter a passphrase to create or recover your wallet. Minimum length: 20 characters"  )
49             
50             p_box = gtk.HBox()
51             p_label = gtk.Label('Passphrase:')
52             p_label.show()
53             p_box.pack_start(p_label)
54             p_entry = gtk.Entry()
55             p_entry.show()
56             p_box.pack_start(p_entry)
57             p_box.show()
58             dialog.vbox.pack_start(p_box, False, True, 0)
59             
60             dialog.show()
61             r = dialog.run()
62             passphrase = p_entry.get_text()
63             dialog.destroy()
64             if r==-6: exit(1)
65             if len(passphrase) < 20:
66                 print len(passphrase)
67                 passphrase = None
68
69         # disable password during recovery
70         # change_password_dialog(None, wallet)
71
72         wallet.passphrase = passphrase
73
74         run_settings_dialog( None, wallet, True)
75
76         dialog = gtk.MessageDialog(
77             parent = None,
78             flags = gtk.DIALOG_MODAL, 
79             buttons = gtk.BUTTONS_CANCEL, 
80             message_format = "Please wait..."  )
81         dialog.show()
82
83         def recover_thread( wallet, dialog, password ):
84             wallet.recover( password )
85             wallet.save()
86             gobject.idle_add( dialog.destroy )
87
88         thread.start_new_thread( recover_thread, ( wallet, dialog, None ) ) # no password
89         r = dialog.run()
90         dialog.destroy()
91         if r==-6: exit(1)
92
93 def settings_dialog(wallet, is_recover):
94
95     dialog = gtk.MessageDialog(
96         parent = None,
97         flags = gtk.DIALOG_MODAL, 
98         buttons = gtk.BUTTONS_OK_CANCEL, 
99         message_format = "Please indicate the server, and the gap limit if you are recovering a lost wallet." if is_recover else '' )
100
101     if not is_recover:
102         dialog.get_image().hide()
103         dialog.set_title("settings")
104
105     pw = gtk.HBox()
106     if not is_recover:
107         pw_label = gtk.Label('Encryption: ')
108         pw_label.set_size_request(100,10)
109         pw_label.show()
110         pw.pack_start(pw_label,False, False, 10)
111         pw_button = gtk.Button( ('Yes' if wallet.use_encryption else 'No'))
112         pw_button.connect("clicked", change_password_dialog, wallet)
113         pw_button.show()
114         pw.pack_start(pw_button,False, False, 10)
115         pw.show()
116
117     gap = gtk.HBox()
118     gap_label = gtk.Label('Max. gap:')
119     gap_label.set_size_request(100,10)
120     gap_label.show()
121     gap.pack_start(gap_label,False, False, 10)
122     gap_entry = gtk.Entry()
123     gap_entry.set_text("%d"%wallet.gap_limit)
124     gap_entry.connect('changed', numbify, True)
125     gap_entry.show()
126     gap.pack_start(gap_entry,False,False, 10)
127     add_help_button(gap, 'The maximum gap that is allowed between unused addresses in your wallet. During wallet recovery, this parameter is used to decide when to stop the recovery process. If you increase this value, you will need to remember it in order to be able to recover your wallet from passphrase.')
128     gap.show()
129
130     host = gtk.HBox()
131     host_label = gtk.Label('Server:')
132     host_label.set_size_request(100,10)
133     host_label.show()
134     host.pack_start(host_label,False, False, 10)
135     host_entry = gtk.Entry()
136     host_entry.set_text(wallet.host+":%d"%wallet.port)
137     host_entry.show()
138     host.pack_start(host_entry,False,False, 10)
139     add_help_button(host, 'The name and port number of your Bitcoin server, separated by a colon. Example: ecdsa.org:50000')
140     host.show()
141
142     fee = gtk.HBox()
143     fee_entry = gtk.Entry()
144     if not is_recover:
145         fee_label = gtk.Label('Tx. fee:')
146         fee_label.set_size_request(100,10)
147         fee_label.show()
148         fee.pack_start(fee_label,False, False, 10)
149         fee_entry.set_text("%f"%(wallet.fee))
150         fee_entry.connect('changed', numbify, False)
151         fee_entry.show()
152         fee.pack_start(fee_entry,False,False, 10)
153         add_help_button(fee, 'Transaction fee. Recommended value:0.005')
154         fee.show()
155
156     vbox = dialog.vbox
157     vbox.pack_start(pw, False, False, 5)
158     vbox.pack_start(host, False,False, 5)
159     vbox.pack_start(gap, False,False, 5)
160     vbox.pack_start(fee, False, False, 5)    
161     return dialog, gap_entry, host_entry, fee_entry
162
163
164 def run_settings_dialog( widget, wallet, is_recovery):
165     dialog, gap_entry, host_entry, fee_entry = settings_dialog(wallet, is_recovery)
166     dialog.show()
167     r = dialog.run()
168     gap = gap_entry.get_text()
169     hh = host_entry.get_text()
170     fee = fee_entry.get_text()
171     dialog.destroy()
172     if r==-6:
173         if is_recovery: 
174             exit(1)
175         else:
176             return
177     try:
178         a, b = hh.split(':')
179         wallet.gap_limit = int(gap)
180         wallet.host = a
181         wallet.port = int(b)
182         wallet.fee = float(fee)
183     except:
184         pass
185
186
187 def show_message(message):
188     dialog = gtk.MessageDialog(
189         parent = None,
190         flags = gtk.DIALOG_MODAL, 
191         buttons = gtk.BUTTONS_CLOSE, 
192         message_format = message )
193     dialog.show()
194     dialog.run()
195     dialog.destroy()
196
197 def password_line(label):
198     password = gtk.HBox()
199     password_label = gtk.Label(label)
200     password_label.set_size_request(120,10)
201     password_label.show()
202     password.pack_start(password_label,False, False, 10)
203     password_entry = gtk.Entry()
204     password_entry.set_visibility(False)
205     password_entry.show()
206     password.pack_start(password_entry,False,False, 10)
207     password.show()
208     return password, password_entry
209
210 def password_dialog():
211     dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
212                                 gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL,  "Your wallet is encrypted.")
213     dialog.get_image().set_visible(False)
214     current_pw, current_pw_entry = password_line('Password:')
215     current_pw_entry.connect("activate", lambda entry, dialog, response: dialog.response(response), dialog, gtk.RESPONSE_OK)
216     dialog.vbox.pack_start(current_pw, False, True, 0)
217     dialog.show()
218     result = dialog.run()
219     pw = current_pw_entry.get_text()
220     dialog.destroy()
221     if result: return pw
222
223 def change_password_dialog(button, wallet):
224     dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
225                                 gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL,  'Change password')
226     if wallet.use_encryption:
227         current_pw, current_pw_entry = password_line('Old password:')
228         dialog.vbox.pack_start(current_pw, False, True, 0)
229
230     password, password_entry = password_line('New password:')
231     dialog.vbox.pack_start(password, False, True, 5)
232     password2, password2_entry = password_line('Confirm password:')
233     dialog.vbox.pack_start(password2, False, True, 5)
234
235     dialog.show()
236     result = dialog.run()
237     password = current_pw_entry.get_text() if wallet.use_encryption else None
238     new_password = password_entry.get_text()
239     new_password2 = password2_entry.get_text()
240     dialog.destroy()
241     if result == 0: 
242         return
243
244     try:
245         passphrase = wallet.pw_decode( wallet.passphrase, password)
246         private_keys = ast.literal_eval( wallet.pw_decode( wallet.private_keys, password) )
247     except:
248         show_message("sorry")
249         return
250
251     if new_password != new_password2:
252         show_message("passwords do not match")
253         return
254
255     wallet.use_encryption = (new_password != '')
256     wallet.passphrase = wallet.pw_encode( passphrase, new_password)
257     wallet.private_keys = wallet.pw_encode( repr( private_keys ), new_password)
258     wallet.save()
259     if button:
260         button.set_label('Yes' if wallet.use_encryption else 'No')
261
262
263 def add_help_button(hbox, message):
264     button = gtk.Button('?')
265     button.connect("clicked", lambda x: show_message(message))
266     button.show()
267     hbox.pack_start(button,False, False)
268
269
270 class MyWindow(gtk.Window): __gsignals__ = dict( mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, None, (str,)) )
271
272 gobject.type_register(MyWindow)
273 gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.W, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+W')
274 gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.Q, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+Q')
275
276
277 class BitcoinGUI:
278
279     def __init__(self, wallet):
280         self.error = ''
281         self.wallet = wallet
282
283         self.update_time = 0 
284         self.window = MyWindow(gtk.WINDOW_TOPLEVEL)
285         self.window.set_title(APP_NAME)
286         self.window.connect("destroy", gtk.main_quit)
287         self.window.set_border_width(0)
288         self.window.connect('mykeypress', gtk.main_quit)
289         self.window.set_default_size(650, 350)
290
291         vbox = gtk.VBox()
292
293         self.notebook = gtk.Notebook()
294         self.create_history_tab()
295         self.create_send_tab()
296         self.create_recv_tab()
297         self.create_book_tab()
298
299         #self.add_tab( make_settings_box( self.wallet, False), 'Preferences')
300         self.create_about_tab()
301
302         self.notebook.show()
303         vbox.pack_start(self.notebook, True, True, 2)
304         
305         # status bar for balance, connection, blocks
306         self.status_bar = gtk.Statusbar()
307         vbox.pack_start(self.status_bar, False, False, 0)
308
309         self.status_image = gtk.Image()
310         self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
311         self.status_image.set_alignment(True, 0.5  )
312         self.status_image.show()
313         self.status_bar.pack_end(self.status_image,False,False)
314
315         settings_icon = gtk.Image()
316         settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
317         settings_icon.set_alignment(True, False)
318         settings_icon.set_size_request(30,9 )
319         settings_icon.show()
320
321         prefs_button = gtk.Button()
322         prefs_button.connect("clicked", run_settings_dialog, self.wallet, False)
323         prefs_button.add(settings_icon)
324         prefs_button.set_tooltip_text("Settings")
325         prefs_button.show()
326         self.status_bar.pack_end(prefs_button,False,False)
327
328         self.window.add(vbox)
329         self.window.show_all()
330
331         self.context_id = self.status_bar.get_context_id("statusbar")
332         self.update_status_bar()
333
334         def update_status_bar_thread():
335             while True:
336                 gobject.idle_add( self.update_status_bar )
337                 time.sleep(0.5)
338
339         def update_wallet_thread():
340             import socket, traceback, sys
341             while True:
342                 try:
343                     self.wallet.new_session()
344                 except:
345                     self.error = "Not connected"
346                     time.sleep(5)
347                     continue
348                 self.info.set_text( self.wallet.message)
349
350                 while True:
351                     try:
352                         u = self.wallet.update()
353                     except:
354                         self.error = "Not connected"
355                         print "error"
356                         traceback.print_exc(file=sys.stdout)
357                         break
358                     self.update_time = time.time()
359                     self.error = ''
360                     if u:
361                         self.wallet.save()
362                         gobject.idle_add( self.update_history_tab )
363                     time.sleep(5)
364                     
365         thread.start_new_thread(update_wallet_thread, ())
366         thread.start_new_thread(update_status_bar_thread, ())
367         self.notebook.set_current_page(0)
368
369
370     def add_tab(self, page, name):
371         tab_label = gtk.Label(name)
372         tab_label.show()
373         self.notebook.append_page(page, tab_label)
374
375
376     def create_send_tab(self):
377
378         page = vbox = gtk.VBox()
379         page.show()
380
381         payto = gtk.HBox()
382         payto_label = gtk.Label('Pay to:')
383         payto_label.set_size_request(100,10)
384         payto_label.show()
385         payto.pack_start(payto_label, False)
386         payto_entry = gtk.Entry()
387         payto_entry.set_size_request(350, 26)
388         payto_entry.show()
389         payto.pack_start(payto_entry, False)
390         vbox.pack_start(payto, False, False, 5)
391         
392         label = gtk.HBox()
393         label_label = gtk.Label('Label:')
394         label_label.set_size_request(100,10)
395         label_label.show()
396         label.pack_start(label_label, False)
397         label_entry = gtk.Entry()
398         label_entry.set_size_request(350, 26)
399         label_entry.show()
400         label.pack_start(label_entry, False)
401         vbox.pack_start(label, False, False, 5)
402
403         amount = gtk.HBox()
404         amount_label = gtk.Label('Amount:')
405         amount_label.set_size_request(100,10)
406         amount_label.show()
407         amount.pack_start(amount_label, False)
408         amount_entry = gtk.Entry()
409         amount_entry.set_size_request(100, 26) 
410         amount_entry.connect('changed', numbify)
411         amount_entry.show()
412         amount.pack_start(amount_entry, False)
413         vbox.pack_start(amount, False, False, 5)
414
415         button = gtk.Button("Send")
416         button.connect("clicked", self.do_send, (payto_entry, label_entry, amount_entry))
417         button.show()
418         amount.pack_start(button, False, False, 5)
419
420         self.payto_entry = payto_entry
421         self.payto_amount_entry = amount_entry
422         self.payto_label_entry = label_entry
423         self.add_tab(page, 'Send')
424
425     def create_about_tab(self):
426         page = gtk.VBox()
427         page.show()
428         self.info = gtk.Label('')  
429         self.info.set_selectable(True)
430         page.pack_start(self.info)
431         #tv = gtk.TextView()
432         #tv.set_editable(False)
433         #tv.set_cursor_visible(False)
434         #page.pack_start(tv)
435         #self.info = tv.get_buffer()
436         self.add_tab(page, 'Board')
437
438     def do_send(self, w, data):
439         payto_entry, label_entry, amount_entry = data
440         
441         label = label_entry.get_text()
442
443         to_address = payto_entry.get_text()
444         if not self.wallet.is_valid(to_address):
445             show_message( "invalid bitcoin address" )
446             return
447
448         try:
449             amount = float(amount_entry.get_text())
450         except:
451             show_message( "invalid amount" )
452             return
453
454         password = password_dialog() if self.wallet.use_encryption else None
455
456         status, msg = self.wallet.send( to_address, amount, label, password )
457         if status:
458             show_message( "payment sent.\n" + msg )
459             payto_entry.set_text("")
460             label_entry.set_text("")
461             amount_entry.set_text("")
462         else:
463             show_message( msg )
464
465
466     def treeview_key_press(self, treeview, event):
467         c = treeview.get_cursor()[0]
468         if event.keyval == gtk.keysyms.Up:
469             if c and c[0] == 0:
470                 treeview.parent.grab_focus()
471                 treeview.set_cursor((0,))
472         elif event.keyval == gtk.keysyms.Return and treeview == self.history_treeview:
473             tx_hash = self.history_list.get_value( self.history_list.get_iter(c), 0)
474             tx = self.wallet.tx_history.get(tx_hash)
475             # print "tx details:\n"+repr(tx)
476             inputs = '\n-'.join(tx['inputs'])
477             outputs = '\n-'.join(tx['outputs'])
478             msg = tx_hash + "\n\ninputs:\n-"+ inputs + "\noutputs:\n-"+ outputs + "\n"
479             show_message(msg)
480         return False
481
482     def create_history_tab(self):
483
484         self.history_list = gtk.ListStore(str, str, str, str, 'gboolean',  str, str,str)
485         treeview = gtk.TreeView(model=self.history_list)
486         self.history_treeview = treeview
487         treeview.set_tooltip_column(7)
488         treeview.show()
489         treeview.connect('key-press-event', self.treeview_key_press)
490
491         tvcolumn = gtk.TreeViewColumn('tx_id')
492         treeview.append_column(tvcolumn)
493         cell = gtk.CellRendererText()
494         tvcolumn.pack_start(cell, False)
495         tvcolumn.add_attribute(cell, 'text', 0)
496         tvcolumn.set_visible(False)
497
498         tvcolumn = gtk.TreeViewColumn('')
499         treeview.append_column(tvcolumn)
500         cell = gtk.CellRendererPixbuf()
501         tvcolumn.pack_start(cell, False)
502         tvcolumn.set_attributes(cell, stock_id=1)
503
504         tvcolumn = gtk.TreeViewColumn('Date')
505         treeview.append_column(tvcolumn)
506         cell = gtk.CellRendererText()
507         tvcolumn.pack_start(cell, False)
508         tvcolumn.add_attribute(cell, 'text', 2)
509
510         tvcolumn = gtk.TreeViewColumn('Label')
511         treeview.append_column(tvcolumn)
512         cell = gtk.CellRendererText()
513         cell.set_property('foreground', 'grey')
514         cell.set_property('family', 'monospace')
515         cell.set_property('editable', True)
516         def edited_cb(cell, path, new_text, h_list):
517             tx = h_list.get_value( h_list.get_iter(path), 0)
518             self.wallet.labels[tx] = new_text
519             self.wallet.save() 
520             self.update_history_tab()
521         cell.connect('edited', edited_cb, self.history_list)
522         def editing_started(cell, entry, path, h_list):
523             tx = h_list.get_value( h_list.get_iter(path), 0)
524             if not self.wallet.labels.get(tx): entry.set_text('')
525         cell.connect('editing-started', editing_started, self.history_list)
526         tvcolumn.set_expand(True)
527         tvcolumn.pack_start(cell, True)
528         tvcolumn.set_attributes(cell, text=3, foreground_set = 4)
529
530         tvcolumn = gtk.TreeViewColumn('Amount')
531         treeview.append_column(tvcolumn)
532         cell = gtk.CellRendererText()
533         cell.set_alignment(1, 0.5)
534         tvcolumn.pack_start(cell, False)
535         tvcolumn.add_attribute(cell, 'text', 5)
536
537         tvcolumn = gtk.TreeViewColumn('Balance')
538         treeview.append_column(tvcolumn)
539         cell = gtk.CellRendererText()
540         cell.set_alignment(1, 0.5)
541         tvcolumn.pack_start(cell, False)
542         tvcolumn.add_attribute(cell, 'text', 6)
543
544         tvcolumn = gtk.TreeViewColumn('Tooltip')
545         treeview.append_column(tvcolumn)
546         cell = gtk.CellRendererText()
547         tvcolumn.pack_start(cell, False)
548         tvcolumn.add_attribute(cell, 'text', 7)
549         tvcolumn.set_visible(False)
550
551         scroll = gtk.ScrolledWindow()
552         scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
553         scroll.add(treeview)
554
555         self.add_tab(scroll, 'History')
556         self.update_history_tab()
557
558
559     def create_recv_tab(self):
560         self.recv_list = gtk.ListStore(str, str, str)
561         self.add_tab( self.make_address_list(True), 'Receive')
562         self.update_receiving_tab()
563
564     def create_book_tab(self):
565         self.addressbook_list = gtk.ListStore(str, str, str)
566         self.add_tab( self.make_address_list(False), 'Contacts')
567         self.update_sending_tab()
568
569     def make_address_list(self, is_recv):
570         liststore = self.recv_list if is_recv else self.addressbook_list
571         treeview = gtk.TreeView(model= liststore)
572         treeview.connect('key-press-event', self.treeview_key_press)
573         treeview.show()
574
575         tvcolumn = gtk.TreeViewColumn('Address')
576         treeview.append_column(tvcolumn)
577         cell = gtk.CellRendererText()
578         cell.set_property('family', 'monospace')
579         tvcolumn.pack_start(cell, True)
580         tvcolumn.add_attribute(cell, 'text', 0)
581
582         tvcolumn = gtk.TreeViewColumn('Label')
583         tvcolumn.set_expand(True)
584         treeview.append_column(tvcolumn)
585         cell = gtk.CellRendererText()
586         cell.set_property('editable', True)
587         def edited_cb2(cell, path, new_text, liststore):
588             address = liststore.get_value( liststore.get_iter(path), 0)
589             self.wallet.labels[address] = new_text
590             self.wallet.save() 
591             self.wallet.update_tx_labels()
592             self.update_receiving_tab()
593             self.update_sending_tab()
594             self.update_history_tab()
595         cell.connect('edited', edited_cb2, liststore)
596         tvcolumn.pack_start(cell, True)
597         tvcolumn.add_attribute(cell, 'text', 1)
598
599         tvcolumn = gtk.TreeViewColumn('Tx')
600         treeview.append_column(tvcolumn)
601         cell = gtk.CellRendererText()
602         tvcolumn.pack_start(cell, True)
603         tvcolumn.add_attribute(cell, 'text', 2)
604
605         scroll = gtk.ScrolledWindow()
606         scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
607         scroll.add(treeview)
608
609         hbox = gtk.HBox()
610         button = gtk.Button("New address")
611         button.connect("clicked", self.newaddress_dialog, is_recv)
612         button.show()
613         hbox.pack_start(button,False)
614
615         button = gtk.Button("Copy to clipboard")
616         def copy2clipboard(w, treeview, liststore):
617             path, col =  treeview.get_cursor()
618             if path:
619                 address =  liststore.get_value( liststore.get_iter(path), 0)
620                 c = gtk.clipboard_get()
621                 c.set_text( address )
622         button.connect("clicked", copy2clipboard, treeview, liststore)
623         button.show()
624         hbox.pack_start(button,False)
625
626         if not is_recv:
627             button = gtk.Button("Pay to")
628             def payto(w, treeview, liststore):
629                 path, col =  treeview.get_cursor()
630                 if path:
631                     address =  liststore.get_value( liststore.get_iter(path), 0)
632                     self.payto_entry.set_text( address )
633                     self.notebook.set_current_page(1)
634                     self.payto_amount_entry.grab_focus()
635
636             button.connect("clicked", payto, treeview, liststore)
637             button.show()
638             hbox.pack_start(button,False)
639
640         vbox = gtk.VBox()
641         vbox.pack_start(scroll,True)
642         vbox.pack_start(hbox, False)
643         return vbox
644
645     def update_status_bar(self):
646         c, u = self.wallet.get_balance()
647         dt = time.time() - self.update_time
648         if dt < 15:
649             self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
650             self.status_image.set_tooltip_text("Connected to %s.\n%d blocks"%(self.wallet.host, self.wallet.blocks))
651         else:
652             self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
653             self.status_image.set_tooltip_text("Trying to contact %s.\n%d blocks"%(self.wallet.host, self.wallet.blocks))
654         text =  "Balance: %s "%( format_satoshis(c) )
655         if u: text +=  "[+ %s unconfirmed]"%( format_satoshis(u) )
656         if self.error: text = self.error
657         self.status_bar.pop(self.context_id) 
658         self.status_bar.push(self.context_id, text) 
659
660     def update_receiving_tab(self):
661         self.recv_list.clear()
662         for address in self.wallet.addresses:
663             label = self.wallet.labels.get(address)
664             n = 0 
665             h = self.wallet.history.get(address)
666             if h:
667                 for item in h:
668                     if not item['is_in'] : n=n+1
669             tx = "None" if n==0 else "%d"%n
670             self.recv_list.prepend((address, label, tx ))
671
672     def update_sending_tab(self):
673         # detect addresses that are not mine in history, add them here...
674         self.addressbook_list.clear()
675         for address in self.wallet.addressbook:
676             label = self.wallet.labels.get(address)
677             n = 0 
678             for item in self.wallet.tx_history.values():
679                 if address in item['outputs'] : n=n+1
680             tx = "None" if n==0 else "%d"%n
681             self.addressbook_list.append((address, label, tx))
682
683     def update_history_tab(self):
684         cursor = self.history_treeview.get_cursor()[0]
685         self.history_list.clear()
686         balance = 0 
687         for tx in self.wallet.get_tx_history():
688             tx_hash = tx['tx_hash']
689             if tx['height']:
690                 conf = self.wallet.blocks - tx['height'] + 1
691                 time_str = datetime.datetime.fromtimestamp( tx['nTime']).isoformat(' ')[:-3]
692                 conf_icon = gtk.STOCK_APPLY
693             else:
694                 conf = 0
695                 time_str = 'pending'
696                 conf_icon = gtk.STOCK_EXECUTE
697             v = tx['value']
698             balance += v 
699             label = self.wallet.labels.get(tx_hash)
700             is_default_label = (label == '') or (label is None)
701             if is_default_label: label = tx['default_label']
702             tooltip = tx_hash + "\n%d confirmations"%conf 
703             self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label, 
704                                         ('+' if v>0 else '') + format_satoshis(v), format_satoshis(balance), tooltip] )
705         if cursor: self.history_treeview.set_cursor( cursor )
706
707
708
709     def newaddress_dialog(self, w, is_recv):
710
711         if not is_recv:
712
713             title = "New sending address" 
714             dialog = gtk.Dialog(title, parent=self.window, 
715                                 flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, 
716                                 buttons= ("cancel", 0, "ok",1)  )
717             dialog.show()
718
719             label = gtk.HBox()
720             label_label = gtk.Label('Label:')
721             label_label.set_size_request(120,10)
722             label_label.show()
723             label.pack_start(label_label)
724             label_entry = gtk.Entry()
725             label_entry.show()
726             label.pack_start(label_entry)
727             label.show()
728             dialog.vbox.pack_start(label, False, True, 5)
729
730             address = gtk.HBox()
731             address_label = gtk.Label('Address:')
732             address_label.set_size_request(120,10)
733             address_label.show()
734             address.pack_start(address_label)
735             address_entry = gtk.Entry()
736             address_entry.show()
737             address.pack_start(address_entry)
738             address.show()
739             dialog.vbox.pack_start(address, False, True, 5)
740
741             result = dialog.run()
742             address = address_entry.get_text()
743             label = label_entry.get_text()
744             dialog.destroy()
745
746             if result == 1:
747                 if self.wallet.is_valid(address):
748                     self.wallet.addressbook.append(address)
749                     if label:  self.wallet.labels[address] = label
750                     self.wallet.save()
751                     self.update_sending_tab()
752                 else:
753                     errorDialog = gtk.MessageDialog(
754                         parent=self.window,
755                         flags=gtk.DIALOG_MODAL, 
756                         buttons= gtk.BUTTONS_CLOSE, 
757                         message_format = "Invalid address")
758                     errorDialog.show()
759                     errorDialog.run()
760                     errorDialog.destroy()
761         else:
762                 password = password_dialog() if self.wallet.use_encryption else None
763                 success, ret = self.wallet.get_new_address(password)
764                 if success:
765                     address = ret
766                     #if label:  self.wallet.labels[address] = label
767                     self.wallet.save()
768                     self.update_receiving_tab()
769                 else:
770                     msg = ret
771                     errorDialog = gtk.MessageDialog(
772                         parent=self.window,
773                         flags=gtk.DIALOG_MODAL, 
774                         buttons= gtk.BUTTONS_CLOSE, 
775                         message_format = msg)
776                     errorDialog.show()
777                     errorDialog.run()
778                     errorDialog.destroy()
779     
780     def main(self):
781         gtk.main()
782