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
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
33 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
34 from electrum.plugins import run_hook
38 from electrum.wallet import format_satoshis
39 from electrum import Transaction
40 from electrum import mnemonic
41 from electrum import util, bitcoin, commands, Interface, Wallet
42 from electrum import SimpleConfig, Wallet, WalletStorage
45 from electrum import bmp, pyqrnative
47 from amountedit import AmountEdit
48 from network_dialog import NetworkDialog
49 from qrcodewidget import QRCodeWidget
51 from decimal import Decimal
59 if platform.system() == 'Windows':
60 MONOSPACE_FONT = 'Lucida Console'
61 elif platform.system() == 'Darwin':
62 MONOSPACE_FONT = 'Monaco'
64 MONOSPACE_FONT = 'monospace'
66 from electrum import ELECTRUM_VERSION
76 class StatusBarButton(QPushButton):
77 def __init__(self, icon, tooltip, func):
78 QPushButton.__init__(self, icon, '')
79 self.setToolTip(tooltip)
81 self.setMaximumWidth(25)
82 self.clicked.connect(func)
84 self.setIconSize(QSize(25,25))
86 def keyPressEvent(self, e):
87 if e.key() == QtCore.Qt.Key_Return:
99 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
101 class ElectrumWindow(QMainWindow):
102 def build_menu(self):
104 m.addAction(_("Show/Hide"), self.show_or_hide)
106 m.addAction(_("Exit Electrum"), self.close)
107 self.tray.setContextMenu(m)
109 def show_or_hide(self):
110 self.tray_activated(QSystemTrayIcon.DoubleClick)
112 def tray_activated(self, reason):
113 if reason == QSystemTrayIcon.DoubleClick:
114 if self.isMinimized() or self.isHidden():
119 def __init__(self, config, network):
120 QMainWindow.__init__(self)
123 self.network = network
125 self._close_electrum = False
128 if sys.platform == 'darwin':
129 self.icon = QIcon(":icons/electrum_dark_icon.png")
130 #self.icon = QIcon(":icons/lock.png")
132 self.icon = QIcon(':icons/electrum_light_icon.png')
134 self.tray = QSystemTrayIcon(self.icon, self)
135 self.tray.setToolTip('Electrum')
136 self.tray.activated.connect(self.tray_activated)
140 self.create_status_bar()
142 self.need_update = threading.Event()
144 self.decimal_point = config.get('decimal_point', 8)
145 self.num_zeros = int(config.get('num_zeros',0))
147 set_language(config.get('language'))
149 self.funds_error = False
150 self.completions = QStringListModel()
152 self.tabs = tabs = QTabWidget(self)
153 self.column_widths = self.config.get("column_widths_2", default_column_widths )
154 tabs.addTab(self.create_history_tab(), _('History') )
155 tabs.addTab(self.create_send_tab(), _('Send') )
156 tabs.addTab(self.create_receive_tab(), _('Receive') )
157 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
158 tabs.addTab(self.create_console_tab(), _('Console') )
159 tabs.setMinimumSize(600, 400)
160 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
161 self.setCentralWidget(tabs)
163 g = self.config.get("winpos-qt",[100, 100, 840, 400])
164 self.setGeometry(g[0], g[1], g[2], g[3])
166 self.setWindowIcon(QIcon(":icons/electrum.png"))
169 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
170 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
171 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
172 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
173 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
175 for i in range(tabs.count()):
176 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
178 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
179 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
180 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
182 self.history_list.setFocus(True)
186 self.network.register_callback('updated', lambda: self.need_update.set())
187 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
188 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
189 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
190 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
192 # set initial message
193 self.console.showMessage(self.network.banner)
200 self.config.set_key('lite_mode', False, True)
205 self.config.set_key('lite_mode', True, True)
212 if not self.check_qt_version():
213 if self.config.get('lite_mode') is True:
214 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
215 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
216 self.config.set_key('lite_mode', False, True)
222 actuator = lite_window.MiniActuator(self)
224 # Should probably not modify the current path but instead
225 # change the behaviour of rsrc(...)
226 old_path = QDir.currentPath()
227 actuator.load_theme()
229 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
231 driver = lite_window.MiniDriver(self, self.mini)
233 # Reset path back to original value now that loading the GUI
235 QDir.setCurrent(old_path)
237 if self.config.get('lite_mode') is True:
243 def check_qt_version(self):
244 qtVersion = qVersion()
245 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
248 def update_account_selector(self):
250 accounts = self.wallet.get_account_names()
251 self.account_selector.clear()
252 if len(accounts) > 1:
253 self.account_selector.addItems([_("All accounts")] + accounts.values())
254 self.account_selector.setCurrentIndex(0)
255 self.account_selector.show()
257 self.account_selector.hide()
260 def load_wallet(self, wallet):
263 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
264 self.current_account = self.wallet.storage.get("current_account", None)
266 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
267 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
268 self.setWindowTitle( title )
270 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
271 self.notify_transactions()
272 self.update_account_selector()
273 self.new_account.setEnabled(self.wallet.seed_version>4)
274 self.update_lock_icon()
275 self.update_buttons_on_seed()
276 self.update_console()
278 run_hook('load_wallet', wallet)
281 def open_wallet(self):
282 wallet_folder = self.wallet.storage.path
283 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
287 storage = WalletStorage({'wallet_path': filename})
288 if not storage.file_exists:
289 self.show_message("file not found "+ filename)
292 self.wallet.stop_threads()
295 wallet = Wallet(storage)
296 wallet.start_threads(self.network)
298 self.load_wallet(wallet)
302 def backup_wallet(self):
304 path = self.wallet.storage.path
305 wallet_folder = os.path.dirname(path)
306 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
310 new_path = os.path.join(wallet_folder, filename)
313 shutil.copy2(path, new_path)
314 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
315 except (IOError, os.error), reason:
316 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
319 def new_wallet(self):
322 wallet_folder = os.path.dirname(self.wallet.storage.path)
323 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
326 filename = os.path.join(wallet_folder, filename)
328 storage = WalletStorage({'wallet_path': filename})
329 if storage.file_exists:
330 QMessageBox.critical(None, "Error", _("File exists"))
333 wizard = installwizard.InstallWizard(self.config, self.network, storage)
334 wallet = wizard.run()
336 self.load_wallet(wallet)
340 def init_menubar(self):
343 file_menu = menubar.addMenu(_("&File"))
344 open_wallet_action = file_menu.addAction(_("&Open"))
345 open_wallet_action.setShortcut(QKeySequence.Open)
346 open_wallet_action.triggered.connect(self.open_wallet)
348 new_wallet_action = file_menu.addAction(_("&New/Restore"))
349 new_wallet_action.setShortcut(QKeySequence.New)
350 new_wallet_action.triggered.connect(self.new_wallet)
352 wallet_backup = file_menu.addAction(_("&Save Copy"))
353 wallet_backup.setShortcut(QKeySequence.SaveAs)
354 wallet_backup.triggered.connect(self.backup_wallet)
356 quit_item = file_menu.addAction(_("&Quit"))
357 #quit_item.setShortcut(QKeySequence.Quit)
358 quit_item.triggered.connect(self.close)
360 wallet_menu = menubar.addMenu(_("&Wallet"))
362 new_contact = wallet_menu.addAction(_("&New contact"))
363 new_contact.triggered.connect(self.new_contact_dialog)
365 self.new_account = wallet_menu.addAction(_("&New account"))
366 self.new_account.triggered.connect(self.new_account_dialog)
368 wallet_menu.addSeparator()
370 pw = wallet_menu.addAction(_("&Password"))
371 pw.triggered.connect(self.change_password_dialog)
373 show_seed = wallet_menu.addAction(_("&Seed"))
374 show_seed.triggered.connect(self.show_seed_dialog)
376 show_mpk = wallet_menu.addAction(_("&Master Public Key"))
377 show_mpk.triggered.connect(self.show_master_public_key)
379 wallet_menu.addSeparator()
381 labels_menu = wallet_menu.addMenu(_("&Labels"))
382 import_labels = labels_menu.addAction(_("&Import"))
383 import_labels.triggered.connect(self.do_import_labels)
384 export_labels = labels_menu.addAction(_("&Export"))
385 export_labels.triggered.connect(self.do_export_labels)
387 keys_menu = wallet_menu.addMenu(_("&Private keys"))
388 import_keys = keys_menu.addAction(_("&Import"))
389 import_keys.triggered.connect(self.do_import_privkey)
390 export_keys = keys_menu.addAction(_("&Export"))
391 export_keys.triggered.connect(self.do_export_privkeys)
393 ex_history = wallet_menu.addAction(_("&Export History"))
394 ex_history.triggered.connect(self.do_export_history)
398 tools_menu = menubar.addMenu(_("&Tools"))
400 # Settings / Preferences are all reserved keywords in OSX using this as work around
401 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
402 preferences_menu = tools_menu.addAction(preferences_name)
403 #preferences_menu.setShortcut(QKeySequence.Preferences)
404 preferences_menu.triggered.connect(self.settings_dialog)
406 network = tools_menu.addAction(_("&Network"))
407 network.triggered.connect(self.run_network_dialog)
409 plugins_labels = tools_menu.addAction(_("&Plugins"))
410 plugins_labels.triggered.connect(self.plugins_dialog)
412 tools_menu.addSeparator()
414 verifymessage = tools_menu.addAction(_("&Verify message"))
415 verifymessage.triggered.connect(lambda: self.sign_verify_message(False))
417 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
419 csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
420 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
422 csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
423 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
425 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
427 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
428 raw_transaction_file.triggered.connect(self.do_process_from_file)
430 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
431 raw_transaction_text.triggered.connect(self.do_process_from_text)
433 raw_transaction_text = raw_transaction_menu.addAction(_("&From the blockchain"))
434 raw_transaction_text.triggered.connect(self.do_process_from_txid)
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.setShortcut(QKeySequence.HelpContents)
446 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
447 report_bug = help_menu.addAction(_("&Report Bug"))
448 report_bug.triggered.connect(self.show_report_bug)
450 self.setMenuBar(menubar)
452 def show_about(self):
453 QMessageBox.about(self, "Electrum",
454 _("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."))
456 def show_report_bug(self):
457 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
458 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
461 def notify_transactions(self):
462 if not self.network or not self.network.is_connected():
465 print_error("Notifying GUI")
466 if len(self.network.interface.pending_transactions_for_notifications) > 0:
467 # Combine the transactions if there are more then three
468 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
471 for tx in self.network.interface.pending_transactions_for_notifications:
472 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
476 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
477 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
479 self.network.interface.pending_transactions_for_notifications = []
481 for tx in self.network.interface.pending_transactions_for_notifications:
483 self.network.interface.pending_transactions_for_notifications.remove(tx)
484 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
486 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
488 def notify(self, message):
489 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
493 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
494 def getOpenFileName(self, title, filter = ""):
495 directory = self.config.get('io_dir', os.path.expanduser('~'))
496 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
497 if fileName and directory != os.path.dirname(fileName):
498 self.config.set_key('io_dir', os.path.dirname(fileName), True)
501 def getSaveFileName(self, title, filename, filter = ""):
502 directory = self.config.get('io_dir', os.path.expanduser('~'))
503 path = os.path.join( directory, filename )
504 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
505 if fileName and directory != os.path.dirname(fileName):
506 self.config.set_key('io_dir', os.path.dirname(fileName), True)
510 QMainWindow.close(self)
511 run_hook('close_main_window')
513 def connect_slots(self, sender):
514 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
515 self.previous_payto_e=''
517 def timer_actions(self):
518 if self.need_update.is_set():
520 self.need_update.clear()
521 run_hook('timer_actions')
523 def format_amount(self, x, is_diff=False, whitespaces=False):
524 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
526 def read_amount(self, x):
527 if x in['.', '']: return None
528 p = pow(10, self.decimal_point)
529 return int( p * Decimal(x) )
532 assert self.decimal_point in [5,8]
533 return "BTC" if self.decimal_point == 8 else "mBTC"
536 def update_status(self):
537 if self.network is None or not self.network.is_running():
539 icon = QIcon(":icons/status_disconnected.png")
541 elif self.network.is_connected():
542 if not self.wallet.up_to_date:
543 text = _("Synchronizing...")
544 icon = QIcon(":icons/status_waiting.png")
545 elif self.network.server_lag > 1:
546 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
547 icon = QIcon(":icons/status_lagging.png")
549 c, u = self.wallet.get_account_balance(self.current_account)
550 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
551 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
554 run_hook('set_quote_text', c+u, r)
557 text += " (%s)"%quote
559 self.tray.setToolTip(text)
560 icon = QIcon(":icons/status_connected.png")
562 text = _("Not connected")
563 icon = QIcon(":icons/status_disconnected.png")
565 self.balance_label.setText(text)
566 self.status_button.setIcon( icon )
569 def update_wallet(self):
571 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
572 self.update_history_tab()
573 self.update_receive_tab()
574 self.update_contacts_tab()
575 self.update_completions()
578 def create_history_tab(self):
579 self.history_list = l = MyTreeWidget(self)
581 for i,width in enumerate(self.column_widths['history']):
582 l.setColumnWidth(i, width)
583 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
584 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
585 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
587 l.customContextMenuRequested.connect(self.create_history_menu)
591 def create_history_menu(self, position):
592 self.history_list.selectedIndexes()
593 item = self.history_list.currentItem()
595 tx_hash = str(item.data(0, Qt.UserRole).toString())
596 if not tx_hash: return
598 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
599 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
600 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
601 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
602 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
605 def show_transaction(self, tx):
606 import transaction_dialog
607 d = transaction_dialog.TxDialog(tx, self)
610 def tx_label_clicked(self, item, column):
611 if column==2 and item.isSelected():
613 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
614 self.history_list.editItem( item, column )
615 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
618 def tx_label_changed(self, item, column):
622 tx_hash = str(item.data(0, Qt.UserRole).toString())
623 tx = self.wallet.transactions.get(tx_hash)
624 text = unicode( item.text(2) )
625 self.wallet.set_label(tx_hash, text)
627 item.setForeground(2, QBrush(QColor('black')))
629 text = self.wallet.get_default_label(tx_hash)
630 item.setText(2, text)
631 item.setForeground(2, QBrush(QColor('gray')))
635 def edit_label(self, is_recv):
636 l = self.receive_list if is_recv else self.contacts_list
637 item = l.currentItem()
638 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
639 l.editItem( item, 1 )
640 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
644 def address_label_clicked(self, item, column, l, column_addr, column_label):
645 if column == column_label and item.isSelected():
646 is_editable = item.data(0, 32).toBool()
649 addr = unicode( item.text(column_addr) )
650 label = unicode( item.text(column_label) )
651 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
652 l.editItem( item, column )
653 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
656 def address_label_changed(self, item, column, l, column_addr, column_label):
657 if column == column_label:
658 addr = unicode( item.text(column_addr) )
659 text = unicode( item.text(column_label) )
660 is_editable = item.data(0, 32).toBool()
664 changed = self.wallet.set_label(addr, text)
666 self.update_history_tab()
667 self.update_completions()
669 self.current_item_changed(item)
671 run_hook('item_changed', item, column)
674 def current_item_changed(self, a):
675 run_hook('current_item_changed', a)
679 def update_history_tab(self):
681 self.history_list.clear()
682 for item in self.wallet.get_tx_history(self.current_account):
683 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
684 time_str = _("unknown")
687 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
689 time_str = _("error")
692 time_str = 'unverified'
693 icon = QIcon(":icons/unconfirmed.png")
696 icon = QIcon(":icons/unconfirmed.png")
698 icon = QIcon(":icons/clock%d.png"%conf)
700 icon = QIcon(":icons/confirmed.png")
702 if value is not None:
703 v_str = self.format_amount(value, True, whitespaces=True)
707 balance_str = self.format_amount(balance, whitespaces=True)
710 label, is_default_label = self.wallet.get_label(tx_hash)
712 label = _('Pruned transaction outputs')
713 is_default_label = False
715 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
716 item.setFont(2, QFont(MONOSPACE_FONT))
717 item.setFont(3, QFont(MONOSPACE_FONT))
718 item.setFont(4, QFont(MONOSPACE_FONT))
720 item.setForeground(3, QBrush(QColor("#BC1E1E")))
722 item.setData(0, Qt.UserRole, tx_hash)
723 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
725 item.setForeground(2, QBrush(QColor('grey')))
727 item.setIcon(0, icon)
728 self.history_list.insertTopLevelItem(0,item)
731 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
732 run_hook('history_tab_update')
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)
760 self.from_label = QLabel(_('From'))
761 grid.addWidget(self.from_label, 3, 0)
762 self.from_list = QTreeWidget(self)
763 self.from_list.setColumnCount(2)
764 self.from_list.setColumnWidth(0, 350)
765 self.from_list.setColumnWidth(1, 50)
766 self.from_list.setHeaderHidden (True)
767 self.from_list.setMaximumHeight(80)
768 grid.addWidget(self.from_list, 3, 1, 1, 3)
769 self.set_pay_from([])
771 self.amount_e = AmountEdit(self.base_unit)
772 grid.addWidget(QLabel(_('Amount')), 4, 0)
773 grid.addWidget(self.amount_e, 4, 1, 1, 2)
774 grid.addWidget(HelpButton(
775 _('Amount to be sent.') + '\n\n' \
776 + _('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.') \
777 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
779 self.fee_e = AmountEdit(self.base_unit)
780 grid.addWidget(QLabel(_('Fee')), 5, 0)
781 grid.addWidget(self.fee_e, 5, 1, 1, 2)
782 grid.addWidget(HelpButton(
783 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
784 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
785 + _('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)
788 self.send_button = EnterButton(_("Send"), self.do_send)
789 grid.addWidget(self.send_button, 6, 1)
791 b = EnterButton(_("Clear"),self.do_clear)
792 grid.addWidget(b, 6, 2)
794 self.payto_sig = QLabel('')
795 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
797 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
798 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
807 def entry_changed( is_fee ):
808 self.funds_error = False
810 if self.amount_e.is_shortcut:
811 self.amount_e.is_shortcut = False
812 sendable = self.get_sendable_balance()
813 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, self.get_payment_sources())
814 fee = self.wallet.estimated_fee(inputs)
816 self.amount_e.setText( self.format_amount(amount) )
817 self.fee_e.setText( self.format_amount( fee ) )
820 amount = self.read_amount(str(self.amount_e.text()))
821 fee = self.read_amount(str(self.fee_e.text()))
823 if not is_fee: fee = None
826 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, self.get_payment_sources())
828 self.fee_e.setText( self.format_amount( fee ) )
831 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
835 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
836 self.funds_error = True
837 text = _( "Not enough funds" )
838 c, u = self.wallet.get_frozen_balance()
839 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
841 self.statusBar().showMessage(text)
842 self.amount_e.setPalette(palette)
843 self.fee_e.setPalette(palette)
845 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
846 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
848 run_hook('create_send_tab', grid)
852 def set_pay_from(self, l):
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 confirm_fee = self.config.get('confirm_fee', 100000)
908 if fee >= confirm_fee:
909 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()}):
912 self.send_tx(to_address, amount, fee, label)
916 def send_tx(self, to_address, amount, fee, label, password):
918 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
919 domain=self.get_payment_sources())
920 except Exception as e:
921 traceback.print_exc(file=sys.stdout)
922 self.show_message(str(e))
925 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
926 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
930 self.wallet.set_label(tx.hash(), label)
933 h = self.wallet.send_tx(tx)
934 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
935 status, msg = self.wallet.receive_tx( h, tx )
937 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
939 self.update_contacts_tab()
941 QMessageBox.warning(self, _('Error'), msg, _('OK'))
944 self.show_transaction(tx)
946 # add recipient to addressbook
947 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
948 self.wallet.addressbook.append(to_address)
953 def set_url(self, url):
954 address, amount, label, message, signature, identity, url = util.parse_url(url)
957 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
958 elif amount: amount = str(Decimal(amount))
961 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
964 self.mini.set_payment_fields(address, amount)
966 if label and self.wallet.labels.get(address) != label:
967 if self.question('Give label "%s" to address %s ?'%(label,address)):
968 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
969 self.wallet.addressbook.append(address)
970 self.wallet.set_label(address, label)
972 run_hook('set_url', url, self.show_message, self.question)
974 self.tabs.setCurrentIndex(1)
975 label = self.wallet.labels.get(address)
976 m_addr = label + ' <'+ address +'>' if label else address
977 self.payto_e.setText(m_addr)
979 self.message_e.setText(message)
981 self.amount_e.setText(amount)
984 self.set_frozen(self.payto_e,True)
985 self.set_frozen(self.amount_e,True)
986 self.set_frozen(self.message_e,True)
987 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
989 self.payto_sig.setVisible(False)
992 self.payto_sig.setVisible(False)
993 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
995 self.set_frozen(e,False)
997 self.set_pay_from([])
1000 def set_frozen(self,entry,frozen):
1002 entry.setReadOnly(True)
1003 entry.setFrame(False)
1004 palette = QPalette()
1005 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1006 entry.setPalette(palette)
1008 entry.setReadOnly(False)
1009 entry.setFrame(True)
1010 palette = QPalette()
1011 palette.setColor(entry.backgroundRole(), QColor('white'))
1012 entry.setPalette(palette)
1015 def set_addrs_frozen(self,addrs,freeze):
1017 if not addr: continue
1018 if addr in self.wallet.frozen_addresses and not freeze:
1019 self.wallet.unfreeze(addr)
1020 elif addr not in self.wallet.frozen_addresses and freeze:
1021 self.wallet.freeze(addr)
1022 self.update_receive_tab()
1026 def create_list_tab(self, headers):
1027 "generic tab creation method"
1028 l = MyTreeWidget(self)
1029 l.setColumnCount( len(headers) )
1030 l.setHeaderLabels( headers )
1033 vbox = QVBoxLayout()
1040 vbox.addWidget(buttons)
1042 hbox = QHBoxLayout()
1045 buttons.setLayout(hbox)
1050 def create_receive_tab(self):
1051 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1052 l.setContextMenuPolicy(Qt.CustomContextMenu)
1053 l.customContextMenuRequested.connect(self.create_receive_menu)
1054 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1055 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1056 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1057 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1058 self.receive_list = l
1059 self.receive_buttons_hbox = hbox
1066 def save_column_widths(self):
1067 self.column_widths["receive"] = []
1068 for i in range(self.receive_list.columnCount() -1):
1069 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1071 self.column_widths["history"] = []
1072 for i in range(self.history_list.columnCount() - 1):
1073 self.column_widths["history"].append(self.history_list.columnWidth(i))
1075 self.column_widths["contacts"] = []
1076 for i in range(self.contacts_list.columnCount() - 1):
1077 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1079 self.config.set_key("column_widths_2", self.column_widths, True)
1082 def create_contacts_tab(self):
1083 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1084 l.setContextMenuPolicy(Qt.CustomContextMenu)
1085 l.customContextMenuRequested.connect(self.create_contact_menu)
1086 for i,width in enumerate(self.column_widths['contacts']):
1087 l.setColumnWidth(i, width)
1089 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1090 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1091 self.contacts_list = l
1092 self.contacts_buttons_hbox = hbox
1097 def delete_imported_key(self, addr):
1098 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1099 self.wallet.delete_imported_key(addr)
1100 self.update_receive_tab()
1101 self.update_history_tab()
1103 def edit_account_label(self, k):
1104 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1106 label = unicode(text)
1107 self.wallet.set_label(k,label)
1108 self.update_receive_tab()
1110 def account_set_expanded(self, item, k, b):
1112 self.accounts_expanded[k] = b
1114 def create_account_menu(self, position, k, item):
1116 if item.isExpanded():
1117 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1119 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1120 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1121 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1122 if self.wallet.account_is_pending(k):
1123 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1124 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1126 def delete_pending_account(self, k):
1127 self.wallet.delete_pending_account(k)
1128 self.update_receive_tab()
1130 def create_receive_menu(self, position):
1131 # fixme: this function apparently has a side effect.
1132 # if it is not called the menu pops up several times
1133 #self.receive_list.selectedIndexes()
1135 selected = self.receive_list.selectedItems()
1136 multi_select = len(selected) > 1
1137 addrs = [unicode(item.text(0)) for item in selected]
1138 if not multi_select:
1139 item = self.receive_list.itemAt(position)
1143 if not is_valid(addr):
1144 k = str(item.data(0,32).toString())
1146 self.create_account_menu(position, k, item)
1148 item.setExpanded(not item.isExpanded())
1152 if not multi_select:
1153 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1154 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1155 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1156 if self.wallet.seed:
1157 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1158 menu.addAction(_("Sign message"), lambda: self.sign_verify_message(True,addr))
1159 if addr in self.wallet.imported_keys:
1160 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1162 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1163 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1164 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1165 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1167 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1168 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1170 run_hook('receive_menu', menu, addrs)
1171 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1174 def get_sendable_balance(self):
1175 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1178 def get_payment_sources(self):
1180 return self.pay_from
1182 return self.wallet.get_account_addresses(self.current_account)
1185 def send_from_addresses(self, addrs):
1186 self.set_pay_from( addrs )
1187 self.tabs.setCurrentIndex(1)
1190 def payto(self, addr):
1192 label = self.wallet.labels.get(addr)
1193 m_addr = label + ' <' + addr + '>' if label else addr
1194 self.tabs.setCurrentIndex(1)
1195 self.payto_e.setText(m_addr)
1196 self.amount_e.setFocus()
1199 def delete_contact(self, x):
1200 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1201 self.wallet.delete_contact(x)
1202 self.wallet.set_label(x, None)
1203 self.update_history_tab()
1204 self.update_contacts_tab()
1205 self.update_completions()
1208 def create_contact_menu(self, position):
1209 item = self.contacts_list.itemAt(position)
1212 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1214 addr = unicode(item.text(0))
1215 label = unicode(item.text(1))
1216 is_editable = item.data(0,32).toBool()
1217 payto_addr = item.data(0,33).toString()
1218 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1219 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1220 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1222 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1223 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1225 run_hook('create_contact_menu', menu, item)
1226 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1229 def update_receive_item(self, item):
1230 item.setFont(0, QFont(MONOSPACE_FONT))
1231 address = str(item.data(0,0).toString())
1232 label = self.wallet.labels.get(address,'')
1233 item.setData(1,0,label)
1234 item.setData(0,32, True) # is editable
1236 run_hook('update_receive_item', address, item)
1238 if not self.wallet.is_mine(address): return
1240 c, u = self.wallet.get_addr_balance(address)
1241 balance = self.format_amount(c + u)
1242 item.setData(2,0,balance)
1244 if address in self.wallet.frozen_addresses:
1245 item.setBackgroundColor(0, QColor('lightblue'))
1248 def update_receive_tab(self):
1249 l = self.receive_list
1252 l.setColumnHidden(2, False)
1253 l.setColumnHidden(3, False)
1254 for i,width in enumerate(self.column_widths['receive']):
1255 l.setColumnWidth(i, width)
1257 if self.current_account is None:
1258 account_items = self.wallet.accounts.items()
1259 elif self.current_account != -1:
1260 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1264 for k, account in account_items:
1265 name = self.wallet.get_account_name(k)
1266 c,u = self.wallet.get_account_balance(k)
1267 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1268 l.addTopLevelItem(account_item)
1269 account_item.setExpanded(self.accounts_expanded.get(k, True))
1270 account_item.setData(0, 32, k)
1272 if not self.wallet.is_seeded(k):
1273 icon = QIcon(":icons/key.png")
1274 account_item.setIcon(0, icon)
1276 for is_change in ([0,1]):
1277 name = _("Receiving") if not is_change else _("Change")
1278 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1279 account_item.addChild(seq_item)
1280 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1282 if not is_change: seq_item.setExpanded(True)
1287 for address in account.get_addresses(is_change):
1288 h = self.wallet.history.get(address,[])
1292 if gap > self.wallet.gap_limit:
1297 c, u = self.wallet.get_addr_balance(address)
1298 num_tx = '*' if h == ['*'] else "%d"%len(h)
1299 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1300 self.update_receive_item(item)
1302 item.setBackgroundColor(1, QColor('red'))
1303 if len(h) > 0 and c == -u:
1305 seq_item.insertChild(0,used_item)
1307 used_item.addChild(item)
1309 seq_item.addChild(item)
1312 for k, addr in self.wallet.get_pending_accounts():
1313 name = self.wallet.labels.get(k,'')
1314 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1315 self.update_receive_item(item)
1316 l.addTopLevelItem(account_item)
1317 account_item.setExpanded(True)
1318 account_item.setData(0, 32, k)
1319 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1320 account_item.addChild(item)
1321 self.update_receive_item(item)
1324 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1325 c,u = self.wallet.get_imported_balance()
1326 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1327 l.addTopLevelItem(account_item)
1328 account_item.setExpanded(True)
1329 for address in self.wallet.imported_keys.keys():
1330 item = QTreeWidgetItem( [ address, '', '', ''] )
1331 self.update_receive_item(item)
1332 account_item.addChild(item)
1335 # we use column 1 because column 0 may be hidden
1336 l.setCurrentItem(l.topLevelItem(0),1)
1339 def update_contacts_tab(self):
1340 l = self.contacts_list
1343 for address in self.wallet.addressbook:
1344 label = self.wallet.labels.get(address,'')
1345 n = self.wallet.get_num_tx(address)
1346 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1347 item.setFont(0, QFont(MONOSPACE_FONT))
1348 # 32 = label can be edited (bool)
1349 item.setData(0,32, True)
1351 item.setData(0,33, address)
1352 l.addTopLevelItem(item)
1354 run_hook('update_contacts_tab', l)
1355 l.setCurrentItem(l.topLevelItem(0))
1359 def create_console_tab(self):
1360 from console import Console
1361 self.console = console = Console()
1365 def update_console(self):
1366 console = self.console
1367 console.history = self.config.get("console-history",[])
1368 console.history_index = len(console.history)
1370 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1371 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1373 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1375 def mkfunc(f, method):
1376 return lambda *args: apply( f, (method, args, self.password_dialog ))
1378 if m[0]=='_' or m in ['network','wallet']: continue
1379 methods[m] = mkfunc(c._run, m)
1381 console.updateNamespace(methods)
1384 def change_account(self,s):
1385 if s == _("All accounts"):
1386 self.current_account = None
1388 accounts = self.wallet.get_account_names()
1389 for k, v in accounts.items():
1391 self.current_account = k
1392 self.update_history_tab()
1393 self.update_status()
1394 self.update_receive_tab()
1396 def create_status_bar(self):
1399 sb.setFixedHeight(35)
1400 qtVersion = qVersion()
1402 self.balance_label = QLabel("")
1403 sb.addWidget(self.balance_label)
1405 from version_getter import UpdateLabel
1406 self.updatelabel = UpdateLabel(self.config, sb)
1408 self.account_selector = QComboBox()
1409 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1410 sb.addPermanentWidget(self.account_selector)
1412 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1413 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1415 self.lock_icon = QIcon()
1416 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1417 sb.addPermanentWidget( self.password_button )
1419 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1420 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1421 sb.addPermanentWidget( self.seed_button )
1422 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1423 sb.addPermanentWidget( self.status_button )
1425 run_hook('create_status_bar', (sb,))
1427 self.setStatusBar(sb)
1430 def update_lock_icon(self):
1431 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1432 self.password_button.setIcon( icon )
1435 def update_buttons_on_seed(self):
1436 if not self.wallet.is_watching_only():
1437 self.seed_button.show()
1438 self.password_button.show()
1439 self.send_button.setText(_("Send"))
1441 self.password_button.hide()
1442 self.seed_button.hide()
1443 self.send_button.setText(_("Create unsigned transaction"))
1446 def change_password_dialog(self):
1447 from password_dialog import PasswordDialog
1448 d = PasswordDialog(self.wallet, self)
1450 self.update_lock_icon()
1453 def new_contact_dialog(self):
1456 vbox = QVBoxLayout(d)
1457 vbox.addWidget(QLabel(_('New Contact')+':'))
1459 grid = QGridLayout()
1462 grid.addWidget(QLabel(_("Address")), 1, 0)
1463 grid.addWidget(line1, 1, 1)
1464 grid.addWidget(QLabel(_("Name")), 2, 0)
1465 grid.addWidget(line2, 2, 1)
1467 vbox.addLayout(grid)
1468 vbox.addLayout(ok_cancel_buttons(d))
1473 address = str(line1.text())
1474 label = unicode(line2.text())
1476 if not is_valid(address):
1477 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1480 self.wallet.add_contact(address)
1482 self.wallet.set_label(address, label)
1484 self.update_contacts_tab()
1485 self.update_history_tab()
1486 self.update_completions()
1487 self.tabs.setCurrentIndex(3)
1490 def new_account_dialog(self):
1492 dialog = QDialog(self)
1494 dialog.setWindowTitle(_("New Account"))
1496 vbox = QVBoxLayout()
1497 vbox.addWidget(QLabel(_('Account name')+':'))
1500 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1501 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1506 vbox.addLayout(ok_cancel_buttons(dialog))
1507 dialog.setLayout(vbox)
1511 name = str(e.text())
1514 self.wallet.create_pending_account('1', name)
1515 self.update_receive_tab()
1516 self.tabs.setCurrentIndex(2)
1520 def show_master_public_key_old(self):
1521 dialog = QDialog(self)
1523 dialog.setWindowTitle(_("Master Public Key"))
1525 main_text = QTextEdit()
1526 main_text.setText(self.wallet.get_master_public_key())
1527 main_text.setReadOnly(True)
1528 main_text.setMaximumHeight(170)
1529 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1531 ok_button = QPushButton(_("OK"))
1532 ok_button.setDefault(True)
1533 ok_button.clicked.connect(dialog.accept)
1535 main_layout = QGridLayout()
1536 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1538 main_layout.addWidget(main_text, 1, 0)
1539 main_layout.addWidget(qrw, 1, 1 )
1541 vbox = QVBoxLayout()
1542 vbox.addLayout(main_layout)
1543 vbox.addLayout(close_button(dialog))
1544 dialog.setLayout(vbox)
1548 def show_master_public_key(self):
1550 if self.wallet.seed_version == 4:
1551 self.show_master_public_key_old()
1554 dialog = QDialog(self)
1556 dialog.setWindowTitle(_("Master Public Keys"))
1558 chain_text = QTextEdit()
1559 chain_text.setReadOnly(True)
1560 chain_text.setMaximumHeight(170)
1561 chain_qrw = QRCodeWidget()
1563 mpk_text = QTextEdit()
1564 mpk_text.setReadOnly(True)
1565 mpk_text.setMaximumHeight(170)
1566 mpk_qrw = QRCodeWidget()
1568 main_layout = QGridLayout()
1570 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1571 main_layout.addWidget(mpk_text, 1, 1)
1572 main_layout.addWidget(mpk_qrw, 1, 2)
1574 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1575 main_layout.addWidget(chain_text, 2, 1)
1576 main_layout.addWidget(chain_qrw, 2, 2)
1579 c, K, cK = self.wallet.master_public_keys[str(key)]
1580 chain_text.setText(c)
1581 chain_qrw.set_addr(c)
1582 chain_qrw.update_qr()
1587 key_selector = QComboBox()
1588 keys = sorted(self.wallet.master_public_keys.keys())
1589 key_selector.addItems(keys)
1591 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1592 main_layout.addWidget(key_selector, 0, 1)
1593 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1597 vbox = QVBoxLayout()
1598 vbox.addLayout(main_layout)
1599 vbox.addLayout(close_button(dialog))
1601 dialog.setLayout(vbox)
1606 def show_seed_dialog(self, password):
1607 if self.wallet.is_watching_only():
1608 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1611 if self.wallet.seed:
1613 mnemonic = self.wallet.get_mnemonic(password)
1615 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1617 from seed_dialog import SeedDialog
1618 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1622 for k in self.wallet.master_private_keys.keys():
1623 pk = self.wallet.get_master_private_key(k, password)
1625 from seed_dialog import PrivateKeysDialog
1626 d = PrivateKeysDialog(self,l)
1633 def show_qrcode(self, data, title = _("QR code")):
1637 d.setWindowTitle(title)
1638 d.setMinimumSize(270, 300)
1639 vbox = QVBoxLayout()
1640 qrw = QRCodeWidget(data)
1641 vbox.addWidget(qrw, 1)
1642 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1643 hbox = QHBoxLayout()
1646 filename = os.path.join(self.config.path, "qrcode.bmp")
1649 bmp.save_qrcode(qrw.qr, filename)
1650 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1652 def copy_to_clipboard():
1653 bmp.save_qrcode(qrw.qr, filename)
1654 self.app.clipboard().setImage(QImage(filename))
1655 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1657 b = QPushButton(_("Copy"))
1659 b.clicked.connect(copy_to_clipboard)
1661 b = QPushButton(_("Save"))
1663 b.clicked.connect(print_qr)
1665 b = QPushButton(_("Close"))
1667 b.clicked.connect(d.accept)
1670 vbox.addLayout(hbox)
1675 def do_protect(self, func, args):
1676 if self.wallet.use_encryption:
1677 password = self.password_dialog()
1683 if args != (False,):
1684 args = (self,) + args + (password,)
1686 args = (self,password)
1691 def show_private_key(self, address, password):
1692 if not address: return
1694 pk_list = self.wallet.get_private_key(address, password)
1695 except Exception as e:
1696 self.show_message(str(e))
1700 d.setMinimumSize(600, 200)
1702 vbox = QVBoxLayout()
1703 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1704 vbox.addWidget( QLabel(_("Private key") + ':'))
1706 keys.setReadOnly(True)
1707 keys.setText('\n'.join(pk_list))
1708 vbox.addWidget(keys)
1709 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1710 vbox.addLayout(close_button(d))
1716 def do_sign(self, address, message, signature, password):
1717 message = unicode(message.toPlainText())
1718 message = message.encode('utf-8')
1720 sig = self.wallet.sign_message(str(address.text()), message, password)
1721 signature.setText(sig)
1722 except Exception as e:
1723 self.show_message(str(e))
1725 def do_verify(self, address, message, signature):
1726 message = unicode(message.toPlainText())
1727 message = message.encode('utf-8')
1728 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1729 self.show_message(_("Signature verified"))
1731 self.show_message(_("Error: wrong signature"))
1734 def sign_verify_message(self, sign, address=''):
1735 if sign and not address: return
1738 d.setWindowTitle(_('Sign Message') if sign else _('Verify Message'))
1739 d.setMinimumSize(410, 290)
1741 layout = QGridLayout(d)
1743 address_e = QLineEdit()
1744 address_e.setText(address)
1745 layout.addWidget(QLabel(_('Address')), 1, 0)
1746 layout.addWidget(address_e, 1, 1)
1748 message_e = QTextEdit()
1749 layout.addWidget(QLabel(_('Message')), 2, 0)
1750 layout.addWidget(message_e, 2, 1)
1751 layout.setRowStretch(2,3)
1753 signature_e = QTextEdit()
1754 layout.addWidget(QLabel(_('Signature')), 3, 0)
1755 layout.addWidget(signature_e, 3, 1)
1756 layout.setRowStretch(3,1)
1758 hbox = QHBoxLayout()
1759 b = QPushButton(_("Sign") if sign else _("Verify"))
1761 f = self.do_sign if sign else self.do_verify
1762 b.clicked.connect(lambda: f(address_e, message_e, signature_e))
1763 b = QPushButton(_("Close"))
1764 b.clicked.connect(d.accept)
1766 layout.addLayout(hbox, 4, 1)
1770 def question(self, msg):
1771 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1773 def show_message(self, msg):
1774 QMessageBox.information(self, _('Message'), msg, _('OK'))
1776 def password_dialog(self ):
1783 vbox = QVBoxLayout()
1784 msg = _('Please enter your password')
1785 vbox.addWidget(QLabel(msg))
1787 grid = QGridLayout()
1789 grid.addWidget(QLabel(_('Password')), 1, 0)
1790 grid.addWidget(pw, 1, 1)
1791 vbox.addLayout(grid)
1793 vbox.addLayout(ok_cancel_buttons(d))
1796 run_hook('password_dialog', pw, grid, 1)
1797 if not d.exec_(): return
1798 return unicode(pw.text())
1807 def tx_from_text(self, txt):
1808 "json or raw hexadecimal"
1811 tx = Transaction(txt)
1817 tx_dict = json.loads(str(txt))
1818 assert "hex" in tx_dict.keys()
1819 assert "complete" in tx_dict.keys()
1820 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1821 if not tx_dict["complete"]:
1822 assert "input_info" in tx_dict.keys()
1823 input_info = json.loads(tx_dict['input_info'])
1824 tx.add_input_info(input_info)
1829 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1833 def read_tx_from_file(self):
1834 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1838 with open(fileName, "r") as f:
1839 file_content = f.read()
1840 except (ValueError, IOError, os.error), reason:
1841 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1843 return self.tx_from_text(file_content)
1847 def sign_raw_transaction(self, tx, input_info, password):
1848 self.wallet.signrawtransaction(tx, input_info, [], password)
1850 def do_process_from_text(self):
1851 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1854 tx = self.tx_from_text(text)
1856 self.show_transaction(tx)
1858 def do_process_from_file(self):
1859 tx = self.read_tx_from_file()
1861 self.show_transaction(tx)
1863 def do_process_from_txid(self):
1864 from electrum import transaction
1865 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1867 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1869 tx = transaction.Transaction(r)
1871 self.show_transaction(tx)
1873 self.show_message("unknown transaction")
1875 def do_process_from_csvReader(self, csvReader):
1880 for position, row in enumerate(csvReader):
1882 if not is_valid(address):
1883 errors.append((position, address))
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 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1894 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1898 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1899 except Exception as e:
1900 self.show_message(str(e))
1903 self.show_transaction(tx)
1905 def do_process_from_csv_file(self):
1906 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1910 with open(fileName, "r") as f:
1911 csvReader = csv.reader(f)
1912 self.do_process_from_csvReader(csvReader)
1913 except (ValueError, IOError, os.error), reason:
1914 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1917 def do_process_from_csv_text(self):
1918 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1919 + _("Format: address, amount. One output per line"), _("Load CSV"))
1922 f = StringIO.StringIO(text)
1923 csvReader = csv.reader(f)
1924 self.do_process_from_csvReader(csvReader)
1929 def do_export_privkeys(self, password):
1930 if not self.wallet.seed:
1931 self.show_message(_("This wallet has no seed"))
1934 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.")))
1937 select_export = _('Select file to export your private keys to')
1938 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1940 with open(fileName, "w+") as csvfile:
1941 transaction = csv.writer(csvfile)
1942 transaction.writerow(["address", "private_key"])
1944 addresses = self.wallet.addresses(True)
1946 for addr in addresses:
1947 pk = "".join(self.wallet.get_private_key(addr, password))
1948 transaction.writerow(["%34s"%addr,pk])
1950 self.show_message(_("Private keys exported."))
1952 except (IOError, os.error), reason:
1953 export_error_label = _("Electrum was unable to produce a private key-export.")
1954 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1956 except Exception as e:
1957 self.show_message(str(e))
1961 def do_import_labels(self):
1962 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1963 if not labelsFile: return
1965 f = open(labelsFile, 'r')
1968 for key, value in json.loads(data).items():
1969 self.wallet.set_label(key, value)
1970 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1971 except (IOError, os.error), reason:
1972 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1975 def do_export_labels(self):
1976 labels = self.wallet.labels
1978 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1980 with open(fileName, 'w+') as f:
1981 json.dump(labels, f)
1982 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1983 except (IOError, os.error), reason:
1984 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1987 def do_export_history(self):
1988 from lite_window import csv_transaction
1989 csv_transaction(self.wallet)
1993 def do_import_privkey(self, password):
1994 if not self.wallet.imported_keys:
1995 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1996 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1997 + _('Are you sure you understand what you are doing?'), 3, 4)
2000 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2003 text = str(text).split()
2008 addr = self.wallet.import_key(key, password)
2009 except Exception as e:
2015 addrlist.append(addr)
2017 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2019 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2020 self.update_receive_tab()
2021 self.update_history_tab()
2024 def settings_dialog(self):
2026 d.setWindowTitle(_('Electrum Settings'))
2028 vbox = QVBoxLayout()
2029 grid = QGridLayout()
2030 grid.setColumnStretch(0,1)
2032 nz_label = QLabel(_('Display zeros') + ':')
2033 grid.addWidget(nz_label, 0, 0)
2034 nz_e = AmountEdit(None,True)
2035 nz_e.setText("%d"% self.num_zeros)
2036 grid.addWidget(nz_e, 0, 1)
2037 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2038 grid.addWidget(HelpButton(msg), 0, 2)
2039 if not self.config.is_modifiable('num_zeros'):
2040 for w in [nz_e, nz_label]: w.setEnabled(False)
2042 lang_label=QLabel(_('Language') + ':')
2043 grid.addWidget(lang_label, 1, 0)
2044 lang_combo = QComboBox()
2045 from electrum.i18n import languages
2046 lang_combo.addItems(languages.values())
2048 index = languages.keys().index(self.config.get("language",''))
2051 lang_combo.setCurrentIndex(index)
2052 grid.addWidget(lang_combo, 1, 1)
2053 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2054 if not self.config.is_modifiable('language'):
2055 for w in [lang_combo, lang_label]: w.setEnabled(False)
2058 fee_label = QLabel(_('Transaction fee') + ':')
2059 grid.addWidget(fee_label, 2, 0)
2060 fee_e = AmountEdit(self.base_unit)
2061 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2062 grid.addWidget(fee_e, 2, 1)
2063 msg = _('Fee per kilobyte of transaction.') + ' ' \
2064 + _('Recommended value') + ': ' + self.format_amount(20000)
2065 grid.addWidget(HelpButton(msg), 2, 2)
2066 if not self.config.is_modifiable('fee_per_kb'):
2067 for w in [fee_e, fee_label]: w.setEnabled(False)
2069 units = ['BTC', 'mBTC']
2070 unit_label = QLabel(_('Base unit') + ':')
2071 grid.addWidget(unit_label, 3, 0)
2072 unit_combo = QComboBox()
2073 unit_combo.addItems(units)
2074 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2075 grid.addWidget(unit_combo, 3, 1)
2076 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2077 + '\n1BTC=1000mBTC.\n' \
2078 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2080 usechange_cb = QCheckBox(_('Use change addresses'))
2081 usechange_cb.setChecked(self.wallet.use_change)
2082 grid.addWidget(usechange_cb, 4, 0)
2083 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2084 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2086 grid.setRowStretch(5,1)
2088 vbox.addLayout(grid)
2089 vbox.addLayout(ok_cancel_buttons(d))
2093 if not d.exec_(): return
2095 fee = unicode(fee_e.text())
2097 fee = self.read_amount(fee)
2099 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2102 self.wallet.set_fee(fee)
2104 nz = unicode(nz_e.text())
2109 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2112 if self.num_zeros != nz:
2114 self.config.set_key('num_zeros', nz, True)
2115 self.update_history_tab()
2116 self.update_receive_tab()
2118 usechange_result = usechange_cb.isChecked()
2119 if self.wallet.use_change != usechange_result:
2120 self.wallet.use_change = usechange_result
2121 self.wallet.storage.put('use_change', self.wallet.use_change)
2123 unit_result = units[unit_combo.currentIndex()]
2124 if self.base_unit() != unit_result:
2125 self.decimal_point = 8 if unit_result == 'BTC' else 5
2126 self.config.set_key('decimal_point', self.decimal_point, True)
2127 self.update_history_tab()
2128 self.update_status()
2130 need_restart = False
2132 lang_request = languages.keys()[lang_combo.currentIndex()]
2133 if lang_request != self.config.get('language'):
2134 self.config.set_key("language", lang_request, True)
2137 run_hook('close_settings_dialog')
2140 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2143 def run_network_dialog(self):
2144 if not self.network:
2146 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2148 def closeEvent(self, event):
2151 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2152 self.save_column_widths()
2153 self.config.set_key("console-history", self.console.history[-50:], True)
2154 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2158 def plugins_dialog(self):
2159 from electrum.plugins import plugins
2162 d.setWindowTitle(_('Electrum Plugins'))
2165 vbox = QVBoxLayout(d)
2168 scroll = QScrollArea()
2169 scroll.setEnabled(True)
2170 scroll.setWidgetResizable(True)
2171 scroll.setMinimumSize(400,250)
2172 vbox.addWidget(scroll)
2176 w.setMinimumHeight(len(plugins)*35)
2178 grid = QGridLayout()
2179 grid.setColumnStretch(0,1)
2182 def do_toggle(cb, p, w):
2185 if w: w.setEnabled(r)
2187 def mk_toggle(cb, p, w):
2188 return lambda: do_toggle(cb,p,w)
2190 for i, p in enumerate(plugins):
2192 cb = QCheckBox(p.fullname())
2193 cb.setDisabled(not p.is_available())
2194 cb.setChecked(p.is_enabled())
2195 grid.addWidget(cb, i, 0)
2196 if p.requires_settings():
2197 w = p.settings_widget(self)
2198 w.setEnabled( p.is_enabled() )
2199 grid.addWidget(w, i, 1)
2202 cb.clicked.connect(mk_toggle(cb,p,w))
2203 grid.addWidget(HelpButton(p.description()), i, 2)
2205 print_msg(_("Error: cannot display plugin"), p)
2206 traceback.print_exc(file=sys.stdout)
2207 grid.setRowStretch(i+1,1)
2209 vbox.addLayout(close_button(d))
2214 def show_account_details(self, k):
2216 d.setWindowTitle(_('Account Details'))
2219 vbox = QVBoxLayout(d)
2220 roots = self.wallet.get_roots(k)
2222 name = self.wallet.get_account_name(k)
2223 label = QLabel('Name: ' + name)
2224 vbox.addWidget(label)
2226 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2227 vbox.addWidget(QLabel('Type: ' + acctype))
2229 label = QLabel('Derivation: ' + k)
2230 vbox.addWidget(label)
2233 # mpk = self.wallet.master_public_keys[root]
2234 # text = QTextEdit()
2235 # text.setReadOnly(True)
2236 # text.setMaximumHeight(120)
2237 # text.setText(repr(mpk))
2238 # vbox.addWidget(text)
2240 vbox.addLayout(close_button(d))