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 build_menu(self):
103 m.addAction(_("Show/Hide"), self.show_or_hide)
105 m.addAction(_("Exit Electrum"), self.close)
106 self.tray.setContextMenu(m)
108 def show_or_hide(self):
109 self.tray_activated(QSystemTrayIcon.DoubleClick)
111 def tray_activated(self, reason):
112 if reason == QSystemTrayIcon.DoubleClick:
113 if self.isMinimized() or self.isHidden():
118 def __init__(self, config, network):
119 QMainWindow.__init__(self)
122 self.network = network
124 self._close_electrum = False
127 if sys.platform == 'darwin':
128 self.icon = QIcon(":icons/electrum_dark_icon.png")
129 #self.icon = QIcon(":icons/lock.png")
131 self.icon = QIcon(':icons/electrum_light_icon.png')
133 self.tray = QSystemTrayIcon(self.icon, self)
134 self.tray.setToolTip('Electrum')
135 self.tray.activated.connect(self.tray_activated)
139 self.create_status_bar()
141 self.need_update = threading.Event()
143 self.decimal_point = config.get('decimal_point', 8)
144 self.num_zeros = int(config.get('num_zeros',0))
146 set_language(config.get('language'))
148 self.funds_error = False
149 self.completions = QStringListModel()
151 self.tabs = tabs = QTabWidget(self)
152 self.column_widths = self.config.get("column_widths_2", default_column_widths )
153 tabs.addTab(self.create_history_tab(), _('History') )
154 tabs.addTab(self.create_send_tab(), _('Send') )
155 tabs.addTab(self.create_receive_tab(), _('Receive') )
156 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
157 tabs.addTab(self.create_console_tab(), _('Console') )
158 tabs.setMinimumSize(600, 400)
159 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
160 self.setCentralWidget(tabs)
162 g = self.config.get("winpos-qt",[100, 100, 840, 400])
163 self.setGeometry(g[0], g[1], g[2], g[3])
165 self.setWindowIcon(QIcon(":icons/electrum.png"))
168 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
169 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
170 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
171 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
172 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
174 for i in range(tabs.count()):
175 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
177 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
178 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
179 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
181 self.history_list.setFocus(True)
185 self.network.register_callback('updated', lambda: self.need_update.set())
186 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
187 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
188 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
189 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
191 # set initial message
192 self.console.showMessage(self.network.banner)
199 self.config.set_key('lite_mode', False, True)
204 self.config.set_key('lite_mode', True, True)
211 if not self.check_qt_version():
212 if self.config.get('lite_mode') is True:
213 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
214 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
215 self.config.set_key('lite_mode', False, True)
221 actuator = lite_window.MiniActuator(self)
223 # Should probably not modify the current path but instead
224 # change the behaviour of rsrc(...)
225 old_path = QDir.currentPath()
226 actuator.load_theme()
228 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
230 driver = lite_window.MiniDriver(self, self.mini)
232 # Reset path back to original value now that loading the GUI
234 QDir.setCurrent(old_path)
236 if self.config.get('lite_mode') is True:
242 def check_qt_version(self):
243 qtVersion = qVersion()
244 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
247 def update_account_selector(self):
249 accounts = self.wallet.get_account_names()
250 self.account_selector.clear()
251 if len(accounts) > 1:
252 self.account_selector.addItems([_("All accounts")] + accounts.values())
253 self.account_selector.setCurrentIndex(0)
254 self.account_selector.show()
256 self.account_selector.hide()
259 def load_wallet(self, wallet):
262 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
263 self.current_account = self.wallet.storage.get("current_account", None)
265 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
266 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
267 self.setWindowTitle( title )
269 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
270 self.notify_transactions()
271 self.update_account_selector()
272 self.new_account.setEnabled(self.wallet.seed_version>4)
273 self.update_lock_icon()
274 self.update_buttons_on_seed()
275 self.update_console()
277 run_hook('load_wallet', wallet)
280 def open_wallet(self):
281 wallet_folder = self.wallet.storage.path
282 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
286 storage = WalletStorage({'wallet_path': filename})
287 if not storage.file_exists:
288 self.show_message("file not found "+ filename)
291 self.wallet.stop_threads()
294 wallet = Wallet(storage)
295 wallet.start_threads(self.network)
297 self.load_wallet(wallet)
301 def backup_wallet(self):
303 path = self.wallet.storage.path
304 wallet_folder = os.path.dirname(path)
305 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
309 new_path = os.path.join(wallet_folder, filename)
312 shutil.copy2(path, new_path)
313 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
314 except (IOError, os.error), reason:
315 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
318 def new_wallet(self):
321 wallet_folder = os.path.dirname(self.wallet.storage.path)
322 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
325 filename = os.path.join(wallet_folder, filename)
327 storage = WalletStorage({'wallet_path': filename})
328 if storage.file_exists:
329 QMessageBox.critical(None, "Error", _("File exists"))
332 wizard = installwizard.InstallWizard(self.config, self.network, storage)
333 wallet = wizard.run()
335 self.load_wallet(wallet)
339 def init_menubar(self):
342 file_menu = menubar.addMenu(_("&File"))
343 open_wallet_action = file_menu.addAction(_("&Open"))
344 open_wallet_action.setShortcut(QKeySequence.Open)
345 open_wallet_action.triggered.connect(self.open_wallet)
347 new_wallet_action = file_menu.addAction(_("&New/Restore"))
348 new_wallet_action.setShortcut(QKeySequence.New)
349 new_wallet_action.triggered.connect(self.new_wallet)
351 wallet_backup = file_menu.addAction(_("&Save Copy"))
352 wallet_backup.setShortcut(QKeySequence.SaveAs)
353 wallet_backup.triggered.connect(self.backup_wallet)
355 quit_item = file_menu.addAction(_("&Quit"))
356 #quit_item.setShortcut(QKeySequence.Quit)
357 quit_item.triggered.connect(self.close)
359 wallet_menu = menubar.addMenu(_("&Wallet"))
361 new_contact = wallet_menu.addAction(_("&New contact"))
362 new_contact.triggered.connect(self.new_contact_dialog)
364 self.new_account = wallet_menu.addAction(_("&New account"))
365 self.new_account.triggered.connect(self.new_account_dialog)
367 wallet_menu.addSeparator()
369 pw = wallet_menu.addAction(_("&Password"))
370 pw.triggered.connect(self.change_password_dialog)
372 show_seed = wallet_menu.addAction(_("&Seed"))
373 show_seed.triggered.connect(self.show_seed_dialog)
375 show_mpk = wallet_menu.addAction(_("&Master Public Key"))
376 show_mpk.triggered.connect(self.show_master_public_key)
378 wallet_menu.addSeparator()
380 labels_menu = wallet_menu.addMenu(_("&Labels"))
381 import_labels = labels_menu.addAction(_("&Import"))
382 import_labels.triggered.connect(self.do_import_labels)
383 export_labels = labels_menu.addAction(_("&Export"))
384 export_labels.triggered.connect(self.do_export_labels)
386 keys_menu = wallet_menu.addMenu(_("&Private keys"))
387 import_keys = keys_menu.addAction(_("&Import"))
388 import_keys.triggered.connect(self.do_import_privkey)
389 export_keys = keys_menu.addAction(_("&Export"))
390 export_keys.triggered.connect(self.do_export_privkeys)
392 ex_history = wallet_menu.addAction(_("&Export History"))
393 ex_history.triggered.connect(self.do_export_history)
397 tools_menu = menubar.addMenu(_("&Tools"))
399 # Settings / Preferences are all reserved keywords in OSX using this as work around
400 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
401 preferences_menu = tools_menu.addAction(preferences_name)
402 #preferences_menu.setShortcut(QKeySequence.Preferences)
403 preferences_menu.triggered.connect(self.settings_dialog)
405 network = tools_menu.addAction(_("&Network"))
406 network.triggered.connect(self.run_network_dialog)
408 plugins_labels = tools_menu.addAction(_("&Plugins"))
409 plugins_labels.triggered.connect(self.plugins_dialog)
411 tools_menu.addSeparator()
413 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
415 csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
416 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
418 csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
419 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
421 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
423 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
424 raw_transaction_file.triggered.connect(self.do_process_from_file)
426 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
427 raw_transaction_text.triggered.connect(self.do_process_from_text)
430 help_menu = menubar.addMenu(_("&Help"))
431 show_about = help_menu.addAction(_("&About"))
432 show_about.triggered.connect(self.show_about)
433 web_open = help_menu.addAction(_("&Official website"))
434 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
436 help_menu.addSeparator()
437 doc_open = help_menu.addAction(_("&Documentation"))
438 doc_open.setShortcut(QKeySequence.HelpContents)
439 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
440 report_bug = help_menu.addAction(_("&Report Bug"))
441 report_bug.triggered.connect(self.show_report_bug)
443 self.setMenuBar(menubar)
445 def show_about(self):
446 QMessageBox.about(self, "Electrum",
447 _("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."))
449 def show_report_bug(self):
450 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
451 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
454 def notify_transactions(self):
455 if not self.network or not self.network.is_connected():
458 print_error("Notifying GUI")
459 if len(self.network.interface.pending_transactions_for_notifications) > 0:
460 # Combine the transactions if there are more then three
461 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
464 for tx in self.network.interface.pending_transactions_for_notifications:
465 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
469 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
470 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
472 self.network.interface.pending_transactions_for_notifications = []
474 for tx in self.network.interface.pending_transactions_for_notifications:
476 self.network.interface.pending_transactions_for_notifications.remove(tx)
477 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
479 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
481 def notify(self, message):
482 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
486 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
487 def getOpenFileName(self, title, filter = ""):
488 directory = self.config.get('io_dir', os.path.expanduser('~'))
489 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
490 if fileName and directory != os.path.dirname(fileName):
491 self.config.set_key('io_dir', os.path.dirname(fileName), True)
494 def getSaveFileName(self, title, filename, filter = ""):
495 directory = self.config.get('io_dir', os.path.expanduser('~'))
496 path = os.path.join( directory, filename )
497 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
498 if fileName and directory != os.path.dirname(fileName):
499 self.config.set_key('io_dir', os.path.dirname(fileName), True)
503 QMainWindow.close(self)
504 run_hook('close_main_window')
506 def connect_slots(self, sender):
507 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
508 self.previous_payto_e=''
510 def timer_actions(self):
511 if self.need_update.is_set():
513 self.need_update.clear()
514 run_hook('timer_actions')
516 def format_amount(self, x, is_diff=False, whitespaces=False):
517 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
519 def read_amount(self, x):
520 if x in['.', '']: return None
521 p = pow(10, self.decimal_point)
522 return int( p * Decimal(x) )
525 assert self.decimal_point in [5,8]
526 return "BTC" if self.decimal_point == 8 else "mBTC"
529 def update_status(self):
530 if self.network is None or not self.network.is_running():
532 icon = QIcon(":icons/status_disconnected.png")
534 elif self.network.is_connected():
535 if not self.wallet.up_to_date:
536 text = _("Synchronizing...")
537 icon = QIcon(":icons/status_waiting.png")
538 elif self.network.server_lag > 1:
539 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
540 icon = QIcon(":icons/status_lagging.png")
542 c, u = self.wallet.get_account_balance(self.current_account)
543 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
544 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
547 run_hook('set_quote_text', c+u, r)
550 text += " (%s)"%quote
552 self.tray.setToolTip(text)
553 icon = QIcon(":icons/status_connected.png")
555 text = _("Not connected")
556 icon = QIcon(":icons/status_disconnected.png")
558 self.balance_label.setText(text)
559 self.status_button.setIcon( icon )
562 def update_wallet(self):
564 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
565 self.update_history_tab()
566 self.update_receive_tab()
567 self.update_contacts_tab()
568 self.update_completions()
571 def create_history_tab(self):
572 self.history_list = l = MyTreeWidget(self)
574 for i,width in enumerate(self.column_widths['history']):
575 l.setColumnWidth(i, width)
576 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
577 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
578 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
580 l.customContextMenuRequested.connect(self.create_history_menu)
584 def create_history_menu(self, position):
585 self.history_list.selectedIndexes()
586 item = self.history_list.currentItem()
588 tx_hash = str(item.data(0, Qt.UserRole).toString())
589 if not tx_hash: return
591 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
592 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
593 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
594 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
597 def show_transaction(self, tx):
598 import transaction_dialog
599 d = transaction_dialog.TxDialog(tx, self)
602 def tx_label_clicked(self, item, column):
603 if column==2 and item.isSelected():
605 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
606 self.history_list.editItem( item, column )
607 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
610 def tx_label_changed(self, item, column):
614 tx_hash = str(item.data(0, Qt.UserRole).toString())
615 tx = self.wallet.transactions.get(tx_hash)
616 text = unicode( item.text(2) )
617 self.wallet.set_label(tx_hash, text)
619 item.setForeground(2, QBrush(QColor('black')))
621 text = self.wallet.get_default_label(tx_hash)
622 item.setText(2, text)
623 item.setForeground(2, QBrush(QColor('gray')))
627 def edit_label(self, is_recv):
628 l = self.receive_list if is_recv else self.contacts_list
629 item = l.currentItem()
630 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
631 l.editItem( item, 1 )
632 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
636 def address_label_clicked(self, item, column, l, column_addr, column_label):
637 if column == column_label and item.isSelected():
638 is_editable = item.data(0, 32).toBool()
641 addr = unicode( item.text(column_addr) )
642 label = unicode( item.text(column_label) )
643 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
644 l.editItem( item, column )
645 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
648 def address_label_changed(self, item, column, l, column_addr, column_label):
649 if column == column_label:
650 addr = unicode( item.text(column_addr) )
651 text = unicode( item.text(column_label) )
652 is_editable = item.data(0, 32).toBool()
656 changed = self.wallet.set_label(addr, text)
658 self.update_history_tab()
659 self.update_completions()
661 self.current_item_changed(item)
663 run_hook('item_changed', item, column)
666 def current_item_changed(self, a):
667 run_hook('current_item_changed', a)
671 def update_history_tab(self):
673 self.history_list.clear()
674 for item in self.wallet.get_tx_history(self.current_account):
675 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
676 time_str = _("unknown")
679 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
681 time_str = _("error")
684 time_str = 'unverified'
685 icon = QIcon(":icons/unconfirmed.png")
688 icon = QIcon(":icons/unconfirmed.png")
690 icon = QIcon(":icons/clock%d.png"%conf)
692 icon = QIcon(":icons/confirmed.png")
694 if value is not None:
695 v_str = self.format_amount(value, True, whitespaces=True)
699 balance_str = self.format_amount(balance, whitespaces=True)
702 label, is_default_label = self.wallet.get_label(tx_hash)
704 label = _('Pruned transaction outputs')
705 is_default_label = False
707 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
708 item.setFont(2, QFont(MONOSPACE_FONT))
709 item.setFont(3, QFont(MONOSPACE_FONT))
710 item.setFont(4, QFont(MONOSPACE_FONT))
712 item.setForeground(3, QBrush(QColor("#BC1E1E")))
714 item.setData(0, Qt.UserRole, tx_hash)
715 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
717 item.setForeground(2, QBrush(QColor('grey')))
719 item.setIcon(0, icon)
720 self.history_list.insertTopLevelItem(0,item)
723 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
726 def create_send_tab(self):
731 grid.setColumnMinimumWidth(3,300)
732 grid.setColumnStretch(5,1)
735 self.payto_e = QLineEdit()
736 grid.addWidget(QLabel(_('Pay to')), 1, 0)
737 grid.addWidget(self.payto_e, 1, 1, 1, 3)
739 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)
741 completer = QCompleter()
742 completer.setCaseSensitivity(False)
743 self.payto_e.setCompleter(completer)
744 completer.setModel(self.completions)
746 self.message_e = QLineEdit()
747 grid.addWidget(QLabel(_('Description')), 2, 0)
748 grid.addWidget(self.message_e, 2, 1, 1, 3)
749 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)
751 self.from_label = QLabel(_('From'))
752 grid.addWidget(self.from_label, 3, 0)
753 self.from_list = QTreeWidget(self)
754 self.from_list.setColumnCount(2)
755 self.from_list.setColumnWidth(0, 350)
756 self.from_list.setColumnWidth(1, 50)
757 self.from_list.setHeaderHidden (True)
758 self.from_list.setMaximumHeight(80)
759 grid.addWidget(self.from_list, 3, 1, 1, 3)
760 self.set_pay_from([])
762 self.amount_e = AmountEdit(self.base_unit)
763 grid.addWidget(QLabel(_('Amount')), 4, 0)
764 grid.addWidget(self.amount_e, 4, 1, 1, 2)
765 grid.addWidget(HelpButton(
766 _('Amount to be sent.') + '\n\n' \
767 + _('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.') \
768 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
770 self.fee_e = AmountEdit(self.base_unit)
771 grid.addWidget(QLabel(_('Fee')), 5, 0)
772 grid.addWidget(self.fee_e, 5, 1, 1, 2)
773 grid.addWidget(HelpButton(
774 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
775 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
776 + _('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)
779 self.send_button = EnterButton(_("Send"), self.do_send)
780 grid.addWidget(self.send_button, 6, 1)
782 b = EnterButton(_("Clear"),self.do_clear)
783 grid.addWidget(b, 6, 2)
785 self.payto_sig = QLabel('')
786 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
788 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
789 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
798 def entry_changed( is_fee ):
799 self.funds_error = False
801 if self.amount_e.is_shortcut:
802 self.amount_e.is_shortcut = False
803 sendable = self.get_sendable_balance()
804 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, self.get_payment_sources())
805 fee = self.wallet.estimated_fee(inputs)
807 self.amount_e.setText( self.format_amount(amount) )
808 self.fee_e.setText( self.format_amount( fee ) )
811 amount = self.read_amount(str(self.amount_e.text()))
812 fee = self.read_amount(str(self.fee_e.text()))
814 if not is_fee: fee = None
817 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, self.get_payment_sources())
819 self.fee_e.setText( self.format_amount( fee ) )
822 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
826 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
827 self.funds_error = True
828 text = _( "Not enough funds" )
829 c, u = self.wallet.get_frozen_balance()
830 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
832 self.statusBar().showMessage(text)
833 self.amount_e.setPalette(palette)
834 self.fee_e.setPalette(palette)
836 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
837 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
839 run_hook('create_send_tab', grid)
843 def set_pay_from(self, l):
845 self.from_list.clear()
846 self.from_label.setHidden(len(self.pay_from) == 0)
847 self.from_list.setHidden(len(self.pay_from) == 0)
848 for addr in self.pay_from:
849 c, u = self.wallet.get_addr_balance(addr)
850 balance = self.format_amount(c + u)
851 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
854 def update_completions(self):
856 for addr,label in self.wallet.labels.items():
857 if addr in self.wallet.addressbook:
858 l.append( label + ' <' + addr + '>')
860 run_hook('update_completions', l)
861 self.completions.setStringList(l)
865 return lambda s, *args: s.do_protect(func, args)
870 label = unicode( self.message_e.text() )
871 r = unicode( self.payto_e.text() )
874 # label or alias, with address in brackets
875 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
876 to_address = m.group(2) if m else r
878 if not is_valid(to_address):
879 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
883 amount = self.read_amount(unicode( self.amount_e.text()))
885 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
888 fee = self.read_amount(unicode( self.fee_e.text()))
890 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
893 confirm_amount = self.config.get('confirm_amount', 100000000)
894 if amount >= confirm_amount:
895 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
898 confirm_fee = self.config.get('confirm_fee', 100000)
899 if fee >= confirm_fee:
900 if not self.question(_("The fee for this transaction seems unusually high.\nAre you really sure you want to pay %(fee)s in fees?")%{ 'fee' : self.format_amount(fee) + ' '+ self.base_unit()}):
903 self.send_tx(to_address, amount, fee, label)
907 def send_tx(self, to_address, amount, fee, label, password):
909 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
910 domain=self.get_payment_sources())
911 except Exception as e:
912 traceback.print_exc(file=sys.stdout)
913 self.show_message(str(e))
916 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
917 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
921 self.wallet.set_label(tx.hash(), label)
924 h = self.wallet.send_tx(tx)
925 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
926 status, msg = self.wallet.receive_tx( h, tx )
928 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
930 self.update_contacts_tab()
932 QMessageBox.warning(self, _('Error'), msg, _('OK'))
935 self.show_transaction(tx)
937 # add recipient to addressbook
938 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
939 self.wallet.addressbook.append(to_address)
944 def set_url(self, url):
945 address, amount, label, message, signature, identity, url = util.parse_url(url)
948 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
949 elif amount: amount = str(Decimal(amount))
952 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
955 self.mini.set_payment_fields(address, amount)
957 if label and self.wallet.labels.get(address) != label:
958 if self.question('Give label "%s" to address %s ?'%(label,address)):
959 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
960 self.wallet.addressbook.append(address)
961 self.wallet.set_label(address, label)
963 run_hook('set_url', url, self.show_message, self.question)
965 self.tabs.setCurrentIndex(1)
966 label = self.wallet.labels.get(address)
967 m_addr = label + ' <'+ address +'>' if label else address
968 self.payto_e.setText(m_addr)
970 self.message_e.setText(message)
972 self.amount_e.setText(amount)
975 self.set_frozen(self.payto_e,True)
976 self.set_frozen(self.amount_e,True)
977 self.set_frozen(self.message_e,True)
978 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
980 self.payto_sig.setVisible(False)
983 self.payto_sig.setVisible(False)
984 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
986 self.set_frozen(e,False)
988 self.set_pay_from([])
991 def set_frozen(self,entry,frozen):
993 entry.setReadOnly(True)
994 entry.setFrame(False)
996 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
997 entry.setPalette(palette)
999 entry.setReadOnly(False)
1000 entry.setFrame(True)
1001 palette = QPalette()
1002 palette.setColor(entry.backgroundRole(), QColor('white'))
1003 entry.setPalette(palette)
1006 def set_addrs_frozen(self,addrs,freeze):
1008 if not addr: continue
1009 if addr in self.wallet.frozen_addresses and not freeze:
1010 self.wallet.unfreeze(addr)
1011 elif addr not in self.wallet.frozen_addresses and freeze:
1012 self.wallet.freeze(addr)
1013 self.update_receive_tab()
1017 def create_list_tab(self, headers):
1018 "generic tab creation method"
1019 l = MyTreeWidget(self)
1020 l.setColumnCount( len(headers) )
1021 l.setHeaderLabels( headers )
1024 vbox = QVBoxLayout()
1031 vbox.addWidget(buttons)
1033 hbox = QHBoxLayout()
1036 buttons.setLayout(hbox)
1041 def create_receive_tab(self):
1042 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1043 l.setContextMenuPolicy(Qt.CustomContextMenu)
1044 l.customContextMenuRequested.connect(self.create_receive_menu)
1045 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1046 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1047 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1048 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1049 self.receive_list = l
1050 self.receive_buttons_hbox = hbox
1057 def save_column_widths(self):
1058 self.column_widths["receive"] = []
1059 for i in range(self.receive_list.columnCount() -1):
1060 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1062 self.column_widths["history"] = []
1063 for i in range(self.history_list.columnCount() - 1):
1064 self.column_widths["history"].append(self.history_list.columnWidth(i))
1066 self.column_widths["contacts"] = []
1067 for i in range(self.contacts_list.columnCount() - 1):
1068 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1070 self.config.set_key("column_widths_2", self.column_widths, True)
1073 def create_contacts_tab(self):
1074 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1075 l.setContextMenuPolicy(Qt.CustomContextMenu)
1076 l.customContextMenuRequested.connect(self.create_contact_menu)
1077 for i,width in enumerate(self.column_widths['contacts']):
1078 l.setColumnWidth(i, width)
1080 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1081 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1082 self.contacts_list = l
1083 self.contacts_buttons_hbox = hbox
1088 def delete_imported_key(self, addr):
1089 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1090 self.wallet.delete_imported_key(addr)
1091 self.update_receive_tab()
1092 self.update_history_tab()
1094 def edit_account_label(self, k):
1095 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1097 label = unicode(text)
1098 self.wallet.set_label(k,label)
1099 self.update_receive_tab()
1101 def account_set_expanded(self, item, k, b):
1103 self.accounts_expanded[k] = b
1105 def create_account_menu(self, position, k, item):
1107 if item.isExpanded():
1108 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1110 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1111 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1112 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1113 if self.wallet.account_is_pending(k):
1114 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1115 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1117 def delete_pending_account(self, k):
1118 self.wallet.delete_pending_account(k)
1119 self.update_receive_tab()
1121 def create_receive_menu(self, position):
1122 # fixme: this function apparently has a side effect.
1123 # if it is not called the menu pops up several times
1124 #self.receive_list.selectedIndexes()
1126 selected = self.receive_list.selectedItems()
1127 multi_select = len(selected) > 1
1128 addrs = [unicode(item.text(0)) for item in selected]
1129 if not multi_select:
1130 item = self.receive_list.itemAt(position)
1134 if not is_valid(addr):
1135 k = str(item.data(0,32).toString())
1137 self.create_account_menu(position, k, item)
1139 item.setExpanded(not item.isExpanded())
1143 if not multi_select:
1144 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1145 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1146 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1147 if self.wallet.seed:
1148 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1149 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1150 if addr in self.wallet.imported_keys:
1151 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1153 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1154 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1155 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1156 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1158 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1159 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1161 run_hook('receive_menu', menu, addrs)
1162 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1165 def get_sendable_balance(self):
1166 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1169 def get_payment_sources(self):
1171 return self.pay_from
1173 return self.wallet.get_account_addresses(self.current_account)
1176 def send_from_addresses(self, addrs):
1177 self.set_pay_from( addrs )
1178 self.tabs.setCurrentIndex(1)
1181 def payto(self, addr):
1183 label = self.wallet.labels.get(addr)
1184 m_addr = label + ' <' + addr + '>' if label else addr
1185 self.tabs.setCurrentIndex(1)
1186 self.payto_e.setText(m_addr)
1187 self.amount_e.setFocus()
1190 def delete_contact(self, x):
1191 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1192 self.wallet.delete_contact(x)
1193 self.wallet.set_label(x, None)
1194 self.update_history_tab()
1195 self.update_contacts_tab()
1196 self.update_completions()
1199 def create_contact_menu(self, position):
1200 item = self.contacts_list.itemAt(position)
1203 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1205 addr = unicode(item.text(0))
1206 label = unicode(item.text(1))
1207 is_editable = item.data(0,32).toBool()
1208 payto_addr = item.data(0,33).toString()
1209 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1210 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1211 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1213 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1214 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1216 run_hook('create_contact_menu', menu, item)
1217 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1220 def update_receive_item(self, item):
1221 item.setFont(0, QFont(MONOSPACE_FONT))
1222 address = str(item.data(0,0).toString())
1223 label = self.wallet.labels.get(address,'')
1224 item.setData(1,0,label)
1225 item.setData(0,32, True) # is editable
1227 run_hook('update_receive_item', address, item)
1229 if not self.wallet.is_mine(address): return
1231 c, u = self.wallet.get_addr_balance(address)
1232 balance = self.format_amount(c + u)
1233 item.setData(2,0,balance)
1235 if address in self.wallet.frozen_addresses:
1236 item.setBackgroundColor(0, QColor('lightblue'))
1239 def update_receive_tab(self):
1240 l = self.receive_list
1243 l.setColumnHidden(2, False)
1244 l.setColumnHidden(3, False)
1245 for i,width in enumerate(self.column_widths['receive']):
1246 l.setColumnWidth(i, width)
1248 if self.current_account is None:
1249 account_items = self.wallet.accounts.items()
1250 elif self.current_account != -1:
1251 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1255 for k, account in account_items:
1256 name = self.wallet.get_account_name(k)
1257 c,u = self.wallet.get_account_balance(k)
1258 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1259 l.addTopLevelItem(account_item)
1260 account_item.setExpanded(self.accounts_expanded.get(k, True))
1261 account_item.setData(0, 32, k)
1263 if not self.wallet.is_seeded(k):
1264 icon = QIcon(":icons/key.png")
1265 account_item.setIcon(0, icon)
1267 for is_change in ([0,1]):
1268 name = _("Receiving") if not is_change else _("Change")
1269 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1270 account_item.addChild(seq_item)
1271 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1273 if not is_change: seq_item.setExpanded(True)
1278 for address in account.get_addresses(is_change):
1279 h = self.wallet.history.get(address,[])
1283 if gap > self.wallet.gap_limit:
1288 c, u = self.wallet.get_addr_balance(address)
1289 num_tx = '*' if h == ['*'] else "%d"%len(h)
1290 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1291 self.update_receive_item(item)
1293 item.setBackgroundColor(1, QColor('red'))
1294 if len(h) > 0 and c == -u:
1296 seq_item.insertChild(0,used_item)
1298 used_item.addChild(item)
1300 seq_item.addChild(item)
1303 for k, addr in self.wallet.get_pending_accounts():
1304 name = self.wallet.labels.get(k,'')
1305 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1306 self.update_receive_item(item)
1307 l.addTopLevelItem(account_item)
1308 account_item.setExpanded(True)
1309 account_item.setData(0, 32, k)
1310 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1311 account_item.addChild(item)
1312 self.update_receive_item(item)
1315 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1316 c,u = self.wallet.get_imported_balance()
1317 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1318 l.addTopLevelItem(account_item)
1319 account_item.setExpanded(True)
1320 for address in self.wallet.imported_keys.keys():
1321 item = QTreeWidgetItem( [ address, '', '', ''] )
1322 self.update_receive_item(item)
1323 account_item.addChild(item)
1326 # we use column 1 because column 0 may be hidden
1327 l.setCurrentItem(l.topLevelItem(0),1)
1330 def update_contacts_tab(self):
1331 l = self.contacts_list
1334 for address in self.wallet.addressbook:
1335 label = self.wallet.labels.get(address,'')
1336 n = self.wallet.get_num_tx(address)
1337 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1338 item.setFont(0, QFont(MONOSPACE_FONT))
1339 # 32 = label can be edited (bool)
1340 item.setData(0,32, True)
1342 item.setData(0,33, address)
1343 l.addTopLevelItem(item)
1345 run_hook('update_contacts_tab', l)
1346 l.setCurrentItem(l.topLevelItem(0))
1350 def create_console_tab(self):
1351 from console import Console
1352 self.console = console = Console()
1356 def update_console(self):
1357 console = self.console
1358 console.history = self.config.get("console-history",[])
1359 console.history_index = len(console.history)
1361 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1362 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1364 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1366 def mkfunc(f, method):
1367 return lambda *args: apply( f, (method, args, self.password_dialog ))
1369 if m[0]=='_' or m in ['network','wallet']: continue
1370 methods[m] = mkfunc(c._run, m)
1372 console.updateNamespace(methods)
1375 def change_account(self,s):
1376 if s == _("All accounts"):
1377 self.current_account = None
1379 accounts = self.wallet.get_account_names()
1380 for k, v in accounts.items():
1382 self.current_account = k
1383 self.update_history_tab()
1384 self.update_status()
1385 self.update_receive_tab()
1387 def create_status_bar(self):
1390 sb.setFixedHeight(35)
1391 qtVersion = qVersion()
1393 self.balance_label = QLabel("")
1394 sb.addWidget(self.balance_label)
1396 from version_getter import UpdateLabel
1397 self.updatelabel = UpdateLabel(self.config, sb)
1399 self.account_selector = QComboBox()
1400 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1401 sb.addPermanentWidget(self.account_selector)
1403 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1404 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1406 self.lock_icon = QIcon()
1407 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1408 sb.addPermanentWidget( self.password_button )
1410 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1411 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1412 sb.addPermanentWidget( self.seed_button )
1413 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1414 sb.addPermanentWidget( self.status_button )
1416 run_hook('create_status_bar', (sb,))
1418 self.setStatusBar(sb)
1421 def update_lock_icon(self):
1422 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1423 self.password_button.setIcon( icon )
1426 def update_buttons_on_seed(self):
1427 if not self.wallet.is_watching_only():
1428 self.seed_button.show()
1429 self.password_button.show()
1430 self.send_button.setText(_("Send"))
1432 self.password_button.hide()
1433 self.seed_button.hide()
1434 self.send_button.setText(_("Create unsigned transaction"))
1437 def change_password_dialog(self):
1438 from password_dialog import PasswordDialog
1439 d = PasswordDialog(self.wallet, self)
1441 self.update_lock_icon()
1444 def new_contact_dialog(self):
1447 vbox = QVBoxLayout(d)
1448 vbox.addWidget(QLabel(_('New Contact')+':'))
1450 grid = QGridLayout()
1453 grid.addWidget(QLabel(_("Address")), 1, 0)
1454 grid.addWidget(line1, 1, 1)
1455 grid.addWidget(QLabel(_("Name")), 2, 0)
1456 grid.addWidget(line2, 2, 1)
1458 vbox.addLayout(grid)
1459 vbox.addLayout(ok_cancel_buttons(d))
1464 address = str(line1.text())
1465 label = unicode(line2.text())
1467 if not is_valid(address):
1468 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1471 self.wallet.add_contact(address)
1473 self.wallet.set_label(address, label)
1475 self.update_contacts_tab()
1476 self.update_history_tab()
1477 self.update_completions()
1478 self.tabs.setCurrentIndex(3)
1481 def new_account_dialog(self):
1483 dialog = QDialog(self)
1485 dialog.setWindowTitle(_("New Account"))
1487 vbox = QVBoxLayout()
1488 vbox.addWidget(QLabel(_('Account name')+':'))
1491 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1492 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1497 vbox.addLayout(ok_cancel_buttons(dialog))
1498 dialog.setLayout(vbox)
1502 name = str(e.text())
1505 self.wallet.create_pending_account('1', name)
1506 self.update_receive_tab()
1507 self.tabs.setCurrentIndex(2)
1511 def show_master_public_key_old(self):
1512 dialog = QDialog(self)
1514 dialog.setWindowTitle(_("Master Public Key"))
1516 main_text = QTextEdit()
1517 main_text.setText(self.wallet.get_master_public_key())
1518 main_text.setReadOnly(True)
1519 main_text.setMaximumHeight(170)
1520 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1522 ok_button = QPushButton(_("OK"))
1523 ok_button.setDefault(True)
1524 ok_button.clicked.connect(dialog.accept)
1526 main_layout = QGridLayout()
1527 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1529 main_layout.addWidget(main_text, 1, 0)
1530 main_layout.addWidget(qrw, 1, 1 )
1532 vbox = QVBoxLayout()
1533 vbox.addLayout(main_layout)
1534 vbox.addLayout(close_button(dialog))
1535 dialog.setLayout(vbox)
1539 def show_master_public_key(self):
1541 if self.wallet.seed_version == 4:
1542 self.show_master_public_key_old()
1545 dialog = QDialog(self)
1547 dialog.setWindowTitle(_("Master Public Keys"))
1549 chain_text = QTextEdit()
1550 chain_text.setReadOnly(True)
1551 chain_text.setMaximumHeight(170)
1552 chain_qrw = QRCodeWidget()
1554 mpk_text = QTextEdit()
1555 mpk_text.setReadOnly(True)
1556 mpk_text.setMaximumHeight(170)
1557 mpk_qrw = QRCodeWidget()
1559 main_layout = QGridLayout()
1561 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1562 main_layout.addWidget(mpk_text, 1, 1)
1563 main_layout.addWidget(mpk_qrw, 1, 2)
1565 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1566 main_layout.addWidget(chain_text, 2, 1)
1567 main_layout.addWidget(chain_qrw, 2, 2)
1570 c, K, cK = self.wallet.master_public_keys[str(key)]
1571 chain_text.setText(c)
1572 chain_qrw.set_addr(c)
1573 chain_qrw.update_qr()
1578 key_selector = QComboBox()
1579 keys = sorted(self.wallet.master_public_keys.keys())
1580 key_selector.addItems(keys)
1582 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1583 main_layout.addWidget(key_selector, 0, 1)
1584 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1588 vbox = QVBoxLayout()
1589 vbox.addLayout(main_layout)
1590 vbox.addLayout(close_button(dialog))
1592 dialog.setLayout(vbox)
1597 def show_seed_dialog(self, password):
1598 if self.wallet.is_watching_only():
1599 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1602 if self.wallet.seed:
1604 mnemonic = self.wallet.get_mnemonic(password)
1606 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1608 from seed_dialog import SeedDialog
1609 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1613 for k in self.wallet.master_private_keys.keys():
1614 pk = self.wallet.get_master_private_key(k, password)
1616 from seed_dialog import PrivateKeysDialog
1617 d = PrivateKeysDialog(self,l)
1624 def show_qrcode(self, data, title = _("QR code")):
1628 d.setWindowTitle(title)
1629 d.setMinimumSize(270, 300)
1630 vbox = QVBoxLayout()
1631 qrw = QRCodeWidget(data)
1632 vbox.addWidget(qrw, 1)
1633 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1634 hbox = QHBoxLayout()
1637 filename = os.path.join(self.config.path, "qrcode.bmp")
1640 bmp.save_qrcode(qrw.qr, filename)
1641 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1643 def copy_to_clipboard():
1644 bmp.save_qrcode(qrw.qr, filename)
1645 self.app.clipboard().setImage(QImage(filename))
1646 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1648 b = QPushButton(_("Copy"))
1650 b.clicked.connect(copy_to_clipboard)
1652 b = QPushButton(_("Save"))
1654 b.clicked.connect(print_qr)
1656 b = QPushButton(_("Close"))
1658 b.clicked.connect(d.accept)
1661 vbox.addLayout(hbox)
1666 def do_protect(self, func, args):
1667 if self.wallet.use_encryption:
1668 password = self.password_dialog()
1674 if args != (False,):
1675 args = (self,) + args + (password,)
1677 args = (self,password)
1682 def show_private_key(self, address, password):
1683 if not address: return
1685 pk_list = self.wallet.get_private_key(address, password)
1686 except Exception as e:
1687 self.show_message(str(e))
1691 d.setMinimumSize(600, 200)
1693 vbox = QVBoxLayout()
1694 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1695 vbox.addWidget( QLabel(_("Private key") + ':'))
1697 keys.setReadOnly(True)
1698 keys.setText('\n'.join(pk_list))
1699 vbox.addWidget(keys)
1700 vbox.addLayout(close_button(d))
1706 def do_sign(self, address, message, signature, password):
1707 message = unicode(message.toPlainText())
1708 message = message.encode('utf-8')
1710 sig = self.wallet.sign_message(str(address.text()), message, password)
1711 signature.setText(sig)
1712 except Exception as e:
1713 self.show_message(str(e))
1715 def sign_message(self, address):
1716 if not address: return
1719 d.setWindowTitle(_('Sign Message'))
1720 d.setMinimumSize(410, 290)
1722 tab_widget = QTabWidget()
1724 layout = QGridLayout(tab)
1726 sign_address = QLineEdit()
1728 sign_address.setText(address)
1729 layout.addWidget(QLabel(_('Address')), 1, 0)
1730 layout.addWidget(sign_address, 1, 1)
1732 sign_message = QTextEdit()
1733 layout.addWidget(QLabel(_('Message')), 2, 0)
1734 layout.addWidget(sign_message, 2, 1)
1735 layout.setRowStretch(2,3)
1737 sign_signature = QTextEdit()
1738 layout.addWidget(QLabel(_('Signature')), 3, 0)
1739 layout.addWidget(sign_signature, 3, 1)
1740 layout.setRowStretch(3,1)
1743 hbox = QHBoxLayout()
1744 b = QPushButton(_("Sign"))
1746 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1747 b = QPushButton(_("Close"))
1748 b.clicked.connect(d.accept)
1750 layout.addLayout(hbox, 4, 1)
1751 tab_widget.addTab(tab, _("Sign"))
1755 layout = QGridLayout(tab)
1757 verify_address = QLineEdit()
1758 layout.addWidget(QLabel(_('Address')), 1, 0)
1759 layout.addWidget(verify_address, 1, 1)
1761 verify_message = QTextEdit()
1762 layout.addWidget(QLabel(_('Message')), 2, 0)
1763 layout.addWidget(verify_message, 2, 1)
1764 layout.setRowStretch(2,3)
1766 verify_signature = QTextEdit()
1767 layout.addWidget(QLabel(_('Signature')), 3, 0)
1768 layout.addWidget(verify_signature, 3, 1)
1769 layout.setRowStretch(3,1)
1772 message = unicode(verify_message.toPlainText())
1773 message = message.encode('utf-8')
1774 if bitcoin.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1775 self.show_message(_("Signature verified"))
1777 self.show_message(_("Error: wrong signature"))
1779 hbox = QHBoxLayout()
1780 b = QPushButton(_("Verify"))
1781 b.clicked.connect(do_verify)
1783 b = QPushButton(_("Close"))
1784 b.clicked.connect(d.accept)
1786 layout.addLayout(hbox, 4, 1)
1787 tab_widget.addTab(tab, _("Verify"))
1789 vbox = QVBoxLayout()
1790 vbox.addWidget(tab_widget)
1797 def question(self, msg):
1798 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1800 def show_message(self, msg):
1801 QMessageBox.information(self, _('Message'), msg, _('OK'))
1803 def password_dialog(self ):
1810 vbox = QVBoxLayout()
1811 msg = _('Please enter your password')
1812 vbox.addWidget(QLabel(msg))
1814 grid = QGridLayout()
1816 grid.addWidget(QLabel(_('Password')), 1, 0)
1817 grid.addWidget(pw, 1, 1)
1818 vbox.addLayout(grid)
1820 vbox.addLayout(ok_cancel_buttons(d))
1823 run_hook('password_dialog', pw, grid, 1)
1824 if not d.exec_(): return
1825 return unicode(pw.text())
1834 def tx_from_text(self, txt):
1835 "json or raw hexadecimal"
1838 tx = Transaction(txt)
1844 tx_dict = json.loads(str(txt))
1845 assert "hex" in tx_dict.keys()
1846 assert "complete" in tx_dict.keys()
1847 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1848 if not tx_dict["complete"]:
1849 assert "input_info" in tx_dict.keys()
1850 input_info = json.loads(tx_dict['input_info'])
1851 tx.add_input_info(input_info)
1856 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1860 def read_tx_from_file(self):
1861 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1865 with open(fileName, "r") as f:
1866 file_content = f.read()
1867 except (ValueError, IOError, os.error), reason:
1868 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1870 return self.tx_from_text(file_content)
1874 def sign_raw_transaction(self, tx, input_info, password):
1875 self.wallet.signrawtransaction(tx, input_info, [], password)
1877 def do_process_from_text(self):
1878 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1881 tx = self.tx_from_text(text)
1883 self.show_transaction(tx)
1885 def do_process_from_file(self):
1886 tx = self.read_tx_from_file()
1888 self.show_transaction(tx)
1890 def do_process_from_csvReader(self, csvReader):
1893 for row in csvReader:
1895 amount = Decimal(row[1])
1896 amount = int(100000000*amount)
1897 outputs.append((address, amount))
1898 except (ValueError, IOError, os.error), reason:
1899 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1903 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1904 except Exception as e:
1905 self.show_message(str(e))
1908 self.show_transaction(tx)
1910 def do_process_from_csv_file(self):
1911 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1915 with open(fileName, "r") as f:
1916 csvReader = csv.reader(f)
1917 self.do_process_from_csvReader(csvReader)
1918 except (ValueError, IOError, os.error), reason:
1919 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1922 def do_process_from_csv_text(self):
1923 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1924 + _("Format: address, amount. One output per line"), _("Load CSV"))
1927 f = StringIO.StringIO(text)
1928 csvReader = csv.reader(f)
1929 self.do_process_from_csvReader(csvReader)
1934 def do_export_privkeys(self, password):
1935 if not self.wallet.seed:
1936 self.show_message(_("This wallet has no seed"))
1939 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.")))
1942 select_export = _('Select file to export your private keys to')
1943 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1945 with open(fileName, "w+") as csvfile:
1946 transaction = csv.writer(csvfile)
1947 transaction.writerow(["address", "private_key"])
1949 addresses = self.wallet.addresses(True)
1951 for addr in addresses:
1952 pk = "".join(self.wallet.get_private_key(addr, password))
1953 transaction.writerow(["%34s"%addr,pk])
1955 self.show_message(_("Private keys exported."))
1957 except (IOError, os.error), reason:
1958 export_error_label = _("Electrum was unable to produce a private key-export.")
1959 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1961 except Exception as e:
1962 self.show_message(str(e))
1966 def do_import_labels(self):
1967 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1968 if not labelsFile: return
1970 f = open(labelsFile, 'r')
1973 for key, value in json.loads(data).items():
1974 self.wallet.set_label(key, value)
1975 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1976 except (IOError, os.error), reason:
1977 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1980 def do_export_labels(self):
1981 labels = self.wallet.labels
1983 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1985 with open(fileName, 'w+') as f:
1986 json.dump(labels, f)
1987 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1988 except (IOError, os.error), reason:
1989 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1992 def do_export_history(self):
1993 from lite_window import csv_transaction
1994 csv_transaction(self.wallet)
1998 def do_import_privkey(self, password):
1999 if not self.wallet.imported_keys:
2000 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2001 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2002 + _('Are you sure you understand what you are doing?'), 3, 4)
2005 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2008 text = str(text).split()
2013 addr = self.wallet.import_key(key, password)
2014 except Exception as e:
2020 addrlist.append(addr)
2022 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2024 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2025 self.update_receive_tab()
2026 self.update_history_tab()
2029 def settings_dialog(self):
2031 d.setWindowTitle(_('Electrum Settings'))
2033 vbox = QVBoxLayout()
2034 grid = QGridLayout()
2035 grid.setColumnStretch(0,1)
2037 nz_label = QLabel(_('Display zeros') + ':')
2038 grid.addWidget(nz_label, 0, 0)
2039 nz_e = AmountEdit(None,True)
2040 nz_e.setText("%d"% self.num_zeros)
2041 grid.addWidget(nz_e, 0, 1)
2042 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2043 grid.addWidget(HelpButton(msg), 0, 2)
2044 if not self.config.is_modifiable('num_zeros'):
2045 for w in [nz_e, nz_label]: w.setEnabled(False)
2047 lang_label=QLabel(_('Language') + ':')
2048 grid.addWidget(lang_label, 1, 0)
2049 lang_combo = QComboBox()
2050 from electrum.i18n import languages
2051 lang_combo.addItems(languages.values())
2053 index = languages.keys().index(self.config.get("language",''))
2056 lang_combo.setCurrentIndex(index)
2057 grid.addWidget(lang_combo, 1, 1)
2058 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2059 if not self.config.is_modifiable('language'):
2060 for w in [lang_combo, lang_label]: w.setEnabled(False)
2063 fee_label = QLabel(_('Transaction fee') + ':')
2064 grid.addWidget(fee_label, 2, 0)
2065 fee_e = AmountEdit(self.base_unit)
2066 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2067 grid.addWidget(fee_e, 2, 1)
2068 msg = _('Fee per kilobyte of transaction.') + ' ' \
2069 + _('Recommended value') + ': ' + self.format_amount(20000)
2070 grid.addWidget(HelpButton(msg), 2, 2)
2071 if not self.config.is_modifiable('fee_per_kb'):
2072 for w in [fee_e, fee_label]: w.setEnabled(False)
2074 units = ['BTC', 'mBTC']
2075 unit_label = QLabel(_('Base unit') + ':')
2076 grid.addWidget(unit_label, 3, 0)
2077 unit_combo = QComboBox()
2078 unit_combo.addItems(units)
2079 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2080 grid.addWidget(unit_combo, 3, 1)
2081 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2082 + '\n1BTC=1000mBTC.\n' \
2083 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2085 usechange_cb = QCheckBox(_('Use change addresses'))
2086 usechange_cb.setChecked(self.wallet.use_change)
2087 grid.addWidget(usechange_cb, 4, 0)
2088 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2089 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2091 grid.setRowStretch(5,1)
2093 vbox.addLayout(grid)
2094 vbox.addLayout(ok_cancel_buttons(d))
2098 if not d.exec_(): return
2100 fee = unicode(fee_e.text())
2102 fee = self.read_amount(fee)
2104 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2107 self.wallet.set_fee(fee)
2109 nz = unicode(nz_e.text())
2114 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2117 if self.num_zeros != nz:
2119 self.config.set_key('num_zeros', nz, True)
2120 self.update_history_tab()
2121 self.update_receive_tab()
2123 usechange_result = usechange_cb.isChecked()
2124 if self.wallet.use_change != usechange_result:
2125 self.wallet.use_change = usechange_result
2126 self.wallet.storage.put('use_change', self.wallet.use_change)
2128 unit_result = units[unit_combo.currentIndex()]
2129 if self.base_unit() != unit_result:
2130 self.decimal_point = 8 if unit_result == 'BTC' else 5
2131 self.config.set_key('decimal_point', self.decimal_point, True)
2132 self.update_history_tab()
2133 self.update_status()
2135 need_restart = False
2137 lang_request = languages.keys()[lang_combo.currentIndex()]
2138 if lang_request != self.config.get('language'):
2139 self.config.set_key("language", lang_request, True)
2142 run_hook('close_settings_dialog')
2145 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2148 def run_network_dialog(self):
2149 if not self.network:
2151 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2153 def closeEvent(self, event):
2156 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2157 self.save_column_widths()
2158 self.config.set_key("console-history", self.console.history[-50:], True)
2159 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2164 def plugins_dialog(self):
2165 from electrum.plugins import plugins
2168 d.setWindowTitle(_('Electrum Plugins'))
2171 vbox = QVBoxLayout(d)
2174 scroll = QScrollArea()
2175 scroll.setEnabled(True)
2176 scroll.setWidgetResizable(True)
2177 scroll.setMinimumSize(400,250)
2178 vbox.addWidget(scroll)
2182 w.setMinimumHeight(len(plugins)*35)
2184 grid = QGridLayout()
2185 grid.setColumnStretch(0,1)
2188 def do_toggle(cb, p, w):
2191 if w: w.setEnabled(r)
2193 def mk_toggle(cb, p, w):
2194 return lambda: do_toggle(cb,p,w)
2196 for i, p in enumerate(plugins):
2198 cb = QCheckBox(p.fullname())
2199 cb.setDisabled(not p.is_available())
2200 cb.setChecked(p.is_enabled())
2201 grid.addWidget(cb, i, 0)
2202 if p.requires_settings():
2203 w = p.settings_widget(self)
2204 w.setEnabled( p.is_enabled() )
2205 grid.addWidget(w, i, 1)
2208 cb.clicked.connect(mk_toggle(cb,p,w))
2209 grid.addWidget(HelpButton(p.description()), i, 2)
2211 print_msg(_("Error: cannot display plugin"), p)
2212 traceback.print_exc(file=sys.stdout)
2213 grid.setRowStretch(i+1,1)
2215 vbox.addLayout(close_button(d))
2220 def show_account_details(self, k):
2222 d.setWindowTitle(_('Account Details'))
2225 vbox = QVBoxLayout(d)
2226 roots = self.wallet.get_roots(k)
2228 name = self.wallet.get_account_name(k)
2229 label = QLabel('Name: ' + name)
2230 vbox.addWidget(label)
2232 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2233 vbox.addWidget(QLabel('Type: ' + acctype))
2235 label = QLabel('Derivation: ' + k)
2236 vbox.addWidget(label)
2239 # mpk = self.wallet.master_public_keys[root]
2240 # text = QTextEdit()
2241 # text.setReadOnly(True)
2242 # text.setMaximumHeight(120)
2243 # text.setText(repr(mpk))
2244 # vbox.addWidget(text)
2246 vbox.addLayout(close_button(d))