3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
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.
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.
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/>.
20 import thread, time, ast, sys
21 import socket, traceback
27 gtk.gdk.threads_init()
30 def format_satoshis(x):
31 xx = ("%f"%(x*1e-8)).rstrip('0')
32 if xx[-1] =='.': xx+="00"
33 if xx[-2] =='.': xx+="0"
36 def numbify(entry, is_int = False):
37 text = entry.get_text().strip()
38 s = ''.join([i for i in text if i in '0123456789.'])
42 def show_seed_dialog(wallet, password):
45 seed = wallet.pw_decode( wallet.seed, password)
46 private_keys = ast.literal_eval( wallet.pw_decode( wallet.private_keys, password) )
48 show_message("Incorrect password")
50 dialog = gtk.MessageDialog(
52 flags = gtk.DIALOG_MODAL,
53 buttons = gtk.BUTTONS_OK,
54 message_format = "Your wallet generation seed is:\n\n" + seed \
55 + "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \
56 + "Alternatively, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" + ' '.join(mnemonic.mn_encode(seed)) + "\"" )
61 def init_wallet(wallet):
65 # ask if the user wants to create a new wallet, or recover from a seed.
66 # if he wants to recover, and nothing is found, do not create wallet
67 dialog = gtk.Dialog("electrum", parent=None,
68 flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR,
69 buttons= ("create", 0, "restore",1, "cancel",2) )
71 label = gtk.Label("Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?" )
73 dialog.vbox.pack_start(label)
87 run_settings_dialog(wallet, is_create=True, is_recovery=False)
90 wallet.create_new_address(False, None)
92 # run a dialog indicating the seed, ask the user to remember it
93 show_seed_dialog(wallet, None)
96 change_password_dialog(wallet, None)
99 # ask for the server, seed and gap.
100 run_settings_dialog(wallet, is_create=True, is_recovery=True)
102 dialog = gtk.MessageDialog(
104 flags = gtk.DIALOG_MODAL,
105 buttons = gtk.BUTTONS_CANCEL,
106 message_format = "Please wait..." )
109 def recover_thread( wallet, dialog, password ):
110 wallet.is_found = wallet.recover( password )
113 gobject.idle_add( dialog.destroy )
115 thread.start_new_thread( recover_thread, ( wallet, dialog, None ) ) # no password
118 if r==gtk.RESPONSE_CANCEL: sys.exit(1)
119 if not wallet.is_found:
120 show_message("No transactions found for this seed")
123 def settings_dialog(wallet, is_create, is_recovery):
126 dialog = gtk.MessageDialog(
128 flags = gtk.DIALOG_MODAL,
129 buttons = gtk.BUTTONS_OK_CANCEL,
130 message_format = "Please indicate the server and port number" if not is_recovery else 'Please enter your wallet seed or the corresponding mnemonic list of words, the server and the gap limit')
132 dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
133 gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, "Settings")
134 dialog.get_image().set_visible(False)
137 dialog.set_default_response(gtk.RESPONSE_OK)
140 # ask seed, server and gap in the same dialog
141 seed_box = gtk.HBox()
142 seed_label = gtk.Label('Seed or mnemonic:')
143 seed_label.set_size_request(150,-1)
144 seed_box.pack_start(seed_label, False, False, 10)
146 seed_entry = gtk.Entry()
148 seed_entry.set_size_request(450,-1)
149 seed_box.pack_start(seed_entry, False, False, 10)
150 add_help_button(seed_box, '.')
152 vbox.pack_start(seed_box, False, False, 5)
154 if is_recovery or (not is_create):
156 gap_label = gtk.Label('Gap limit:')
157 gap_label.set_size_request(150,10)
159 gap.pack_start(gap_label,False, False, 10)
160 gap_entry = gtk.Entry()
161 gap_entry.set_text("%d"%wallet.gap_limit)
162 gap_entry.connect('changed', numbify, True)
164 gap.pack_start(gap_entry,False,False, 10)
165 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 seed.')
167 vbox.pack_start(gap, False,False, 5)
170 host_label = gtk.Label('Server:')
171 host_label.set_size_request(150,10)
173 host.pack_start(host_label,False, False, 10)
174 host_entry = gtk.Entry()
175 host_entry.set_text(wallet.host+":%d"%wallet.port)
177 host.pack_start(host_entry,False,False, 10)
178 add_help_button(host, 'The name and port number of your Bitcoin server, separated by a colon. Example: "ecdsa.org:50000". If no port number is provided, the http port 80 will be tried.')
180 vbox.pack_start(host, False,False, 5)
184 fee_entry = gtk.Entry()
185 fee_label = gtk.Label('Tx. fee:')
186 fee_label.set_size_request(150,10)
188 fee.pack_start(fee_label,False, False, 10)
189 fee_entry.set_text("%f"%(wallet.fee))
190 fee_entry.connect('changed', numbify, False)
192 fee.pack_start(fee_entry,False,False, 10)
193 add_help_button(fee, 'Transaction fee. Recommended value:0.005')
195 vbox.pack_start(fee, False,False, 5)
198 return dialog, fee_entry, gap_entry, host_entry
200 return dialog, seed_entry, gap_entry, host_entry
202 return dialog, host_entry
205 def run_settings_dialog( wallet, is_create, is_recovery):
208 dialog, fee_entry, gap_entry, host_entry = settings_dialog(wallet, is_create, is_recovery)
210 dialog, seed_entry, gap_entry, host_entry = settings_dialog(wallet, is_create, is_recovery)
212 dialog, host_entry, = settings_dialog(wallet, is_create, is_recovery)
216 hh = host_entry.get_text()
218 gap = gap_entry.get_text()
219 seed = seed_entry.get_text()
224 print "not hex, trying decode"
225 seed = mnemonic.mn_decode( seed.split(' ') )
227 if r==gtk.RESPONSE_CANCEL:
228 if is_create: sys.exit(1)
233 host, port = hh.split(':')
238 if is_recovery: gap = int(gap)
240 show_message("error")
247 wallet.gap_limit = gap
251 def show_message(message):
252 dialog = gtk.MessageDialog(
254 flags = gtk.DIALOG_MODAL,
255 buttons = gtk.BUTTONS_CLOSE,
256 message_format = message )
261 def password_line(label):
262 password = gtk.HBox()
263 password_label = gtk.Label(label)
264 password_label.set_size_request(120,10)
265 password_label.show()
266 password.pack_start(password_label,False, False, 10)
267 password_entry = gtk.Entry()
268 password_entry.set_size_request(300,-1)
269 password_entry.set_visibility(False)
270 password_entry.show()
271 password.pack_start(password_entry,False,False, 10)
273 return password, password_entry
275 def password_dialog():
276 dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
277 gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, "Please enter your password.")
278 dialog.get_image().set_visible(False)
279 current_pw, current_pw_entry = password_line('Password:')
280 current_pw_entry.connect("activate", lambda entry, dialog, response: dialog.response(response), dialog, gtk.RESPONSE_OK)
281 dialog.vbox.pack_start(current_pw, False, True, 0)
283 result = dialog.run()
284 pw = current_pw_entry.get_text()
288 def change_password_dialog(wallet, icon):
290 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'
292 msg = "Please choose a password to encrypt your wallet keys"
294 dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
297 if wallet.use_encryption:
298 current_pw, current_pw_entry = password_line('Current password:')
299 dialog.vbox.pack_start(current_pw, False, True, 0)
301 password, password_entry = password_line('New password:')
302 dialog.vbox.pack_start(password, False, True, 5)
303 password2, password2_entry = password_line('Confirm password:')
304 dialog.vbox.pack_start(password2, False, True, 5)
307 result = dialog.run()
308 password = current_pw_entry.get_text() if wallet.use_encryption else None
309 new_password = password_entry.get_text()
310 new_password2 = password2_entry.get_text()
312 if result == gtk.RESPONSE_CANCEL:
316 seed = wallet.pw_decode( wallet.seed, password)
317 private_keys = ast.literal_eval( wallet.pw_decode( wallet.private_keys, password) )
319 show_message("Incorrect password")
322 if new_password != new_password2:
323 show_message("passwords do not match")
326 wallet.use_encryption = (new_password != '')
327 wallet.seed = wallet.pw_encode( seed, new_password)
328 wallet.private_keys = wallet.pw_encode( repr( private_keys ), new_password)
332 if wallet.use_encryption:
333 icon.set_tooltip_text('wallet is encrypted')
335 icon.set_tooltip_text('wallet is unencrypted')
338 def add_help_button(hbox, message):
339 button = gtk.Button('?')
340 button.connect("clicked", lambda x: show_message(message))
342 hbox.pack_start(button,False, False)
345 class MyWindow(gtk.Window): __gsignals__ = dict( mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, None, (str,)) )
347 gobject.type_register(MyWindow)
348 gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.W, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+W')
349 gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.Q, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+Q')
354 def __init__(self, wallet):
360 self.window = MyWindow(gtk.WINDOW_TOPLEVEL)
361 self.window.set_title(APP_NAME)
362 self.window.connect("destroy", gtk.main_quit)
363 self.window.set_border_width(0)
364 self.window.connect('mykeypress', gtk.main_quit)
365 self.window.set_default_size(670, 350)
369 self.notebook = gtk.Notebook()
370 self.create_history_tab()
371 self.create_send_tab()
372 self.create_recv_tab()
373 self.create_book_tab()
374 #self.add_tab( make_settings_box( self.wallet, False), 'Preferences')
375 self.create_about_tab()
377 vbox.pack_start(self.notebook, True, True, 2)
379 self.status_bar = gtk.Statusbar()
380 vbox.pack_start(self.status_bar, False, False, 0)
382 self.status_image = gtk.Image()
383 self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
384 self.status_image.set_alignment(True, 0.5 )
385 self.status_image.show()
386 self.status_bar.pack_end(self.status_image, False, False)
389 def seedb(w, wallet):
390 if wallet.use_encryption:
391 password = password_dialog()
392 if not password: return
393 else: password = None
394 show_seed_dialog(wallet, password)
395 button = gtk.Button('S')
396 button.connect("clicked", seedb, wallet )
397 button.set_relief(gtk.RELIEF_NONE)
399 self.status_bar.pack_end(button,False, False)
401 settings_icon = gtk.Image()
402 settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
403 settings_icon.set_alignment(0.5, 0.5)
404 settings_icon.set_size_request(16,16 )
407 prefs_button = gtk.Button()
408 prefs_button.connect("clicked", lambda x: run_settings_dialog(self.wallet, False, False) )
409 prefs_button.add(settings_icon)
410 prefs_button.set_tooltip_text("Settings")
411 prefs_button.set_relief(gtk.RELIEF_NONE)
413 self.status_bar.pack_end(prefs_button,False,False)
415 pw_icon = gtk.Image()
416 pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU)
417 pw_icon.set_alignment(0.5, 0.5)
418 pw_icon.set_size_request(16,16 )
421 password_button = gtk.Button()
422 password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, pw_icon))
423 password_button.add(pw_icon)
424 password_button.set_relief(gtk.RELIEF_NONE)
425 password_button.show()
426 self.status_bar.pack_end(password_button,False,False)
428 self.window.add(vbox)
429 self.window.show_all()
431 self.context_id = self.status_bar.get_context_id("statusbar")
432 self.update_status_bar()
434 def update_status_bar_thread():
436 gobject.idle_add( self.update_status_bar )
439 def update_wallet_thread():
442 self.wallet.new_session()
444 self.error = "Not connected"
445 time.sleep(self.period)
448 self.info.set_text( self.wallet.message)
451 self.period = 15 if self.wallet.use_http() else 5
453 u = self.wallet.update()
454 except socket.gaierror:
455 self.error = "Not connected"
458 self.error = "Not connected"
460 traceback.print_exc(file=sys.stdout)
462 self.update_time = time.time()
466 gobject.idle_add( self.update_history_tab )
467 time.sleep(self.period)
469 thread.start_new_thread(update_wallet_thread, ())
470 thread.start_new_thread(update_status_bar_thread, ())
471 self.notebook.set_current_page(0)
474 def add_tab(self, page, name):
475 tab_label = gtk.Label(name)
477 self.notebook.append_page(page, tab_label)
480 def create_send_tab(self):
482 page = vbox = gtk.VBox()
486 payto_label = gtk.Label('Pay to:')
487 payto_label.set_size_request(100,10)
489 payto.pack_start(payto_label, False)
490 payto_entry = gtk.Entry()
491 payto_entry.set_size_request(350, 26)
493 payto.pack_start(payto_entry, False)
494 vbox.pack_start(payto, False, False, 5)
497 label_label = gtk.Label('Label:')
498 label_label.set_size_request(100,10)
500 label.pack_start(label_label, False)
501 label_entry = gtk.Entry()
502 label_entry.set_size_request(350, 26)
504 label.pack_start(label_entry, False)
505 vbox.pack_start(label, False, False, 5)
508 amount_label = gtk.Label('Amount:')
509 amount_label.set_size_request(100,10)
511 amount.pack_start(amount_label, False)
512 amount_entry = gtk.Entry()
513 amount_entry.set_size_request(100, 26)
514 amount_entry.connect('changed', numbify)
516 amount.pack_start(amount_entry, False)
517 vbox.pack_start(amount, False, False, 5)
519 button = gtk.Button("Send")
520 button.connect("clicked", self.do_send, (payto_entry, label_entry, amount_entry))
522 amount.pack_start(button, False, False, 5)
524 self.payto_entry = payto_entry
525 self.payto_amount_entry = amount_entry
526 self.payto_label_entry = label_entry
527 self.add_tab(page, 'Send')
529 def create_about_tab(self):
532 self.info = gtk.Label('')
533 self.info.set_selectable(True)
534 page.pack_start(self.info)
536 #tv.set_editable(False)
537 #tv.set_cursor_visible(False)
539 #self.info = tv.get_buffer()
540 self.add_tab(page, 'Wall')
542 def do_send(self, w, data):
543 payto_entry, label_entry, amount_entry = data
545 label = label_entry.get_text()
547 to_address = payto_entry.get_text()
548 if not self.wallet.is_valid(to_address):
549 show_message( "invalid bitcoin address" )
553 amount = float(amount_entry.get_text())
555 show_message( "invalid amount" )
558 password = password_dialog() if self.wallet.use_encryption else None
560 status, tx = self.wallet.mktx( to_address, amount, label, password )
561 self.wallet.new_session() # we created a new change address
566 status, msg = self.wallet.sendtx( tx )
568 show_message( "payment sent.\n" + msg )
569 payto_entry.set_text("")
570 label_entry.set_text("")
571 amount_entry.set_text("")
572 self.update_sending_tab()
577 def treeview_key_press(self, treeview, event):
578 c = treeview.get_cursor()[0]
579 if event.keyval == gtk.keysyms.Up:
581 treeview.parent.grab_focus()
582 treeview.set_cursor((0,))
583 elif event.keyval == gtk.keysyms.Return and treeview == self.history_treeview:
584 tx_hash = self.history_list.get_value( self.history_list.get_iter(c), 0)
585 tx = self.wallet.tx_history.get(tx_hash)
586 # print "tx details:\n"+repr(tx)
587 inputs = '\n-'.join(tx['inputs'])
588 outputs = '\n-'.join(tx['outputs'])
589 msg = tx_hash + "\n\ninputs:\n-"+ inputs + "\noutputs:\n-"+ outputs + "\n"
593 def create_history_tab(self):
595 self.history_list = gtk.ListStore(str, str, str, str, 'gboolean', str, str,str)
596 treeview = gtk.TreeView(model=self.history_list)
597 self.history_treeview = treeview
598 treeview.set_tooltip_column(7)
600 treeview.connect('key-press-event', self.treeview_key_press)
602 tvcolumn = gtk.TreeViewColumn('tx_id')
603 treeview.append_column(tvcolumn)
604 cell = gtk.CellRendererText()
605 tvcolumn.pack_start(cell, False)
606 tvcolumn.add_attribute(cell, 'text', 0)
607 tvcolumn.set_visible(False)
609 tvcolumn = gtk.TreeViewColumn('')
610 treeview.append_column(tvcolumn)
611 cell = gtk.CellRendererPixbuf()
612 tvcolumn.pack_start(cell, False)
613 tvcolumn.set_attributes(cell, stock_id=1)
615 tvcolumn = gtk.TreeViewColumn('Date')
616 treeview.append_column(tvcolumn)
617 cell = gtk.CellRendererText()
618 tvcolumn.pack_start(cell, False)
619 tvcolumn.add_attribute(cell, 'text', 2)
621 tvcolumn = gtk.TreeViewColumn('Label')
622 treeview.append_column(tvcolumn)
623 cell = gtk.CellRendererText()
624 cell.set_property('foreground', 'grey')
625 cell.set_property('family', 'monospace')
626 cell.set_property('editable', True)
627 def edited_cb(cell, path, new_text, h_list):
628 tx = h_list.get_value( h_list.get_iter(path), 0)
629 self.wallet.labels[tx] = new_text
631 self.update_history_tab()
632 cell.connect('edited', edited_cb, self.history_list)
633 def editing_started(cell, entry, path, h_list):
634 tx = h_list.get_value( h_list.get_iter(path), 0)
635 if not self.wallet.labels.get(tx): entry.set_text('')
636 cell.connect('editing-started', editing_started, self.history_list)
637 tvcolumn.set_expand(True)
638 tvcolumn.pack_start(cell, True)
639 tvcolumn.set_attributes(cell, text=3, foreground_set = 4)
641 tvcolumn = gtk.TreeViewColumn('Amount')
642 treeview.append_column(tvcolumn)
643 cell = gtk.CellRendererText()
644 cell.set_alignment(1, 0.5)
645 tvcolumn.pack_start(cell, False)
646 tvcolumn.add_attribute(cell, 'text', 5)
648 tvcolumn = gtk.TreeViewColumn('Balance')
649 treeview.append_column(tvcolumn)
650 cell = gtk.CellRendererText()
651 cell.set_alignment(1, 0.5)
652 tvcolumn.pack_start(cell, False)
653 tvcolumn.add_attribute(cell, 'text', 6)
655 tvcolumn = gtk.TreeViewColumn('Tooltip')
656 treeview.append_column(tvcolumn)
657 cell = gtk.CellRendererText()
658 tvcolumn.pack_start(cell, False)
659 tvcolumn.add_attribute(cell, 'text', 7)
660 tvcolumn.set_visible(False)
662 scroll = gtk.ScrolledWindow()
663 scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
666 self.add_tab(scroll, 'History')
667 self.update_history_tab()
670 def create_recv_tab(self):
671 self.recv_list = gtk.ListStore(str, str, str)
672 self.add_tab( self.make_address_list(True), 'Receive')
673 self.update_receiving_tab()
675 def create_book_tab(self):
676 self.addressbook_list = gtk.ListStore(str, str, str)
677 self.add_tab( self.make_address_list(False), 'Contacts')
678 self.update_sending_tab()
680 def make_address_list(self, is_recv):
681 liststore = self.recv_list if is_recv else self.addressbook_list
682 treeview = gtk.TreeView(model= liststore)
683 treeview.connect('key-press-event', self.treeview_key_press)
686 tvcolumn = gtk.TreeViewColumn('Address')
687 treeview.append_column(tvcolumn)
688 cell = gtk.CellRendererText()
689 cell.set_property('family', 'monospace')
690 tvcolumn.pack_start(cell, True)
691 tvcolumn.add_attribute(cell, 'text', 0)
693 tvcolumn = gtk.TreeViewColumn('Label')
694 tvcolumn.set_expand(True)
695 treeview.append_column(tvcolumn)
696 cell = gtk.CellRendererText()
697 cell.set_property('editable', True)
698 def edited_cb2(cell, path, new_text, liststore):
699 address = liststore.get_value( liststore.get_iter(path), 0)
700 self.wallet.labels[address] = new_text
702 self.wallet.update_tx_labels()
703 self.update_receiving_tab()
704 self.update_sending_tab()
705 self.update_history_tab()
706 cell.connect('edited', edited_cb2, liststore)
707 tvcolumn.pack_start(cell, True)
708 tvcolumn.add_attribute(cell, 'text', 1)
710 tvcolumn = gtk.TreeViewColumn('Tx')
711 treeview.append_column(tvcolumn)
712 cell = gtk.CellRendererText()
713 tvcolumn.pack_start(cell, True)
714 tvcolumn.add_attribute(cell, 'text', 2)
716 scroll = gtk.ScrolledWindow()
717 scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
721 button = gtk.Button("New address")
722 button.connect("clicked", self.newaddress_dialog, is_recv)
724 hbox.pack_start(button,False)
726 def showqrcode(w, treeview, liststore):
727 path, col = treeview.get_cursor()
729 address = liststore.get_value(liststore.get_iter(path), 0)
730 qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H)
734 size = qr.getModuleCount()*boxsize
735 def area_expose_cb(area, event):
736 style = area.get_style()
737 k = qr.getModuleCount()
740 gc = style.black_gc if qr.isDark(r, c) else style.white_gc
741 area.window.draw_rectangle(gc, True, c*boxsize, r*boxsize, boxsize, boxsize)
742 area = gtk.DrawingArea()
743 area.set_size_request(size, size)
744 area.connect("expose-event", area_expose_cb)
746 dialog = gtk.Dialog(address, parent=self.window, flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, buttons = ("ok",1))
747 dialog.vbox.add(area)
751 button = gtk.Button("QR")
752 button.connect("clicked", showqrcode, treeview, liststore)
754 hbox.pack_start(button,False)
756 button = gtk.Button("Copy to clipboard")
757 def copy2clipboard(w, treeview, liststore):
759 path, col = treeview.get_cursor()
761 address = liststore.get_value( liststore.get_iter(path), 0)
762 if platform.system() == 'Windows':
763 from Tkinter import Tk
767 r.clipboard_append( address )
770 c = gtk.clipboard_get()
771 c.set_text( address )
772 button.connect("clicked", copy2clipboard, treeview, liststore)
774 hbox.pack_start(button,False)
777 button = gtk.Button("Pay to")
778 def payto(w, treeview, liststore):
779 path, col = treeview.get_cursor()
781 address = liststore.get_value( liststore.get_iter(path), 0)
782 self.payto_entry.set_text( address )
783 self.notebook.set_current_page(1)
784 self.payto_amount_entry.grab_focus()
786 button.connect("clicked", payto, treeview, liststore)
788 hbox.pack_start(button,False)
791 vbox.pack_start(scroll,True)
792 vbox.pack_start(hbox, False)
795 def update_status_bar(self):
796 c, u = self.wallet.get_balance()
797 dt = time.time() - self.update_time
798 if dt < 2*self.period:
799 self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
800 self.status_image.set_tooltip_text("Connected to %s.\n%d blocks\nresponse time: %f"%(self.wallet.host, self.wallet.blocks, self.wallet.rtime))
802 self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
803 self.status_image.set_tooltip_text("Trying to contact %s.\n%d blocks"%(self.wallet.host, self.wallet.blocks))
804 text = "Balance: %s "%( format_satoshis(c) )
805 if u: text += "[+ %s unconfirmed]"%( format_satoshis(u) )
806 if self.error: text = self.error
807 self.status_bar.pop(self.context_id)
808 self.status_bar.push(self.context_id, text)
810 def update_receiving_tab(self):
811 self.recv_list.clear()
812 for address in self.wallet.addresses:
813 if self.wallet.is_change(address):continue
814 label = self.wallet.labels.get(address)
816 h = self.wallet.history.get(address)
819 if not item['is_in'] : n=n+1
820 tx = "None" if n==0 else "%d"%n
821 self.recv_list.prepend((address, label, tx ))
823 def update_sending_tab(self):
824 # detect addresses that are not mine in history, add them here...
825 self.addressbook_list.clear()
826 for address in self.wallet.addressbook:
827 label = self.wallet.labels.get(address)
829 for item in self.wallet.tx_history.values():
830 if address in item['outputs'] : n=n+1
831 tx = "None" if n==0 else "%d"%n
832 self.addressbook_list.append((address, label, tx))
834 def update_history_tab(self):
835 cursor = self.history_treeview.get_cursor()[0]
836 self.history_list.clear()
838 for tx in self.wallet.get_tx_history():
839 tx_hash = tx['tx_hash']
841 conf = self.wallet.blocks - tx['height'] + 1
842 time_str = datetime.datetime.fromtimestamp( tx['nTime']).isoformat(' ')[:-3]
843 conf_icon = gtk.STOCK_APPLY
847 conf_icon = gtk.STOCK_EXECUTE
850 label = self.wallet.labels.get(tx_hash)
851 is_default_label = (label == '') or (label is None)
852 if is_default_label: label = tx['default_label']
853 tooltip = tx_hash + "\n%d confirmations"%conf
854 self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
855 ('+' if v>0 else '') + format_satoshis(v), format_satoshis(balance), tooltip] )
856 if cursor: self.history_treeview.set_cursor( cursor )
860 def newaddress_dialog(self, w, is_recv):
864 title = "New sending address"
865 dialog = gtk.Dialog(title, parent=self.window,
866 flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR,
867 buttons= ("cancel", 0, "ok",1) )
871 label_label = gtk.Label('Label:')
872 label_label.set_size_request(120,10)
874 label.pack_start(label_label)
875 label_entry = gtk.Entry()
877 label.pack_start(label_entry)
879 dialog.vbox.pack_start(label, False, True, 5)
882 address_label = gtk.Label('Address:')
883 address_label.set_size_request(120,10)
885 address.pack_start(address_label)
886 address_entry = gtk.Entry()
888 address.pack_start(address_entry)
890 dialog.vbox.pack_start(address, False, True, 5)
892 result = dialog.run()
893 address = address_entry.get_text()
894 label = label_entry.get_text()
898 if self.wallet.is_valid(address):
899 self.wallet.addressbook.append(address)
900 if label: self.wallet.labels[address] = label
902 self.update_sending_tab()
904 errorDialog = gtk.MessageDialog(
906 flags=gtk.DIALOG_MODAL,
907 buttons= gtk.BUTTONS_CLOSE,
908 message_format = "Invalid address")
911 errorDialog.destroy()
913 password = password_dialog() if self.wallet.use_encryption else None
914 success, ret = self.wallet.get_new_address(password)
915 self.wallet.new_session() # we created a new address
918 #if label: self.wallet.labels[address] = label
920 self.update_receiving_tab()
923 errorDialog = gtk.MessageDialog(
925 flags=gtk.DIALOG_MODAL,
926 buttons= gtk.BUTTONS_CLOSE,
927 message_format = msg)
930 errorDialog.destroy()