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 verifymessage = tools_menu.addAction(_("&Verify message"))
413 verifymessage.triggered.connect(lambda: self.sign_verify_message(False))
415 tools_menu.addSeparator()
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))
734 def create_send_tab(self):
739 grid.setColumnMinimumWidth(3,300)
740 grid.setColumnStretch(5,1)
743 self.payto_e = QLineEdit()
744 grid.addWidget(QLabel(_('Pay to')), 1, 0)
745 grid.addWidget(self.payto_e, 1, 1, 1, 3)
747 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)
749 completer = QCompleter()
750 completer.setCaseSensitivity(False)
751 self.payto_e.setCompleter(completer)
752 completer.setModel(self.completions)
754 self.message_e = QLineEdit()
755 grid.addWidget(QLabel(_('Description')), 2, 0)
756 grid.addWidget(self.message_e, 2, 1, 1, 3)
757 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)
759 self.from_label = QLabel(_('From'))
760 grid.addWidget(self.from_label, 3, 0)
761 self.from_list = QTreeWidget(self)
762 self.from_list.setColumnCount(2)
763 self.from_list.setColumnWidth(0, 350)
764 self.from_list.setColumnWidth(1, 50)
765 self.from_list.setHeaderHidden (True)
766 self.from_list.setMaximumHeight(80)
767 grid.addWidget(self.from_list, 3, 1, 1, 3)
768 self.set_pay_from([])
770 self.amount_e = AmountEdit(self.base_unit)
771 grid.addWidget(QLabel(_('Amount')), 4, 0)
772 grid.addWidget(self.amount_e, 4, 1, 1, 2)
773 grid.addWidget(HelpButton(
774 _('Amount to be sent.') + '\n\n' \
775 + _('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.') \
776 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
778 self.fee_e = AmountEdit(self.base_unit)
779 grid.addWidget(QLabel(_('Fee')), 5, 0)
780 grid.addWidget(self.fee_e, 5, 1, 1, 2)
781 grid.addWidget(HelpButton(
782 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
783 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
784 + _('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)
787 self.send_button = EnterButton(_("Send"), self.do_send)
788 grid.addWidget(self.send_button, 6, 1)
790 b = EnterButton(_("Clear"),self.do_clear)
791 grid.addWidget(b, 6, 2)
793 self.payto_sig = QLabel('')
794 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
796 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
797 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
806 def entry_changed( is_fee ):
807 self.funds_error = False
809 if self.amount_e.is_shortcut:
810 self.amount_e.is_shortcut = False
811 sendable = self.get_sendable_balance()
812 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, self.get_payment_sources())
813 fee = self.wallet.estimated_fee(inputs)
815 self.amount_e.setText( self.format_amount(amount) )
816 self.fee_e.setText( self.format_amount( fee ) )
819 amount = self.read_amount(str(self.amount_e.text()))
820 fee = self.read_amount(str(self.fee_e.text()))
822 if not is_fee: fee = None
825 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, self.get_payment_sources())
827 self.fee_e.setText( self.format_amount( fee ) )
830 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
834 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
835 self.funds_error = True
836 text = _( "Not enough funds" )
837 c, u = self.wallet.get_frozen_balance()
838 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
840 self.statusBar().showMessage(text)
841 self.amount_e.setPalette(palette)
842 self.fee_e.setPalette(palette)
844 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
845 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
847 run_hook('create_send_tab', grid)
851 def set_pay_from(self, l):
853 self.from_list.clear()
854 self.from_label.setHidden(len(self.pay_from) == 0)
855 self.from_list.setHidden(len(self.pay_from) == 0)
856 for addr in self.pay_from:
857 c, u = self.wallet.get_addr_balance(addr)
858 balance = self.format_amount(c + u)
859 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
862 def update_completions(self):
864 for addr,label in self.wallet.labels.items():
865 if addr in self.wallet.addressbook:
866 l.append( label + ' <' + addr + '>')
868 run_hook('update_completions', l)
869 self.completions.setStringList(l)
873 return lambda s, *args: s.do_protect(func, args)
878 label = unicode( self.message_e.text() )
879 r = unicode( self.payto_e.text() )
882 # label or alias, with address in brackets
883 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
884 to_address = m.group(2) if m else r
886 if not is_valid(to_address):
887 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
891 amount = self.read_amount(unicode( self.amount_e.text()))
893 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
896 fee = self.read_amount(unicode( self.fee_e.text()))
898 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
901 confirm_amount = self.config.get('confirm_amount', 100000000)
902 if amount >= confirm_amount:
903 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
906 confirm_fee = self.config.get('confirm_fee', 100000)
907 if fee >= confirm_fee:
908 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()}):
911 self.send_tx(to_address, amount, fee, label)
915 def send_tx(self, to_address, amount, fee, label, password):
917 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
918 domain=self.get_payment_sources())
919 except Exception as e:
920 traceback.print_exc(file=sys.stdout)
921 self.show_message(str(e))
924 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
925 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
929 self.wallet.set_label(tx.hash(), label)
932 h = self.wallet.send_tx(tx)
933 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
934 status, msg = self.wallet.receive_tx( h, tx )
936 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
938 self.update_contacts_tab()
940 QMessageBox.warning(self, _('Error'), msg, _('OK'))
943 self.show_transaction(tx)
945 # add recipient to addressbook
946 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
947 self.wallet.addressbook.append(to_address)
952 def set_url(self, url):
953 address, amount, label, message, signature, identity, url = util.parse_url(url)
956 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
957 elif amount: amount = str(Decimal(amount))
960 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
963 self.mini.set_payment_fields(address, amount)
965 if label and self.wallet.labels.get(address) != label:
966 if self.question('Give label "%s" to address %s ?'%(label,address)):
967 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
968 self.wallet.addressbook.append(address)
969 self.wallet.set_label(address, label)
971 run_hook('set_url', url, self.show_message, self.question)
973 self.tabs.setCurrentIndex(1)
974 label = self.wallet.labels.get(address)
975 m_addr = label + ' <'+ address +'>' if label else address
976 self.payto_e.setText(m_addr)
978 self.message_e.setText(message)
980 self.amount_e.setText(amount)
983 self.set_frozen(self.payto_e,True)
984 self.set_frozen(self.amount_e,True)
985 self.set_frozen(self.message_e,True)
986 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
988 self.payto_sig.setVisible(False)
991 self.payto_sig.setVisible(False)
992 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
994 self.set_frozen(e,False)
996 self.set_pay_from([])
999 def set_frozen(self,entry,frozen):
1001 entry.setReadOnly(True)
1002 entry.setFrame(False)
1003 palette = QPalette()
1004 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1005 entry.setPalette(palette)
1007 entry.setReadOnly(False)
1008 entry.setFrame(True)
1009 palette = QPalette()
1010 palette.setColor(entry.backgroundRole(), QColor('white'))
1011 entry.setPalette(palette)
1014 def set_addrs_frozen(self,addrs,freeze):
1016 if not addr: continue
1017 if addr in self.wallet.frozen_addresses and not freeze:
1018 self.wallet.unfreeze(addr)
1019 elif addr not in self.wallet.frozen_addresses and freeze:
1020 self.wallet.freeze(addr)
1021 self.update_receive_tab()
1025 def create_list_tab(self, headers):
1026 "generic tab creation method"
1027 l = MyTreeWidget(self)
1028 l.setColumnCount( len(headers) )
1029 l.setHeaderLabels( headers )
1032 vbox = QVBoxLayout()
1039 vbox.addWidget(buttons)
1041 hbox = QHBoxLayout()
1044 buttons.setLayout(hbox)
1049 def create_receive_tab(self):
1050 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1051 l.setContextMenuPolicy(Qt.CustomContextMenu)
1052 l.customContextMenuRequested.connect(self.create_receive_menu)
1053 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1054 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1055 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1056 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1057 self.receive_list = l
1058 self.receive_buttons_hbox = hbox
1065 def save_column_widths(self):
1066 self.column_widths["receive"] = []
1067 for i in range(self.receive_list.columnCount() -1):
1068 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1070 self.column_widths["history"] = []
1071 for i in range(self.history_list.columnCount() - 1):
1072 self.column_widths["history"].append(self.history_list.columnWidth(i))
1074 self.column_widths["contacts"] = []
1075 for i in range(self.contacts_list.columnCount() - 1):
1076 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1078 self.config.set_key("column_widths_2", self.column_widths, True)
1081 def create_contacts_tab(self):
1082 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1083 l.setContextMenuPolicy(Qt.CustomContextMenu)
1084 l.customContextMenuRequested.connect(self.create_contact_menu)
1085 for i,width in enumerate(self.column_widths['contacts']):
1086 l.setColumnWidth(i, width)
1088 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1089 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1090 self.contacts_list = l
1091 self.contacts_buttons_hbox = hbox
1096 def delete_imported_key(self, addr):
1097 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1098 self.wallet.delete_imported_key(addr)
1099 self.update_receive_tab()
1100 self.update_history_tab()
1102 def edit_account_label(self, k):
1103 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1105 label = unicode(text)
1106 self.wallet.set_label(k,label)
1107 self.update_receive_tab()
1109 def account_set_expanded(self, item, k, b):
1111 self.accounts_expanded[k] = b
1113 def create_account_menu(self, position, k, item):
1115 if item.isExpanded():
1116 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1118 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1119 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1120 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1121 if self.wallet.account_is_pending(k):
1122 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1123 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1125 def delete_pending_account(self, k):
1126 self.wallet.delete_pending_account(k)
1127 self.update_receive_tab()
1129 def create_receive_menu(self, position):
1130 # fixme: this function apparently has a side effect.
1131 # if it is not called the menu pops up several times
1132 #self.receive_list.selectedIndexes()
1134 selected = self.receive_list.selectedItems()
1135 multi_select = len(selected) > 1
1136 addrs = [unicode(item.text(0)) for item in selected]
1137 if not multi_select:
1138 item = self.receive_list.itemAt(position)
1142 if not is_valid(addr):
1143 k = str(item.data(0,32).toString())
1145 self.create_account_menu(position, k, item)
1147 item.setExpanded(not item.isExpanded())
1151 if not multi_select:
1152 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1153 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1154 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1155 if self.wallet.seed:
1156 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1157 menu.addAction(_("Sign message"), lambda: self.sign_verify_message(True,addr))
1158 if addr in self.wallet.imported_keys:
1159 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1161 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1162 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1163 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1164 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1166 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1167 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1169 run_hook('receive_menu', menu, addrs)
1170 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1173 def get_sendable_balance(self):
1174 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1177 def get_payment_sources(self):
1179 return self.pay_from
1181 return self.wallet.get_account_addresses(self.current_account)
1184 def send_from_addresses(self, addrs):
1185 self.set_pay_from( addrs )
1186 self.tabs.setCurrentIndex(1)
1189 def payto(self, addr):
1191 label = self.wallet.labels.get(addr)
1192 m_addr = label + ' <' + addr + '>' if label else addr
1193 self.tabs.setCurrentIndex(1)
1194 self.payto_e.setText(m_addr)
1195 self.amount_e.setFocus()
1198 def delete_contact(self, x):
1199 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1200 self.wallet.delete_contact(x)
1201 self.wallet.set_label(x, None)
1202 self.update_history_tab()
1203 self.update_contacts_tab()
1204 self.update_completions()
1207 def create_contact_menu(self, position):
1208 item = self.contacts_list.itemAt(position)
1211 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1213 addr = unicode(item.text(0))
1214 label = unicode(item.text(1))
1215 is_editable = item.data(0,32).toBool()
1216 payto_addr = item.data(0,33).toString()
1217 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1218 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1219 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1221 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1222 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1224 run_hook('create_contact_menu', menu, item)
1225 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1228 def update_receive_item(self, item):
1229 item.setFont(0, QFont(MONOSPACE_FONT))
1230 address = str(item.data(0,0).toString())
1231 label = self.wallet.labels.get(address,'')
1232 item.setData(1,0,label)
1233 item.setData(0,32, True) # is editable
1235 run_hook('update_receive_item', address, item)
1237 if not self.wallet.is_mine(address): return
1239 c, u = self.wallet.get_addr_balance(address)
1240 balance = self.format_amount(c + u)
1241 item.setData(2,0,balance)
1243 if address in self.wallet.frozen_addresses:
1244 item.setBackgroundColor(0, QColor('lightblue'))
1247 def update_receive_tab(self):
1248 l = self.receive_list
1251 l.setColumnHidden(2, False)
1252 l.setColumnHidden(3, False)
1253 for i,width in enumerate(self.column_widths['receive']):
1254 l.setColumnWidth(i, width)
1256 if self.current_account is None:
1257 account_items = self.wallet.accounts.items()
1258 elif self.current_account != -1:
1259 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1263 for k, account in account_items:
1264 name = self.wallet.get_account_name(k)
1265 c,u = self.wallet.get_account_balance(k)
1266 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1267 l.addTopLevelItem(account_item)
1268 account_item.setExpanded(self.accounts_expanded.get(k, True))
1269 account_item.setData(0, 32, k)
1271 if not self.wallet.is_seeded(k):
1272 icon = QIcon(":icons/key.png")
1273 account_item.setIcon(0, icon)
1275 for is_change in ([0,1]):
1276 name = _("Receiving") if not is_change else _("Change")
1277 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1278 account_item.addChild(seq_item)
1279 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1281 if not is_change: seq_item.setExpanded(True)
1286 for address in account.get_addresses(is_change):
1287 h = self.wallet.history.get(address,[])
1291 if gap > self.wallet.gap_limit:
1296 c, u = self.wallet.get_addr_balance(address)
1297 num_tx = '*' if h == ['*'] else "%d"%len(h)
1298 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1299 self.update_receive_item(item)
1301 item.setBackgroundColor(1, QColor('red'))
1302 if len(h) > 0 and c == -u:
1304 seq_item.insertChild(0,used_item)
1306 used_item.addChild(item)
1308 seq_item.addChild(item)
1311 for k, addr in self.wallet.get_pending_accounts():
1312 name = self.wallet.labels.get(k,'')
1313 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1314 self.update_receive_item(item)
1315 l.addTopLevelItem(account_item)
1316 account_item.setExpanded(True)
1317 account_item.setData(0, 32, k)
1318 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1319 account_item.addChild(item)
1320 self.update_receive_item(item)
1323 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1324 c,u = self.wallet.get_imported_balance()
1325 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1326 l.addTopLevelItem(account_item)
1327 account_item.setExpanded(True)
1328 for address in self.wallet.imported_keys.keys():
1329 item = QTreeWidgetItem( [ address, '', '', ''] )
1330 self.update_receive_item(item)
1331 account_item.addChild(item)
1334 # we use column 1 because column 0 may be hidden
1335 l.setCurrentItem(l.topLevelItem(0),1)
1338 def update_contacts_tab(self):
1339 l = self.contacts_list
1342 for address in self.wallet.addressbook:
1343 label = self.wallet.labels.get(address,'')
1344 n = self.wallet.get_num_tx(address)
1345 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1346 item.setFont(0, QFont(MONOSPACE_FONT))
1347 # 32 = label can be edited (bool)
1348 item.setData(0,32, True)
1350 item.setData(0,33, address)
1351 l.addTopLevelItem(item)
1353 run_hook('update_contacts_tab', l)
1354 l.setCurrentItem(l.topLevelItem(0))
1358 def create_console_tab(self):
1359 from console import Console
1360 self.console = console = Console()
1364 def update_console(self):
1365 console = self.console
1366 console.history = self.config.get("console-history",[])
1367 console.history_index = len(console.history)
1369 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1370 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1372 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1374 def mkfunc(f, method):
1375 return lambda *args: apply( f, (method, args, self.password_dialog ))
1377 if m[0]=='_' or m in ['network','wallet']: continue
1378 methods[m] = mkfunc(c._run, m)
1380 console.updateNamespace(methods)
1383 def change_account(self,s):
1384 if s == _("All accounts"):
1385 self.current_account = None
1387 accounts = self.wallet.get_account_names()
1388 for k, v in accounts.items():
1390 self.current_account = k
1391 self.update_history_tab()
1392 self.update_status()
1393 self.update_receive_tab()
1395 def create_status_bar(self):
1398 sb.setFixedHeight(35)
1399 qtVersion = qVersion()
1401 self.balance_label = QLabel("")
1402 sb.addWidget(self.balance_label)
1404 from version_getter import UpdateLabel
1405 self.updatelabel = UpdateLabel(self.config, sb)
1407 self.account_selector = QComboBox()
1408 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1409 sb.addPermanentWidget(self.account_selector)
1411 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1412 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1414 self.lock_icon = QIcon()
1415 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1416 sb.addPermanentWidget( self.password_button )
1418 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1419 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1420 sb.addPermanentWidget( self.seed_button )
1421 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1422 sb.addPermanentWidget( self.status_button )
1424 run_hook('create_status_bar', (sb,))
1426 self.setStatusBar(sb)
1429 def update_lock_icon(self):
1430 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1431 self.password_button.setIcon( icon )
1434 def update_buttons_on_seed(self):
1435 if not self.wallet.is_watching_only():
1436 self.seed_button.show()
1437 self.password_button.show()
1438 self.send_button.setText(_("Send"))
1440 self.password_button.hide()
1441 self.seed_button.hide()
1442 self.send_button.setText(_("Create unsigned transaction"))
1445 def change_password_dialog(self):
1446 from password_dialog import PasswordDialog
1447 d = PasswordDialog(self.wallet, self)
1449 self.update_lock_icon()
1452 def new_contact_dialog(self):
1455 vbox = QVBoxLayout(d)
1456 vbox.addWidget(QLabel(_('New Contact')+':'))
1458 grid = QGridLayout()
1461 grid.addWidget(QLabel(_("Address")), 1, 0)
1462 grid.addWidget(line1, 1, 1)
1463 grid.addWidget(QLabel(_("Name")), 2, 0)
1464 grid.addWidget(line2, 2, 1)
1466 vbox.addLayout(grid)
1467 vbox.addLayout(ok_cancel_buttons(d))
1472 address = str(line1.text())
1473 label = unicode(line2.text())
1475 if not is_valid(address):
1476 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1479 self.wallet.add_contact(address)
1481 self.wallet.set_label(address, label)
1483 self.update_contacts_tab()
1484 self.update_history_tab()
1485 self.update_completions()
1486 self.tabs.setCurrentIndex(3)
1489 def new_account_dialog(self):
1491 dialog = QDialog(self)
1493 dialog.setWindowTitle(_("New Account"))
1495 vbox = QVBoxLayout()
1496 vbox.addWidget(QLabel(_('Account name')+':'))
1499 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1500 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1505 vbox.addLayout(ok_cancel_buttons(dialog))
1506 dialog.setLayout(vbox)
1510 name = str(e.text())
1513 self.wallet.create_pending_account('1', name)
1514 self.update_receive_tab()
1515 self.tabs.setCurrentIndex(2)
1519 def show_master_public_key_old(self):
1520 dialog = QDialog(self)
1522 dialog.setWindowTitle(_("Master Public Key"))
1524 main_text = QTextEdit()
1525 main_text.setText(self.wallet.get_master_public_key())
1526 main_text.setReadOnly(True)
1527 main_text.setMaximumHeight(170)
1528 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1530 ok_button = QPushButton(_("OK"))
1531 ok_button.setDefault(True)
1532 ok_button.clicked.connect(dialog.accept)
1534 main_layout = QGridLayout()
1535 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1537 main_layout.addWidget(main_text, 1, 0)
1538 main_layout.addWidget(qrw, 1, 1 )
1540 vbox = QVBoxLayout()
1541 vbox.addLayout(main_layout)
1542 vbox.addLayout(close_button(dialog))
1543 dialog.setLayout(vbox)
1547 def show_master_public_key(self):
1549 if self.wallet.seed_version == 4:
1550 self.show_master_public_key_old()
1553 dialog = QDialog(self)
1555 dialog.setWindowTitle(_("Master Public Keys"))
1557 chain_text = QTextEdit()
1558 chain_text.setReadOnly(True)
1559 chain_text.setMaximumHeight(170)
1560 chain_qrw = QRCodeWidget()
1562 mpk_text = QTextEdit()
1563 mpk_text.setReadOnly(True)
1564 mpk_text.setMaximumHeight(170)
1565 mpk_qrw = QRCodeWidget()
1567 main_layout = QGridLayout()
1569 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1570 main_layout.addWidget(mpk_text, 1, 1)
1571 main_layout.addWidget(mpk_qrw, 1, 2)
1573 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1574 main_layout.addWidget(chain_text, 2, 1)
1575 main_layout.addWidget(chain_qrw, 2, 2)
1578 c, K, cK = self.wallet.master_public_keys[str(key)]
1579 chain_text.setText(c)
1580 chain_qrw.set_addr(c)
1581 chain_qrw.update_qr()
1586 key_selector = QComboBox()
1587 keys = sorted(self.wallet.master_public_keys.keys())
1588 key_selector.addItems(keys)
1590 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1591 main_layout.addWidget(key_selector, 0, 1)
1592 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1596 vbox = QVBoxLayout()
1597 vbox.addLayout(main_layout)
1598 vbox.addLayout(close_button(dialog))
1600 dialog.setLayout(vbox)
1605 def show_seed_dialog(self, password):
1606 if self.wallet.is_watching_only():
1607 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1610 if self.wallet.seed:
1612 mnemonic = self.wallet.get_mnemonic(password)
1614 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1616 from seed_dialog import SeedDialog
1617 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1621 for k in self.wallet.master_private_keys.keys():
1622 pk = self.wallet.get_master_private_key(k, password)
1624 from seed_dialog import PrivateKeysDialog
1625 d = PrivateKeysDialog(self,l)
1632 def show_qrcode(self, data, title = _("QR code")):
1636 d.setWindowTitle(title)
1637 d.setMinimumSize(270, 300)
1638 vbox = QVBoxLayout()
1639 qrw = QRCodeWidget(data)
1640 vbox.addWidget(qrw, 1)
1641 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1642 hbox = QHBoxLayout()
1645 filename = os.path.join(self.config.path, "qrcode.bmp")
1648 bmp.save_qrcode(qrw.qr, filename)
1649 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1651 def copy_to_clipboard():
1652 bmp.save_qrcode(qrw.qr, filename)
1653 self.app.clipboard().setImage(QImage(filename))
1654 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1656 b = QPushButton(_("Copy"))
1658 b.clicked.connect(copy_to_clipboard)
1660 b = QPushButton(_("Save"))
1662 b.clicked.connect(print_qr)
1664 b = QPushButton(_("Close"))
1666 b.clicked.connect(d.accept)
1669 vbox.addLayout(hbox)
1674 def do_protect(self, func, args):
1675 if self.wallet.use_encryption:
1676 password = self.password_dialog()
1682 if args != (False,):
1683 args = (self,) + args + (password,)
1685 args = (self,password)
1690 def show_private_key(self, address, password):
1691 if not address: return
1693 pk_list = self.wallet.get_private_key(address, password)
1694 except Exception as e:
1695 self.show_message(str(e))
1699 d.setMinimumSize(600, 200)
1701 vbox = QVBoxLayout()
1702 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1703 vbox.addWidget( QLabel(_("Private key") + ':'))
1705 keys.setReadOnly(True)
1706 keys.setText('\n'.join(pk_list))
1707 vbox.addWidget(keys)
1708 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1709 vbox.addLayout(close_button(d))
1715 def do_sign(self, address, message, signature, password):
1716 message = unicode(message.toPlainText())
1717 message = message.encode('utf-8')
1719 sig = self.wallet.sign_message(str(address.text()), message, password)
1720 signature.setText(sig)
1721 except Exception as e:
1722 self.show_message(str(e))
1724 def do_verify(self, address, message, signature):
1725 message = unicode(message.toPlainText())
1726 message = message.encode('utf-8')
1727 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1728 self.show_message(_("Signature verified"))
1730 self.show_message(_("Error: wrong signature"))
1733 def sign_verify_message(self, sign, address=''):
1734 if sign and not address: return
1737 d.setWindowTitle(_('Sign Message') if sign else _('Verify Message'))
1738 d.setMinimumSize(410, 290)
1740 layout = QGridLayout(d)
1742 address_e = QLineEdit()
1743 address_e.setText(address)
1744 layout.addWidget(QLabel(_('Address')), 1, 0)
1745 layout.addWidget(address_e, 1, 1)
1747 message_e = QTextEdit()
1748 layout.addWidget(QLabel(_('Message')), 2, 0)
1749 layout.addWidget(message_e, 2, 1)
1750 layout.setRowStretch(2,3)
1752 signature_e = QTextEdit()
1753 layout.addWidget(QLabel(_('Signature')), 3, 0)
1754 layout.addWidget(signature_e, 3, 1)
1755 layout.setRowStretch(3,1)
1757 hbox = QHBoxLayout()
1759 b = QPushButton(_("Sign"))
1761 b = QPushButton(_("Verify"))
1763 f = self.do_sign if sign else self.do_verify
1764 b.clicked.connect(lambda: f(address_e, message_e, signature_e))
1765 b = QPushButton(_("Close"))
1766 b.clicked.connect(d.accept)
1768 layout.addLayout(hbox, 4, 1)
1772 def question(self, msg):
1773 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1775 def show_message(self, msg):
1776 QMessageBox.information(self, _('Message'), msg, _('OK'))
1778 def password_dialog(self ):
1785 vbox = QVBoxLayout()
1786 msg = _('Please enter your password')
1787 vbox.addWidget(QLabel(msg))
1789 grid = QGridLayout()
1791 grid.addWidget(QLabel(_('Password')), 1, 0)
1792 grid.addWidget(pw, 1, 1)
1793 vbox.addLayout(grid)
1795 vbox.addLayout(ok_cancel_buttons(d))
1798 run_hook('password_dialog', pw, grid, 1)
1799 if not d.exec_(): return
1800 return unicode(pw.text())
1809 def tx_from_text(self, txt):
1810 "json or raw hexadecimal"
1813 tx = Transaction(txt)
1819 tx_dict = json.loads(str(txt))
1820 assert "hex" in tx_dict.keys()
1821 assert "complete" in tx_dict.keys()
1822 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1823 if not tx_dict["complete"]:
1824 assert "input_info" in tx_dict.keys()
1825 input_info = json.loads(tx_dict['input_info'])
1826 tx.add_input_info(input_info)
1831 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1835 def read_tx_from_file(self):
1836 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1840 with open(fileName, "r") as f:
1841 file_content = f.read()
1842 except (ValueError, IOError, os.error), reason:
1843 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1845 return self.tx_from_text(file_content)
1849 def sign_raw_transaction(self, tx, input_info, password):
1850 self.wallet.signrawtransaction(tx, input_info, [], password)
1852 def do_process_from_text(self):
1853 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1856 tx = self.tx_from_text(text)
1858 self.show_transaction(tx)
1860 def do_process_from_file(self):
1861 tx = self.read_tx_from_file()
1863 self.show_transaction(tx)
1865 def do_process_from_txid(self):
1866 from electrum import transaction
1867 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1869 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1871 tx = transaction.Transaction(r)
1873 self.show_transaction(tx)
1875 self.show_message("unknown transaction")
1877 def do_process_from_csvReader(self, csvReader):
1882 for position, row in enumerate(csvReader):
1884 if not is_valid(address):
1885 errors.append((position, address))
1887 amount = Decimal(row[1])
1888 amount = int(100000000*amount)
1889 outputs.append((address, amount))
1890 except (ValueError, IOError, os.error), reason:
1891 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1895 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1896 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1900 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1901 except Exception as e:
1902 self.show_message(str(e))
1905 self.show_transaction(tx)
1907 def do_process_from_csv_file(self):
1908 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1912 with open(fileName, "r") as f:
1913 csvReader = csv.reader(f)
1914 self.do_process_from_csvReader(csvReader)
1915 except (ValueError, IOError, os.error), reason:
1916 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1919 def do_process_from_csv_text(self):
1920 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1921 + _("Format: address, amount. One output per line"), _("Load CSV"))
1924 f = StringIO.StringIO(text)
1925 csvReader = csv.reader(f)
1926 self.do_process_from_csvReader(csvReader)
1931 def do_export_privkeys(self, password):
1932 if not self.wallet.seed:
1933 self.show_message(_("This wallet has no seed"))
1936 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.")))
1939 select_export = _('Select file to export your private keys to')
1940 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1942 with open(fileName, "w+") as csvfile:
1943 transaction = csv.writer(csvfile)
1944 transaction.writerow(["address", "private_key"])
1946 addresses = self.wallet.addresses(True)
1948 for addr in addresses:
1949 pk = "".join(self.wallet.get_private_key(addr, password))
1950 transaction.writerow(["%34s"%addr,pk])
1952 self.show_message(_("Private keys exported."))
1954 except (IOError, os.error), reason:
1955 export_error_label = _("Electrum was unable to produce a private key-export.")
1956 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1958 except Exception as e:
1959 self.show_message(str(e))
1963 def do_import_labels(self):
1964 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1965 if not labelsFile: return
1967 f = open(labelsFile, 'r')
1970 for key, value in json.loads(data).items():
1971 self.wallet.set_label(key, value)
1972 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1973 except (IOError, os.error), reason:
1974 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1977 def do_export_labels(self):
1978 labels = self.wallet.labels
1980 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1982 with open(fileName, 'w+') as f:
1983 json.dump(labels, f)
1984 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1985 except (IOError, os.error), reason:
1986 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1989 def do_export_history(self):
1990 from lite_window import csv_transaction
1991 csv_transaction(self.wallet)
1995 def do_import_privkey(self, password):
1996 if not self.wallet.imported_keys:
1997 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1998 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1999 + _('Are you sure you understand what you are doing?'), 3, 4)
2002 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2005 text = str(text).split()
2010 addr = self.wallet.import_key(key, password)
2011 except Exception as e:
2017 addrlist.append(addr)
2019 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2021 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2022 self.update_receive_tab()
2023 self.update_history_tab()
2026 def settings_dialog(self):
2028 d.setWindowTitle(_('Electrum Settings'))
2030 vbox = QVBoxLayout()
2031 grid = QGridLayout()
2032 grid.setColumnStretch(0,1)
2034 nz_label = QLabel(_('Display zeros') + ':')
2035 grid.addWidget(nz_label, 0, 0)
2036 nz_e = AmountEdit(None,True)
2037 nz_e.setText("%d"% self.num_zeros)
2038 grid.addWidget(nz_e, 0, 1)
2039 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2040 grid.addWidget(HelpButton(msg), 0, 2)
2041 if not self.config.is_modifiable('num_zeros'):
2042 for w in [nz_e, nz_label]: w.setEnabled(False)
2044 lang_label=QLabel(_('Language') + ':')
2045 grid.addWidget(lang_label, 1, 0)
2046 lang_combo = QComboBox()
2047 from electrum.i18n import languages
2048 lang_combo.addItems(languages.values())
2050 index = languages.keys().index(self.config.get("language",''))
2053 lang_combo.setCurrentIndex(index)
2054 grid.addWidget(lang_combo, 1, 1)
2055 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2056 if not self.config.is_modifiable('language'):
2057 for w in [lang_combo, lang_label]: w.setEnabled(False)
2060 fee_label = QLabel(_('Transaction fee') + ':')
2061 grid.addWidget(fee_label, 2, 0)
2062 fee_e = AmountEdit(self.base_unit)
2063 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2064 grid.addWidget(fee_e, 2, 1)
2065 msg = _('Fee per kilobyte of transaction.') + ' ' \
2066 + _('Recommended value') + ': ' + self.format_amount(20000)
2067 grid.addWidget(HelpButton(msg), 2, 2)
2068 if not self.config.is_modifiable('fee_per_kb'):
2069 for w in [fee_e, fee_label]: w.setEnabled(False)
2071 units = ['BTC', 'mBTC']
2072 unit_label = QLabel(_('Base unit') + ':')
2073 grid.addWidget(unit_label, 3, 0)
2074 unit_combo = QComboBox()
2075 unit_combo.addItems(units)
2076 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2077 grid.addWidget(unit_combo, 3, 1)
2078 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2079 + '\n1BTC=1000mBTC.\n' \
2080 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2082 usechange_cb = QCheckBox(_('Use change addresses'))
2083 usechange_cb.setChecked(self.wallet.use_change)
2084 grid.addWidget(usechange_cb, 4, 0)
2085 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2086 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2088 grid.setRowStretch(5,1)
2090 vbox.addLayout(grid)
2091 vbox.addLayout(ok_cancel_buttons(d))
2095 if not d.exec_(): return
2097 fee = unicode(fee_e.text())
2099 fee = self.read_amount(fee)
2101 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2104 self.wallet.set_fee(fee)
2106 nz = unicode(nz_e.text())
2111 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2114 if self.num_zeros != nz:
2116 self.config.set_key('num_zeros', nz, True)
2117 self.update_history_tab()
2118 self.update_receive_tab()
2120 usechange_result = usechange_cb.isChecked()
2121 if self.wallet.use_change != usechange_result:
2122 self.wallet.use_change = usechange_result
2123 self.wallet.storage.put('use_change', self.wallet.use_change)
2125 unit_result = units[unit_combo.currentIndex()]
2126 if self.base_unit() != unit_result:
2127 self.decimal_point = 8 if unit_result == 'BTC' else 5
2128 self.config.set_key('decimal_point', self.decimal_point, True)
2129 self.update_history_tab()
2130 self.update_status()
2132 need_restart = False
2134 lang_request = languages.keys()[lang_combo.currentIndex()]
2135 if lang_request != self.config.get('language'):
2136 self.config.set_key("language", lang_request, True)
2139 run_hook('close_settings_dialog')
2142 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2145 def run_network_dialog(self):
2146 if not self.network:
2148 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2150 def closeEvent(self, event):
2153 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2154 self.save_column_widths()
2155 self.config.set_key("console-history", self.console.history[-50:], True)
2156 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2160 def plugins_dialog(self):
2161 from electrum.plugins import plugins
2164 d.setWindowTitle(_('Electrum Plugins'))
2167 vbox = QVBoxLayout(d)
2170 scroll = QScrollArea()
2171 scroll.setEnabled(True)
2172 scroll.setWidgetResizable(True)
2173 scroll.setMinimumSize(400,250)
2174 vbox.addWidget(scroll)
2178 w.setMinimumHeight(len(plugins)*35)
2180 grid = QGridLayout()
2181 grid.setColumnStretch(0,1)
2184 def do_toggle(cb, p, w):
2187 if w: w.setEnabled(r)
2189 def mk_toggle(cb, p, w):
2190 return lambda: do_toggle(cb,p,w)
2192 for i, p in enumerate(plugins):
2194 cb = QCheckBox(p.fullname())
2195 cb.setDisabled(not p.is_available())
2196 cb.setChecked(p.is_enabled())
2197 grid.addWidget(cb, i, 0)
2198 if p.requires_settings():
2199 w = p.settings_widget(self)
2200 w.setEnabled( p.is_enabled() )
2201 grid.addWidget(w, i, 1)
2204 cb.clicked.connect(mk_toggle(cb,p,w))
2205 grid.addWidget(HelpButton(p.description()), i, 2)
2207 print_msg(_("Error: cannot display plugin"), p)
2208 traceback.print_exc(file=sys.stdout)
2209 grid.setRowStretch(i+1,1)
2211 vbox.addLayout(close_button(d))
2216 def show_account_details(self, k):
2218 d.setWindowTitle(_('Account Details'))
2221 vbox = QVBoxLayout(d)
2222 roots = self.wallet.get_roots(k)
2224 name = self.wallet.get_account_name(k)
2225 label = QLabel('Name: ' + name)
2226 vbox.addWidget(label)
2228 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2229 vbox.addWidget(QLabel('Type: ' + acctype))
2231 label = QLabel('Derivation: ' + k)
2232 vbox.addWidget(label)
2235 # mpk = self.wallet.master_public_keys[root]
2236 # text = QTextEdit()
2237 # text.setReadOnly(True)
2238 # text.setMaximumHeight(120)
2239 # text.setText(repr(mpk))
2240 # vbox.addWidget(text)
2242 vbox.addLayout(close_button(d))