3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 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/>.
19 import sys, time, datetime, re, threading
20 from electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
32 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
33 from electrum.plugins import run_hook
37 from electrum.wallet import format_satoshis
38 from electrum import Transaction
39 from electrum import mnemonic
40 from electrum import util, bitcoin, commands, Interface, Wallet
41 from electrum import SimpleConfig, Wallet, WalletStorage
44 from electrum import bmp, pyqrnative
46 from amountedit import AmountEdit
47 from network_dialog import NetworkDialog
48 from qrcodewidget import QRCodeWidget
50 from decimal import Decimal
58 if platform.system() == 'Windows':
59 MONOSPACE_FONT = 'Lucida Console'
60 elif platform.system() == 'Darwin':
61 MONOSPACE_FONT = 'Monaco'
63 MONOSPACE_FONT = 'monospace'
65 from electrum import ELECTRUM_VERSION
75 class StatusBarButton(QPushButton):
76 def __init__(self, icon, tooltip, func):
77 QPushButton.__init__(self, icon, '')
78 self.setToolTip(tooltip)
80 self.setMaximumWidth(25)
81 self.clicked.connect(func)
83 self.setIconSize(QSize(25,25))
85 def keyPressEvent(self, e):
86 if e.key() == QtCore.Qt.Key_Return:
98 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
100 class ElectrumWindow(QMainWindow):
101 def changeEvent(self, event):
102 flags = self.windowFlags();
103 if event and event.type() == QtCore.QEvent.WindowStateChange:
104 if self.windowState() & QtCore.Qt.WindowMinimized:
105 self.build_menu(True)
106 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
107 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
108 # Electrum from closing.
109 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
110 # self.setWindowFlags(flags & ~Qt.ToolTip)
111 elif event.oldState() & QtCore.Qt.WindowMinimized:
112 self.build_menu(False)
113 #self.setWindowFlags(flags | Qt.ToolTip)
115 def build_menu(self, is_hidden = False):
117 if self.isMinimized():
118 m.addAction(_("Show"), self.showNormal)
120 m.addAction(_("Hide"), self.showMinimized)
123 m.addAction(_("Exit Electrum"), self.close)
124 self.tray.setContextMenu(m)
126 def tray_activated(self, reason):
127 if reason == QSystemTrayIcon.DoubleClick:
130 def showNormal(self):
131 self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
133 def __init__(self, config, network):
134 QMainWindow.__init__(self)
137 self.network = network
139 self._close_electrum = False
142 if sys.platform == 'darwin':
143 self.icon = QIcon(":icons/electrum_dark_icon.png")
144 #self.icon = QIcon(":icons/lock.png")
146 self.icon = QIcon(':icons/electrum_light_icon.png')
148 self.tray = QSystemTrayIcon(self.icon, self)
149 self.tray.setToolTip('Electrum')
150 self.tray.activated.connect(self.tray_activated)
154 self.create_status_bar()
156 self.need_update = threading.Event()
158 self.decimal_point = config.get('decimal_point', 8)
159 self.num_zeros = int(config.get('num_zeros',0))
161 set_language(config.get('language'))
163 self.funds_error = False
164 self.completions = QStringListModel()
166 self.tabs = tabs = QTabWidget(self)
167 self.column_widths = self.config.get("column_widths_2", default_column_widths )
168 tabs.addTab(self.create_history_tab(), _('History') )
169 tabs.addTab(self.create_send_tab(), _('Send') )
170 tabs.addTab(self.create_receive_tab(), _('Receive') )
171 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
172 tabs.addTab(self.create_console_tab(), _('Console') )
173 tabs.setMinimumSize(600, 400)
174 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
175 self.setCentralWidget(tabs)
177 g = self.config.get("winpos-qt",[100, 100, 840, 400])
178 self.setGeometry(g[0], g[1], g[2], g[3])
180 self.setWindowIcon(QIcon(":icons/electrum.png"))
183 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
184 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
185 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
186 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
187 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
189 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
190 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
191 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
193 self.history_list.setFocus(True)
197 self.network.register_callback('updated', lambda: self.need_update.set())
198 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
199 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
200 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
201 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
203 # set initial message
204 self.console.showMessage(self.network.banner)
211 self.config.set_key('lite_mode', False, True)
216 self.config.set_key('lite_mode', True, True)
223 if not self.check_qt_version():
224 if self.config.get('lite_mode') is True:
225 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
226 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
227 self.config.set_key('lite_mode', False, True)
233 actuator = lite_window.MiniActuator(self)
235 # Should probably not modify the current path but instead
236 # change the behaviour of rsrc(...)
237 old_path = QDir.currentPath()
238 actuator.load_theme()
240 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
242 driver = lite_window.MiniDriver(self, self.mini)
244 # Reset path back to original value now that loading the GUI
246 QDir.setCurrent(old_path)
248 if self.config.get('lite_mode') is True:
254 def check_qt_version(self):
255 qtVersion = qVersion()
256 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
259 def update_account_selector(self):
261 accounts = self.wallet.get_account_names()
262 self.account_selector.clear()
263 if len(accounts) > 1:
264 self.account_selector.addItems([_("All accounts")] + accounts.values())
265 self.account_selector.setCurrentIndex(0)
266 self.account_selector.show()
268 self.account_selector.hide()
271 def load_wallet(self, wallet):
274 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
275 self.current_account = self.wallet.storage.get("current_account", None)
277 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
278 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
279 self.setWindowTitle( title )
281 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
282 self.notify_transactions()
283 self.update_account_selector()
284 self.new_account.setEnabled(self.wallet.seed_version>4)
285 self.update_lock_icon()
286 self.update_buttons_on_seed()
287 self.update_console()
289 run_hook('load_wallet', wallet)
292 def open_wallet(self):
293 wallet_folder = self.wallet.storage.path
294 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
298 storage = WalletStorage({'wallet_path': filename})
299 if not storage.file_exists:
300 self.show_message("file not found "+ filename)
303 self.wallet.stop_threads()
306 wallet = Wallet(storage)
307 wallet.start_threads(self.network)
309 self.load_wallet(wallet)
313 def backup_wallet(self):
315 path = self.wallet.storage.path
316 wallet_folder = os.path.dirname(path)
317 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
321 new_path = os.path.join(wallet_folder, filename)
324 shutil.copy2(path, new_path)
325 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
326 except (IOError, os.error), reason:
327 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
330 def new_wallet(self):
333 wallet_folder = os.path.dirname(self.wallet.storage.path)
334 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
337 filename = os.path.join(wallet_folder, filename)
339 storage = WalletStorage({'wallet_path': filename})
340 if storage.file_exists:
341 QMessageBox.critical(None, "Error", _("File exists"))
344 wizard = installwizard.InstallWizard(self.config, self.network, storage)
345 wallet = wizard.run()
347 self.load_wallet(wallet)
351 def init_menubar(self):
354 file_menu = menubar.addMenu(_("&File"))
355 open_wallet_action = file_menu.addAction(_("&Open"))
356 open_wallet_action.triggered.connect(self.open_wallet)
358 new_wallet_action = file_menu.addAction(_("&Create/Restore"))
359 new_wallet_action.triggered.connect(self.new_wallet)
361 wallet_backup = file_menu.addAction(_("&Copy"))
362 wallet_backup.triggered.connect(self.backup_wallet)
364 quit_item = file_menu.addAction(_("&Close"))
365 quit_item.triggered.connect(self.close)
367 wallet_menu = menubar.addMenu(_("&Wallet"))
369 new_contact = wallet_menu.addAction(_("&New contact"))
370 new_contact.triggered.connect(self.new_contact_dialog)
372 self.new_account = wallet_menu.addAction(_("&New account"))
373 self.new_account.triggered.connect(self.new_account_dialog)
375 wallet_menu.addSeparator()
377 pw = wallet_menu.addAction(_("&Password"))
378 pw.triggered.connect(self.change_password_dialog)
380 show_seed = wallet_menu.addAction(_("&Seed"))
381 show_seed.triggered.connect(self.show_seed_dialog)
383 show_mpk = wallet_menu.addAction(_("&Master Public Key"))
384 show_mpk.triggered.connect(self.show_master_public_key)
386 wallet_menu.addSeparator()
388 labels_menu = wallet_menu.addMenu(_("&Labels"))
389 import_labels = labels_menu.addAction(_("&Import"))
390 import_labels.triggered.connect(self.do_import_labels)
391 export_labels = labels_menu.addAction(_("&Export"))
392 export_labels.triggered.connect(self.do_export_labels)
394 keys_menu = wallet_menu.addMenu(_("&Private keys"))
395 import_keys = keys_menu.addAction(_("&Import"))
396 import_keys.triggered.connect(self.do_import_privkey)
397 export_keys = keys_menu.addAction(_("&Export"))
398 export_keys.triggered.connect(self.do_export_privkeys)
400 ex_history = wallet_menu.addAction(_("&Export History"))
401 ex_history.triggered.connect(self.do_export_history)
405 tools_menu = menubar.addMenu(_("&Tools"))
407 # Settings / Preferences are all reserved keywords in OSX using this as work around
408 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
409 preferences_menu = tools_menu.addAction(preferences_name)
410 preferences_menu.triggered.connect(self.settings_dialog)
412 network = tools_menu.addAction(_("&Network"))
413 network.triggered.connect(self.run_network_dialog)
415 plugins_labels = tools_menu.addAction(_("&Plugins"))
416 plugins_labels.triggered.connect(self.plugins_dialog)
418 tools_menu.addSeparator()
420 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
422 csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
423 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
425 csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
426 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
428 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
430 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
431 raw_transaction_file.triggered.connect(self.do_process_from_file)
433 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
434 raw_transaction_text.triggered.connect(self.do_process_from_text)
437 help_menu = menubar.addMenu(_("&Help"))
438 show_about = help_menu.addAction(_("&About"))
439 show_about.triggered.connect(self.show_about)
440 web_open = help_menu.addAction(_("&Official website"))
441 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
443 help_menu.addSeparator()
444 doc_open = help_menu.addAction(_("&Documentation"))
445 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
446 report_bug = help_menu.addAction(_("&Report Bug"))
447 report_bug.triggered.connect(self.show_report_bug)
449 self.setMenuBar(menubar)
451 def show_about(self):
452 QMessageBox.about(self, "Electrum",
453 _("Version")+" %s" % (self.wallet.electrum_version) + "\n\n" + _("Electrum's focus is speed, with low resource usage and simplifying Bitcoin. You do not need to perform regular backups, because your wallet can be recovered from a secret phrase that you can memorize or write on paper. Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin system."))
455 def show_report_bug(self):
456 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
457 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
460 def notify_transactions(self):
461 if not self.network or not self.network.is_connected():
464 print_error("Notifying GUI")
465 if len(self.network.interface.pending_transactions_for_notifications) > 0:
466 # Combine the transactions if there are more then three
467 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
470 for tx in self.network.interface.pending_transactions_for_notifications:
471 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
475 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
476 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
478 self.network.interface.pending_transactions_for_notifications = []
480 for tx in self.network.interface.pending_transactions_for_notifications:
482 self.network.interface.pending_transactions_for_notifications.remove(tx)
483 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
485 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
487 def notify(self, message):
488 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
492 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
493 def getOpenFileName(self, title, filter = ""):
494 directory = self.config.get('io_dir', os.path.expanduser('~'))
495 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
496 if fileName and directory != os.path.dirname(fileName):
497 self.config.set_key('io_dir', os.path.dirname(fileName), True)
500 def getSaveFileName(self, title, filename, filter = ""):
501 directory = self.config.get('io_dir', os.path.expanduser('~'))
502 path = os.path.join( directory, filename )
503 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
504 if fileName and directory != os.path.dirname(fileName):
505 self.config.set_key('io_dir', os.path.dirname(fileName), True)
509 QMainWindow.close(self)
510 run_hook('close_main_window')
512 def connect_slots(self, sender):
513 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
514 self.previous_payto_e=''
516 def timer_actions(self):
517 if self.need_update.is_set():
519 self.need_update.clear()
520 run_hook('timer_actions')
522 def format_amount(self, x, is_diff=False, whitespaces=False):
523 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
525 def read_amount(self, x):
526 if x in['.', '']: return None
527 p = pow(10, self.decimal_point)
528 return int( p * Decimal(x) )
531 assert self.decimal_point in [5,8]
532 return "BTC" if self.decimal_point == 8 else "mBTC"
535 def update_status(self):
536 if self.network is None:
538 icon = QIcon(":icons/status_disconnected.png")
540 elif self.network.is_connected():
541 if not self.wallet.up_to_date:
542 text = _("Synchronizing...")
543 icon = QIcon(":icons/status_waiting.png")
544 elif self.network.server_lag > 1:
545 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
546 icon = QIcon(":icons/status_lagging.png")
548 c, u = self.wallet.get_account_balance(self.current_account)
549 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
550 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
553 run_hook('set_quote_text', c+u, r)
556 text += " (%s)"%quote
558 self.tray.setToolTip(text)
559 icon = QIcon(":icons/status_connected.png")
561 text = _("Not connected")
562 icon = QIcon(":icons/status_disconnected.png")
564 self.balance_label.setText(text)
565 self.status_button.setIcon( icon )
568 def update_wallet(self):
570 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
571 self.update_history_tab()
572 self.update_receive_tab()
573 self.update_contacts_tab()
574 self.update_completions()
577 def create_history_tab(self):
578 self.history_list = l = MyTreeWidget(self)
580 for i,width in enumerate(self.column_widths['history']):
581 l.setColumnWidth(i, width)
582 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
583 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
584 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
586 l.customContextMenuRequested.connect(self.create_history_menu)
590 def create_history_menu(self, position):
591 self.history_list.selectedIndexes()
592 item = self.history_list.currentItem()
594 tx_hash = str(item.data(0, Qt.UserRole).toString())
595 if not tx_hash: return
597 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
598 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
599 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
600 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
603 def show_transaction(self, tx):
604 import transaction_dialog
605 d = transaction_dialog.TxDialog(tx, self)
608 def tx_label_clicked(self, item, column):
609 if column==2 and item.isSelected():
611 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
612 self.history_list.editItem( item, column )
613 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
616 def tx_label_changed(self, item, column):
620 tx_hash = str(item.data(0, Qt.UserRole).toString())
621 tx = self.wallet.transactions.get(tx_hash)
622 text = unicode( item.text(2) )
623 self.wallet.set_label(tx_hash, text)
625 item.setForeground(2, QBrush(QColor('black')))
627 text = self.wallet.get_default_label(tx_hash)
628 item.setText(2, text)
629 item.setForeground(2, QBrush(QColor('gray')))
633 def edit_label(self, is_recv):
634 l = self.receive_list if is_recv else self.contacts_list
635 item = l.currentItem()
636 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
637 l.editItem( item, 1 )
638 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
642 def address_label_clicked(self, item, column, l, column_addr, column_label):
643 if column == column_label and item.isSelected():
644 is_editable = item.data(0, 32).toBool()
647 addr = unicode( item.text(column_addr) )
648 label = unicode( item.text(column_label) )
649 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
650 l.editItem( item, column )
651 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
654 def address_label_changed(self, item, column, l, column_addr, column_label):
655 if column == column_label:
656 addr = unicode( item.text(column_addr) )
657 text = unicode( item.text(column_label) )
658 is_editable = item.data(0, 32).toBool()
662 changed = self.wallet.set_label(addr, text)
664 self.update_history_tab()
665 self.update_completions()
667 self.current_item_changed(item)
669 run_hook('item_changed', item, column)
672 def current_item_changed(self, a):
673 run_hook('current_item_changed', a)
676 self.tabs.emit(SIGNAL('currentChanged(int)'), 1)
680 def update_history_tab(self):
682 self.history_list.clear()
683 for item in self.wallet.get_tx_history(self.current_account):
684 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
685 time_str = _("unknown")
688 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
690 time_str = _("error")
693 time_str = 'unverified'
694 icon = QIcon(":icons/unconfirmed.png")
697 icon = QIcon(":icons/unconfirmed.png")
699 icon = QIcon(":icons/clock%d.png"%conf)
701 icon = QIcon(":icons/confirmed.png")
703 if value is not None:
704 v_str = self.format_amount(value, True, whitespaces=True)
708 balance_str = self.format_amount(balance, whitespaces=True)
711 label, is_default_label = self.wallet.get_label(tx_hash)
713 label = _('Pruned transaction outputs')
714 is_default_label = False
716 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
717 item.setFont(2, QFont(MONOSPACE_FONT))
718 item.setFont(3, QFont(MONOSPACE_FONT))
719 item.setFont(4, QFont(MONOSPACE_FONT))
721 item.setForeground(3, QBrush(QColor("#BC1E1E")))
723 item.setData(0, Qt.UserRole, tx_hash)
724 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
726 item.setForeground(2, QBrush(QColor('grey')))
728 item.setIcon(0, icon)
729 self.history_list.insertTopLevelItem(0,item)
732 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
735 def create_send_tab(self):
740 grid.setColumnMinimumWidth(3,300)
741 grid.setColumnStretch(5,1)
744 self.payto_e = QLineEdit()
745 grid.addWidget(QLabel(_('Pay to')), 1, 0)
746 grid.addWidget(self.payto_e, 1, 1, 1, 3)
748 grid.addWidget(HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')), 1, 4)
750 completer = QCompleter()
751 completer.setCaseSensitivity(False)
752 self.payto_e.setCompleter(completer)
753 completer.setModel(self.completions)
755 self.message_e = QLineEdit()
756 grid.addWidget(QLabel(_('Description')), 2, 0)
757 grid.addWidget(self.message_e, 2, 1, 1, 3)
758 grid.addWidget(HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')), 2, 4)
761 self.from_label = QLabel(_('From'))
762 grid.addWidget(self.from_label, 3, 0)
763 self.from_list = QTreeWidget(self)
764 self.from_list.setColumnCount(2)
765 self.from_list.setColumnWidth(0, 350)
766 self.from_list.setColumnWidth(1, 50)
767 self.from_list.setHeaderHidden (True)
768 self.from_list.setMaximumHeight(80)
769 grid.addWidget(self.from_list, 3, 1, 1, 3)
770 self.connect(self.tabs, SIGNAL('currentChanged(int)'), lambda: self.update_pay_from_list(grid))
772 self.amount_e = AmountEdit(self.base_unit)
773 grid.addWidget(QLabel(_('Amount')), 4, 0)
774 grid.addWidget(self.amount_e, 4, 1, 1, 2)
775 grid.addWidget(HelpButton(
776 _('Amount to be sent.') + '\n\n' \
777 + _('The amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') \
778 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
780 self.fee_e = AmountEdit(self.base_unit)
781 grid.addWidget(QLabel(_('Fee')), 5, 0)
782 grid.addWidget(self.fee_e, 5, 1, 1, 2)
783 grid.addWidget(HelpButton(
784 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
785 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
786 + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 5, 3)
789 self.send_button = EnterButton(_("Send"), self.do_send)
790 grid.addWidget(self.send_button, 6, 1)
792 b = EnterButton(_("Clear"),self.do_clear)
793 grid.addWidget(b, 6, 2)
795 self.payto_sig = QLabel('')
796 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
798 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
799 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
808 def entry_changed( is_fee ):
809 self.funds_error = False
811 if self.amount_e.is_shortcut:
812 self.amount_e.is_shortcut = False
813 sendable = self.get_sendable_balance()
814 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, self.get_payment_sources())
815 fee = self.wallet.estimated_fee(inputs)
817 self.amount_e.setText( self.format_amount(amount) )
818 self.fee_e.setText( self.format_amount( fee ) )
821 amount = self.read_amount(str(self.amount_e.text()))
822 fee = self.read_amount(str(self.fee_e.text()))
824 if not is_fee: fee = None
827 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, self.get_payment_sources())
829 self.fee_e.setText( self.format_amount( fee ) )
832 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
836 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
837 self.funds_error = True
838 text = _( "Not enough funds" )
839 c, u = self.wallet.get_frozen_balance()
840 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
842 self.statusBar().showMessage(text)
843 self.amount_e.setPalette(palette)
844 self.fee_e.setPalette(palette)
846 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
847 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
849 run_hook('create_send_tab', grid)
853 def update_pay_from_list(self, grid):
854 self.from_list.clear()
855 self.from_label.setHidden(len(self.pay_from) == 0)
856 self.from_list.setHidden(len(self.pay_from) == 0)
857 for addr in self.pay_from:
858 c, u = self.wallet.get_addr_balance(addr)
859 balance = self.format_amount(c + u)
860 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
863 def update_completions(self):
865 for addr,label in self.wallet.labels.items():
866 if addr in self.wallet.addressbook:
867 l.append( label + ' <' + addr + '>')
869 run_hook('update_completions', l)
870 self.completions.setStringList(l)
874 return lambda s, *args: s.do_protect(func, args)
879 label = unicode( self.message_e.text() )
880 r = unicode( self.payto_e.text() )
883 # label or alias, with address in brackets
884 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
885 to_address = m.group(2) if m else r
887 if not is_valid(to_address):
888 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
892 amount = self.read_amount(unicode( self.amount_e.text()))
894 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
897 fee = self.read_amount(unicode( self.fee_e.text()))
899 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
902 confirm_amount = self.config.get('confirm_amount', 100000000)
903 if amount >= confirm_amount:
904 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
907 self.send_tx(to_address, amount, fee, label)
911 def send_tx(self, to_address, amount, fee, label, password):
913 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
914 domain=self.get_payment_sources())
915 except Exception as e:
916 traceback.print_exc(file=sys.stdout)
917 self.show_message(str(e))
920 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
921 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
925 self.wallet.set_label(tx.hash(), label)
928 h = self.wallet.send_tx(tx)
929 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
930 status, msg = self.wallet.receive_tx( h )
932 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
934 self.update_contacts_tab()
936 QMessageBox.warning(self, _('Error'), msg, _('OK'))
939 self.show_transaction(tx)
941 # add recipient to addressbook
942 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
943 self.wallet.addressbook.append(to_address)
948 def set_url(self, url):
949 address, amount, label, message, signature, identity, url = util.parse_url(url)
952 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
953 elif amount: amount = str(Decimal(amount))
956 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
959 self.mini.set_payment_fields(address, amount)
961 if label and self.wallet.labels.get(address) != label:
962 if self.question('Give label "%s" to address %s ?'%(label,address)):
963 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
964 self.wallet.addressbook.append(address)
965 self.wallet.set_label(address, label)
967 run_hook('set_url', url, self.show_message, self.question)
969 self.tabs.setCurrentIndex(1)
970 label = self.wallet.labels.get(address)
971 m_addr = label + ' <'+ address +'>' if label else address
972 self.payto_e.setText(m_addr)
974 self.message_e.setText(message)
976 self.amount_e.setText(amount)
979 self.set_frozen(self.payto_e,True)
980 self.set_frozen(self.amount_e,True)
981 self.set_frozen(self.message_e,True)
982 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
984 self.payto_sig.setVisible(False)
987 self.payto_sig.setVisible(False)
988 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
990 self.set_frozen(e,False)
993 self.tabs.emit(SIGNAL('currentChanged(int)'), 1)
997 def set_frozen(self,entry,frozen):
999 entry.setReadOnly(True)
1000 entry.setFrame(False)
1001 palette = QPalette()
1002 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1003 entry.setPalette(palette)
1005 entry.setReadOnly(False)
1006 entry.setFrame(True)
1007 palette = QPalette()
1008 palette.setColor(entry.backgroundRole(), QColor('white'))
1009 entry.setPalette(palette)
1012 def set_addrs_frozen(self,addrs,freeze):
1014 if not addr: continue
1015 if addr in self.wallet.frozen_addresses and not freeze:
1016 self.wallet.unfreeze(addr)
1017 elif addr not in self.wallet.frozen_addresses and freeze:
1018 self.wallet.freeze(addr)
1019 self.update_receive_tab()
1023 def create_list_tab(self, headers):
1024 "generic tab creation method"
1025 l = MyTreeWidget(self)
1026 l.setColumnCount( len(headers) )
1027 l.setHeaderLabels( headers )
1030 vbox = QVBoxLayout()
1037 vbox.addWidget(buttons)
1039 hbox = QHBoxLayout()
1042 buttons.setLayout(hbox)
1047 def create_receive_tab(self):
1048 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1049 l.setContextMenuPolicy(Qt.CustomContextMenu)
1050 l.customContextMenuRequested.connect(self.create_receive_menu)
1051 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1052 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1053 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1054 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1055 self.receive_list = l
1056 self.receive_buttons_hbox = hbox
1063 def save_column_widths(self):
1064 self.column_widths["receive"] = []
1065 for i in range(self.receive_list.columnCount() -1):
1066 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1068 self.column_widths["history"] = []
1069 for i in range(self.history_list.columnCount() - 1):
1070 self.column_widths["history"].append(self.history_list.columnWidth(i))
1072 self.column_widths["contacts"] = []
1073 for i in range(self.contacts_list.columnCount() - 1):
1074 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1076 self.config.set_key("column_widths_2", self.column_widths, True)
1079 def create_contacts_tab(self):
1080 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1081 l.setContextMenuPolicy(Qt.CustomContextMenu)
1082 l.customContextMenuRequested.connect(self.create_contact_menu)
1083 for i,width in enumerate(self.column_widths['contacts']):
1084 l.setColumnWidth(i, width)
1086 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1087 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1088 self.contacts_list = l
1089 self.contacts_buttons_hbox = hbox
1094 def delete_imported_key(self, addr):
1095 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1096 self.wallet.delete_imported_key(addr)
1097 self.update_receive_tab()
1098 self.update_history_tab()
1100 def edit_account_label(self, k):
1101 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1103 label = unicode(text)
1104 self.wallet.set_label(k,label)
1105 self.update_receive_tab()
1107 def account_set_expanded(self, item, k, b):
1109 self.accounts_expanded[k] = b
1111 def create_account_menu(self, position, k, item):
1113 if item.isExpanded():
1114 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1116 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1117 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1118 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1119 if self.wallet.account_is_pending(k):
1120 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1121 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1123 def delete_pending_account(self, k):
1124 self.wallet.delete_pending_account(k)
1125 self.update_receive_tab()
1127 def create_receive_menu(self, position):
1128 # fixme: this function apparently has a side effect.
1129 # if it is not called the menu pops up several times
1130 #self.receive_list.selectedIndexes()
1132 selected = self.receive_list.selectedItems()
1133 multi_select = len(selected) > 1
1134 addrs = [unicode(item.text(0)) for item in selected]
1135 if not multi_select:
1136 item = self.receive_list.itemAt(position)
1140 if not is_valid(addr):
1141 k = str(item.data(0,32).toString())
1143 self.create_account_menu(position, k, item)
1145 item.setExpanded(not item.isExpanded())
1149 if not multi_select:
1150 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1151 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1152 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1153 if self.wallet.seed:
1154 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1155 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1156 if addr in self.wallet.imported_keys:
1157 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1159 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1160 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1161 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1162 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1164 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1166 run_hook('receive_menu', menu, addrs)
1167 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1170 def get_sendable_balance(self):
1171 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1174 def get_payment_sources(self):
1176 return self.pay_from
1178 return self.wallet.get_account_addresses(self.current_account)
1181 def send_from_addresses(self, addrs):
1182 self.pay_from = addrs[:]
1183 self.tabs.setCurrentIndex(1)
1186 def payto(self, addr):
1188 label = self.wallet.labels.get(addr)
1189 m_addr = label + ' <' + addr + '>' if label else addr
1190 self.tabs.setCurrentIndex(1)
1191 self.payto_e.setText(m_addr)
1192 self.amount_e.setFocus()
1195 def delete_contact(self, x):
1196 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1197 self.wallet.delete_contact(x)
1198 self.wallet.set_label(x, None)
1199 self.update_history_tab()
1200 self.update_contacts_tab()
1201 self.update_completions()
1204 def create_contact_menu(self, position):
1205 item = self.contacts_list.itemAt(position)
1207 addr = unicode(item.text(0))
1208 label = unicode(item.text(1))
1209 is_editable = item.data(0,32).toBool()
1210 payto_addr = item.data(0,33).toString()
1212 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1213 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1214 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1216 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1217 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1219 run_hook('create_contact_menu', menu, item)
1220 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1223 def update_receive_item(self, item):
1224 item.setFont(0, QFont(MONOSPACE_FONT))
1225 address = str(item.data(0,0).toString())
1226 label = self.wallet.labels.get(address,'')
1227 item.setData(1,0,label)
1228 item.setData(0,32, True) # is editable
1230 run_hook('update_receive_item', address, item)
1232 if not self.wallet.is_mine(address): return
1234 c, u = self.wallet.get_addr_balance(address)
1235 balance = self.format_amount(c + u)
1236 item.setData(2,0,balance)
1238 if address in self.wallet.frozen_addresses:
1239 item.setBackgroundColor(0, QColor('lightblue'))
1242 def update_receive_tab(self):
1243 l = self.receive_list
1246 l.setColumnHidden(2, False)
1247 l.setColumnHidden(3, False)
1248 for i,width in enumerate(self.column_widths['receive']):
1249 l.setColumnWidth(i, width)
1251 if self.current_account is None:
1252 account_items = self.wallet.accounts.items()
1253 elif self.current_account != -1:
1254 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1258 for k, account in account_items:
1259 name = self.wallet.get_account_name(k)
1260 c,u = self.wallet.get_account_balance(k)
1261 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1262 l.addTopLevelItem(account_item)
1263 account_item.setExpanded(self.accounts_expanded.get(k, True))
1264 account_item.setData(0, 32, k)
1266 if not self.wallet.is_seeded(k):
1267 icon = QIcon(":icons/key.png")
1268 account_item.setIcon(0, icon)
1270 for is_change in ([0,1]):
1271 name = _("Receiving") if not is_change else _("Change")
1272 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1273 account_item.addChild(seq_item)
1274 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1276 if not is_change: seq_item.setExpanded(True)
1281 for address in account.get_addresses(is_change):
1282 h = self.wallet.history.get(address,[])
1286 if gap > self.wallet.gap_limit:
1291 c, u = self.wallet.get_addr_balance(address)
1292 num_tx = '*' if h == ['*'] else "%d"%len(h)
1293 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1294 self.update_receive_item(item)
1296 item.setBackgroundColor(1, QColor('red'))
1297 if len(h) > 0 and c == -u:
1299 seq_item.addChild(used_item)
1301 used_item.addChild(item)
1303 seq_item.addChild(item)
1306 for k, addr in self.wallet.get_pending_accounts():
1307 name = self.wallet.labels.get(k,'')
1308 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1309 self.update_receive_item(item)
1310 l.addTopLevelItem(account_item)
1311 account_item.setExpanded(True)
1312 account_item.setData(0, 32, k)
1313 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1314 account_item.addChild(item)
1315 self.update_receive_item(item)
1318 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1319 c,u = self.wallet.get_imported_balance()
1320 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1321 l.addTopLevelItem(account_item)
1322 account_item.setExpanded(True)
1323 for address in self.wallet.imported_keys.keys():
1324 item = QTreeWidgetItem( [ address, '', '', ''] )
1325 self.update_receive_item(item)
1326 account_item.addChild(item)
1329 # we use column 1 because column 0 may be hidden
1330 l.setCurrentItem(l.topLevelItem(0),1)
1333 def update_contacts_tab(self):
1334 l = self.contacts_list
1337 for address in self.wallet.addressbook:
1338 label = self.wallet.labels.get(address,'')
1339 n = self.wallet.get_num_tx(address)
1340 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1341 item.setFont(0, QFont(MONOSPACE_FONT))
1342 # 32 = label can be edited (bool)
1343 item.setData(0,32, True)
1345 item.setData(0,33, address)
1346 l.addTopLevelItem(item)
1348 run_hook('update_contacts_tab', l)
1349 l.setCurrentItem(l.topLevelItem(0))
1353 def create_console_tab(self):
1354 from console import Console
1355 self.console = console = Console()
1359 def update_console(self):
1360 console = self.console
1361 console.history = self.config.get("console-history",[])
1362 console.history_index = len(console.history)
1364 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1365 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1367 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1369 def mkfunc(f, method):
1370 return lambda *args: apply( f, (method, args, self.password_dialog ))
1372 if m[0]=='_' or m in ['network','wallet']: continue
1373 methods[m] = mkfunc(c._run, m)
1375 console.updateNamespace(methods)
1378 def change_account(self,s):
1379 if s == _("All accounts"):
1380 self.current_account = None
1382 accounts = self.wallet.get_account_names()
1383 for k, v in accounts.items():
1385 self.current_account = k
1386 self.update_history_tab()
1387 self.update_status()
1388 self.update_receive_tab()
1390 def create_status_bar(self):
1393 sb.setFixedHeight(35)
1394 qtVersion = qVersion()
1396 self.balance_label = QLabel("")
1397 sb.addWidget(self.balance_label)
1399 from version_getter import UpdateLabel
1400 self.updatelabel = UpdateLabel(self.config, sb)
1402 self.account_selector = QComboBox()
1403 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1404 sb.addPermanentWidget(self.account_selector)
1406 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1407 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1409 self.lock_icon = QIcon()
1410 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1411 sb.addPermanentWidget( self.password_button )
1413 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1414 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1415 sb.addPermanentWidget( self.seed_button )
1416 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1417 sb.addPermanentWidget( self.status_button )
1419 run_hook('create_status_bar', (sb,))
1421 self.setStatusBar(sb)
1424 def update_lock_icon(self):
1425 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1426 self.password_button.setIcon( icon )
1429 def update_buttons_on_seed(self):
1430 if not self.wallet.is_watching_only():
1431 self.seed_button.show()
1432 self.password_button.show()
1433 self.send_button.setText(_("Send"))
1435 self.password_button.hide()
1436 self.seed_button.hide()
1437 self.send_button.setText(_("Create unsigned transaction"))
1440 def change_password_dialog(self):
1441 from password_dialog import PasswordDialog
1442 d = PasswordDialog(self.wallet, self)
1444 self.update_lock_icon()
1447 def new_contact_dialog(self):
1450 vbox = QVBoxLayout(d)
1451 vbox.addWidget(QLabel(_('New Contact')+':'))
1453 grid = QGridLayout()
1456 grid.addWidget(QLabel(_("Address")), 1, 0)
1457 grid.addWidget(line1, 1, 1)
1458 grid.addWidget(QLabel(_("Name")), 2, 0)
1459 grid.addWidget(line2, 2, 1)
1461 vbox.addLayout(grid)
1462 vbox.addLayout(ok_cancel_buttons(d))
1467 address = str(line1.text())
1468 label = unicode(line2.text())
1470 if not is_valid(address):
1471 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1474 self.wallet.add_contact(address)
1476 self.wallet.set_label(address, label)
1478 self.update_contacts_tab()
1479 self.update_history_tab()
1480 self.update_completions()
1481 self.tabs.setCurrentIndex(3)
1484 def new_account_dialog(self):
1486 dialog = QDialog(self)
1488 dialog.setWindowTitle(_("New Account"))
1490 vbox = QVBoxLayout()
1491 vbox.addWidget(QLabel(_('Account name')+':'))
1494 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1495 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1500 vbox.addLayout(ok_cancel_buttons(dialog))
1501 dialog.setLayout(vbox)
1505 name = str(e.text())
1508 self.wallet.create_pending_account('1', name)
1509 self.update_receive_tab()
1510 self.tabs.setCurrentIndex(2)
1514 def show_master_public_key_old(self):
1515 dialog = QDialog(self)
1517 dialog.setWindowTitle(_("Master Public Key"))
1519 main_text = QTextEdit()
1520 main_text.setText(self.wallet.get_master_public_key())
1521 main_text.setReadOnly(True)
1522 main_text.setMaximumHeight(170)
1523 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1525 ok_button = QPushButton(_("OK"))
1526 ok_button.setDefault(True)
1527 ok_button.clicked.connect(dialog.accept)
1529 main_layout = QGridLayout()
1530 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1532 main_layout.addWidget(main_text, 1, 0)
1533 main_layout.addWidget(qrw, 1, 1 )
1535 vbox = QVBoxLayout()
1536 vbox.addLayout(main_layout)
1537 vbox.addLayout(close_button(dialog))
1538 dialog.setLayout(vbox)
1542 def show_master_public_key(self):
1544 if self.wallet.seed_version == 4:
1545 self.show_master_public_key_old()
1548 dialog = QDialog(self)
1550 dialog.setWindowTitle(_("Master Public Keys"))
1552 chain_text = QTextEdit()
1553 chain_text.setReadOnly(True)
1554 chain_text.setMaximumHeight(170)
1555 chain_qrw = QRCodeWidget()
1557 mpk_text = QTextEdit()
1558 mpk_text.setReadOnly(True)
1559 mpk_text.setMaximumHeight(170)
1560 mpk_qrw = QRCodeWidget()
1562 main_layout = QGridLayout()
1564 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1565 main_layout.addWidget(mpk_text, 1, 1)
1566 main_layout.addWidget(mpk_qrw, 1, 2)
1568 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1569 main_layout.addWidget(chain_text, 2, 1)
1570 main_layout.addWidget(chain_qrw, 2, 2)
1573 c, K, cK = self.wallet.master_public_keys[str(key)]
1574 chain_text.setText(c)
1575 chain_qrw.set_addr(c)
1576 chain_qrw.update_qr()
1581 key_selector = QComboBox()
1582 keys = sorted(self.wallet.master_public_keys.keys())
1583 key_selector.addItems(keys)
1585 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1586 main_layout.addWidget(key_selector, 0, 1)
1587 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1591 vbox = QVBoxLayout()
1592 vbox.addLayout(main_layout)
1593 vbox.addLayout(close_button(dialog))
1595 dialog.setLayout(vbox)
1600 def show_seed_dialog(self, password):
1601 if self.wallet.is_watching_only():
1602 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1605 if self.wallet.seed:
1607 mnemonic = self.wallet.get_mnemonic(password)
1609 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1611 from seed_dialog import SeedDialog
1612 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1616 for k in self.wallet.master_private_keys.keys():
1617 pk = self.wallet.get_master_private_key(k, password)
1619 from seed_dialog import PrivateKeysDialog
1620 d = PrivateKeysDialog(self,l)
1627 def show_qrcode(self, data, title = _("QR code")):
1631 d.setWindowTitle(title)
1632 d.setMinimumSize(270, 300)
1633 vbox = QVBoxLayout()
1634 qrw = QRCodeWidget(data)
1635 vbox.addWidget(qrw, 1)
1636 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1637 hbox = QHBoxLayout()
1640 filename = os.path.join(self.config.path, "qrcode.bmp")
1643 bmp.save_qrcode(qrw.qr, filename)
1644 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1646 def copy_to_clipboard():
1647 bmp.save_qrcode(qrw.qr, filename)
1648 self.app.clipboard().setImage(QImage(filename))
1649 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1651 b = QPushButton(_("Copy"))
1653 b.clicked.connect(copy_to_clipboard)
1655 b = QPushButton(_("Save"))
1657 b.clicked.connect(print_qr)
1659 b = QPushButton(_("Close"))
1661 b.clicked.connect(d.accept)
1664 vbox.addLayout(hbox)
1669 def do_protect(self, func, args):
1670 if self.wallet.use_encryption:
1671 password = self.password_dialog()
1677 if args != (False,):
1678 args = (self,) + args + (password,)
1680 args = (self,password)
1685 def show_private_key(self, address, password):
1686 if not address: return
1688 pk_list = self.wallet.get_private_key(address, password)
1689 except Exception as e:
1690 self.show_message(str(e))
1692 QMessageBox.information(self, _('Private key'), _('Address')+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1696 def do_sign(self, address, message, signature, password):
1697 message = unicode(message.toPlainText())
1698 message = message.encode('utf-8')
1700 sig = self.wallet.sign_message(str(address.text()), message, password)
1701 signature.setText(sig)
1702 except Exception as e:
1703 self.show_message(str(e))
1705 def sign_message(self, address):
1706 if not address: return
1709 d.setWindowTitle(_('Sign Message'))
1710 d.setMinimumSize(410, 290)
1712 tab_widget = QTabWidget()
1714 layout = QGridLayout(tab)
1716 sign_address = QLineEdit()
1718 sign_address.setText(address)
1719 layout.addWidget(QLabel(_('Address')), 1, 0)
1720 layout.addWidget(sign_address, 1, 1)
1722 sign_message = QTextEdit()
1723 layout.addWidget(QLabel(_('Message')), 2, 0)
1724 layout.addWidget(sign_message, 2, 1)
1725 layout.setRowStretch(2,3)
1727 sign_signature = QTextEdit()
1728 layout.addWidget(QLabel(_('Signature')), 3, 0)
1729 layout.addWidget(sign_signature, 3, 1)
1730 layout.setRowStretch(3,1)
1733 hbox = QHBoxLayout()
1734 b = QPushButton(_("Sign"))
1736 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1737 b = QPushButton(_("Close"))
1738 b.clicked.connect(d.accept)
1740 layout.addLayout(hbox, 4, 1)
1741 tab_widget.addTab(tab, _("Sign"))
1745 layout = QGridLayout(tab)
1747 verify_address = QLineEdit()
1748 layout.addWidget(QLabel(_('Address')), 1, 0)
1749 layout.addWidget(verify_address, 1, 1)
1751 verify_message = QTextEdit()
1752 layout.addWidget(QLabel(_('Message')), 2, 0)
1753 layout.addWidget(verify_message, 2, 1)
1754 layout.setRowStretch(2,3)
1756 verify_signature = QTextEdit()
1757 layout.addWidget(QLabel(_('Signature')), 3, 0)
1758 layout.addWidget(verify_signature, 3, 1)
1759 layout.setRowStretch(3,1)
1762 message = unicode(verify_message.toPlainText())
1763 message = message.encode('utf-8')
1764 if bitcoin.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1765 self.show_message(_("Signature verified"))
1767 self.show_message(_("Error: wrong signature"))
1769 hbox = QHBoxLayout()
1770 b = QPushButton(_("Verify"))
1771 b.clicked.connect(do_verify)
1773 b = QPushButton(_("Close"))
1774 b.clicked.connect(d.accept)
1776 layout.addLayout(hbox, 4, 1)
1777 tab_widget.addTab(tab, _("Verify"))
1779 vbox = QVBoxLayout()
1780 vbox.addWidget(tab_widget)
1787 def question(self, msg):
1788 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1790 def show_message(self, msg):
1791 QMessageBox.information(self, _('Message'), msg, _('OK'))
1793 def password_dialog(self ):
1800 vbox = QVBoxLayout()
1801 msg = _('Please enter your password')
1802 vbox.addWidget(QLabel(msg))
1804 grid = QGridLayout()
1806 grid.addWidget(QLabel(_('Password')), 1, 0)
1807 grid.addWidget(pw, 1, 1)
1808 vbox.addLayout(grid)
1810 vbox.addLayout(ok_cancel_buttons(d))
1813 run_hook('password_dialog', pw, grid, 1)
1814 if not d.exec_(): return
1815 return unicode(pw.text())
1824 def tx_from_text(self, txt):
1825 "json or raw hexadecimal"
1828 tx = Transaction(txt)
1834 tx_dict = json.loads(str(txt))
1835 assert "hex" in tx_dict.keys()
1836 assert "complete" in tx_dict.keys()
1837 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1838 if not tx_dict["complete"]:
1839 assert "input_info" in tx_dict.keys()
1840 input_info = json.loads(tx_dict['input_info'])
1841 tx.add_input_info(input_info)
1846 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1850 def read_tx_from_file(self):
1851 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1855 with open(fileName, "r") as f:
1856 file_content = f.read()
1857 except (ValueError, IOError, os.error), reason:
1858 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1860 return self.tx_from_text(file_content)
1864 def sign_raw_transaction(self, tx, input_info, password):
1865 self.wallet.signrawtransaction(tx, input_info, [], password)
1867 def do_process_from_text(self):
1868 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1871 tx = self.tx_from_text(text)
1873 self.show_transaction(tx)
1875 def do_process_from_file(self):
1876 tx = self.read_tx_from_file()
1878 self.show_transaction(tx)
1880 def do_process_from_csvReader(self, csvReader):
1883 for row in csvReader:
1885 amount = Decimal(row[1])
1886 amount = int(100000000*amount)
1887 outputs.append((address, amount))
1888 except (ValueError, IOError, os.error), reason:
1889 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1893 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1894 except Exception as e:
1895 self.show_message(str(e))
1898 self.show_transaction(tx)
1900 def do_process_from_csv_file(self):
1901 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1905 with open(fileName, "r") as f:
1906 csvReader = csv.reader(f)
1907 self.do_process_from_csvReader(csvReader)
1908 except (ValueError, IOError, os.error), reason:
1909 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1912 def do_process_from_csv_text(self):
1913 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1914 + _("Format: address, amount. One output per line"), _("Load CSV"))
1917 f = StringIO.StringIO(text)
1918 csvReader = csv.reader(f)
1919 self.do_process_from_csvReader(csvReader)
1924 def do_export_privkeys(self, password):
1925 self.show_message("%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."), _("Exposing a single private key can compromise your entire wallet!"), _("In particular, DO NOT use 'redeem private key' services proposed by third parties.")))
1928 select_export = _('Select file to export your private keys to')
1929 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1931 with open(fileName, "w+") as csvfile:
1932 transaction = csv.writer(csvfile)
1933 transaction.writerow(["address", "private_key"])
1935 addresses = self.wallet.addresses(True)
1937 for addr in addresses:
1938 pk = "".join(self.wallet.get_private_key(addr, password))
1939 transaction.writerow(["%34s"%addr,pk])
1941 self.show_message(_("Private keys exported."))
1943 except (IOError, os.error), reason:
1944 export_error_label = _("Electrum was unable to produce a private key-export.")
1945 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1947 except Exception as e:
1948 self.show_message(str(e))
1952 def do_import_labels(self):
1953 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1954 if not labelsFile: return
1956 f = open(labelsFile, 'r')
1959 for key, value in json.loads(data).items():
1960 self.wallet.set_label(key, value)
1961 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1962 except (IOError, os.error), reason:
1963 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1966 def do_export_labels(self):
1967 labels = self.wallet.labels
1969 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1971 with open(fileName, 'w+') as f:
1972 json.dump(labels, f)
1973 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1974 except (IOError, os.error), reason:
1975 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1978 def do_export_history(self):
1979 from lite_window import csv_transaction
1980 csv_transaction(self.wallet)
1984 def do_import_privkey(self, password):
1985 if not self.wallet.imported_keys:
1986 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1987 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1988 + _('Are you sure you understand what you are doing?'), 3, 4)
1991 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1994 text = str(text).split()
1999 addr = self.wallet.import_key(key, password)
2000 except Exception as e:
2006 addrlist.append(addr)
2008 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2010 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2011 self.update_receive_tab()
2012 self.update_history_tab()
2015 def settings_dialog(self):
2017 d.setWindowTitle(_('Electrum Settings'))
2019 vbox = QVBoxLayout()
2020 grid = QGridLayout()
2021 grid.setColumnStretch(0,1)
2023 nz_label = QLabel(_('Display zeros') + ':')
2024 grid.addWidget(nz_label, 0, 0)
2025 nz_e = AmountEdit(None,True)
2026 nz_e.setText("%d"% self.num_zeros)
2027 grid.addWidget(nz_e, 0, 1)
2028 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2029 grid.addWidget(HelpButton(msg), 0, 2)
2030 if not self.config.is_modifiable('num_zeros'):
2031 for w in [nz_e, nz_label]: w.setEnabled(False)
2033 lang_label=QLabel(_('Language') + ':')
2034 grid.addWidget(lang_label, 1, 0)
2035 lang_combo = QComboBox()
2036 from electrum.i18n import languages
2037 lang_combo.addItems(languages.values())
2039 index = languages.keys().index(self.config.get("language",''))
2042 lang_combo.setCurrentIndex(index)
2043 grid.addWidget(lang_combo, 1, 1)
2044 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2045 if not self.config.is_modifiable('language'):
2046 for w in [lang_combo, lang_label]: w.setEnabled(False)
2049 fee_label = QLabel(_('Transaction fee') + ':')
2050 grid.addWidget(fee_label, 2, 0)
2051 fee_e = AmountEdit(self.base_unit)
2052 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2053 grid.addWidget(fee_e, 2, 1)
2054 msg = _('Fee per kilobyte of transaction.') + ' ' \
2055 + _('Recommended value') + ': ' + self.format_amount(20000)
2056 grid.addWidget(HelpButton(msg), 2, 2)
2057 if not self.config.is_modifiable('fee_per_kb'):
2058 for w in [fee_e, fee_label]: w.setEnabled(False)
2060 units = ['BTC', 'mBTC']
2061 unit_label = QLabel(_('Base unit') + ':')
2062 grid.addWidget(unit_label, 3, 0)
2063 unit_combo = QComboBox()
2064 unit_combo.addItems(units)
2065 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2066 grid.addWidget(unit_combo, 3, 1)
2067 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2068 + '\n1BTC=1000mBTC.\n' \
2069 + _(' This settings affects the fields in the Send tab')+' '), 3, 2)
2071 usechange_cb = QCheckBox(_('Use change addresses'))
2072 usechange_cb.setChecked(self.wallet.use_change)
2073 grid.addWidget(usechange_cb, 4, 0)
2074 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2075 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2077 grid.setRowStretch(5,1)
2079 vbox.addLayout(grid)
2080 vbox.addLayout(ok_cancel_buttons(d))
2084 if not d.exec_(): return
2086 fee = unicode(fee_e.text())
2088 fee = self.read_amount(fee)
2090 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2093 self.wallet.set_fee(fee)
2095 nz = unicode(nz_e.text())
2100 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2103 if self.num_zeros != nz:
2105 self.config.set_key('num_zeros', nz, True)
2106 self.update_history_tab()
2107 self.update_receive_tab()
2109 usechange_result = usechange_cb.isChecked()
2110 if self.wallet.use_change != usechange_result:
2111 self.wallet.use_change = usechange_result
2112 self.wallet.storage.put('use_change', self.wallet.use_change)
2114 unit_result = units[unit_combo.currentIndex()]
2115 if self.base_unit() != unit_result:
2116 self.decimal_point = 8 if unit_result == 'BTC' else 5
2117 self.config.set_key('decimal_point', self.decimal_point, True)
2118 self.update_history_tab()
2119 self.update_status()
2121 need_restart = False
2123 lang_request = languages.keys()[lang_combo.currentIndex()]
2124 if lang_request != self.config.get('language'):
2125 self.config.set_key("language", lang_request, True)
2128 run_hook('close_settings_dialog')
2131 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2134 def run_network_dialog(self):
2135 if not self.network:
2137 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2139 def closeEvent(self, event):
2142 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2143 self.save_column_widths()
2144 self.config.set_key("console-history", self.console.history[-50:], True)
2145 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2150 def plugins_dialog(self):
2151 from electrum.plugins import plugins
2154 d.setWindowTitle(_('Electrum Plugins'))
2157 vbox = QVBoxLayout(d)
2160 scroll = QScrollArea()
2161 scroll.setEnabled(True)
2162 scroll.setWidgetResizable(True)
2163 scroll.setMinimumSize(400,250)
2164 vbox.addWidget(scroll)
2168 w.setMinimumHeight(len(plugins)*35)
2170 grid = QGridLayout()
2171 grid.setColumnStretch(0,1)
2174 def do_toggle(cb, p, w):
2177 if w: w.setEnabled(r)
2179 def mk_toggle(cb, p, w):
2180 return lambda: do_toggle(cb,p,w)
2182 for i, p in enumerate(plugins):
2184 cb = QCheckBox(p.fullname())
2185 cb.setDisabled(not p.is_available())
2186 cb.setChecked(p.is_enabled())
2187 grid.addWidget(cb, i, 0)
2188 if p.requires_settings():
2189 w = p.settings_widget(self)
2190 w.setEnabled( p.is_enabled() )
2191 grid.addWidget(w, i, 1)
2194 cb.clicked.connect(mk_toggle(cb,p,w))
2195 grid.addWidget(HelpButton(p.description()), i, 2)
2197 print_msg(_("Error: cannot display plugin"), p)
2198 traceback.print_exc(file=sys.stdout)
2199 grid.setRowStretch(i+1,1)
2201 vbox.addLayout(close_button(d))
2206 def show_account_details(self, k):
2208 d.setWindowTitle(_('Account Details'))
2211 vbox = QVBoxLayout(d)
2212 roots = self.wallet.get_roots(k)
2214 name = self.wallet.get_account_name(k)
2215 label = QLabel('Name: ' + name)
2216 vbox.addWidget(label)
2218 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2219 vbox.addWidget(QLabel('Type: ' + acctype))
2221 label = QLabel('Derivation: ' + k)
2222 vbox.addWidget(label)
2225 # mpk = self.wallet.master_public_keys[root]
2226 # text = QTextEdit()
2227 # text.setReadOnly(True)
2228 # text.setMaximumHeight(120)
2229 # text.setText(repr(mpk))
2230 # vbox.addWidget(text)
2232 vbox.addLayout(close_button(d))