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 actuator.load_theme()
226 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
228 driver = lite_window.MiniDriver(self, self.mini)
230 if self.config.get('lite_mode') is True:
236 def check_qt_version(self):
237 qtVersion = qVersion()
238 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
241 def update_account_selector(self):
243 accounts = self.wallet.get_account_names()
244 self.account_selector.clear()
245 if len(accounts) > 1:
246 self.account_selector.addItems([_("All accounts")] + accounts.values())
247 self.account_selector.setCurrentIndex(0)
248 self.account_selector.show()
250 self.account_selector.hide()
253 def load_wallet(self, wallet):
256 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
257 self.current_account = self.wallet.storage.get("current_account", None)
259 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
260 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
261 self.setWindowTitle( title )
263 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
264 self.notify_transactions()
265 self.update_account_selector()
266 self.new_account.setEnabled(self.wallet.seed_version>4)
267 self.update_lock_icon()
268 self.update_buttons_on_seed()
269 self.update_console()
271 run_hook('load_wallet', wallet)
274 def open_wallet(self):
275 wallet_folder = self.wallet.storage.path
276 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
280 storage = WalletStorage({'wallet_path': filename})
281 if not storage.file_exists:
282 self.show_message("file not found "+ filename)
285 self.wallet.stop_threads()
288 wallet = Wallet(storage)
289 wallet.start_threads(self.network)
291 self.load_wallet(wallet)
295 def backup_wallet(self):
297 path = self.wallet.storage.path
298 wallet_folder = os.path.dirname(path)
299 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
303 new_path = os.path.join(wallet_folder, filename)
306 shutil.copy2(path, new_path)
307 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
308 except (IOError, os.error), reason:
309 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
312 def new_wallet(self):
315 wallet_folder = os.path.dirname(self.wallet.storage.path)
316 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
319 filename = os.path.join(wallet_folder, filename)
321 storage = WalletStorage({'wallet_path': filename})
322 if storage.file_exists:
323 QMessageBox.critical(None, "Error", _("File exists"))
326 wizard = installwizard.InstallWizard(self.config, self.network, storage)
327 wallet = wizard.run()
329 self.load_wallet(wallet)
333 def init_menubar(self):
336 file_menu = menubar.addMenu(_("&File"))
337 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
338 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
339 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
340 file_menu.addAction(_("&Quit"), self.close)
342 wallet_menu = menubar.addMenu(_("&Wallet"))
343 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
344 self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
346 wallet_menu.addSeparator()
348 wallet_menu.addAction(_("&Password"), self.change_password_dialog)
349 wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
350 wallet_menu.addAction(_("&Master Public Key"), self.show_master_public_key)
352 wallet_menu.addSeparator()
353 labels_menu = wallet_menu.addMenu(_("&Labels"))
354 labels_menu.addAction(_("&Import"), self.do_import_labels)
355 labels_menu.addAction(_("&Export"), self.do_export_labels)
357 keys_menu = wallet_menu.addMenu(_("&Private keys"))
358 keys_menu.addAction(_("&Import"), self.do_import_privkey)
359 keys_menu.addAction(_("&Export"), self.do_export_privkeys)
361 wallet_menu.addAction(_("&Export History"), self.do_export_history)
363 tools_menu = menubar.addMenu(_("&Tools"))
365 # Settings / Preferences are all reserved keywords in OSX using this as work around
366 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
367 tools_menu.addAction(_("&Network"), self.run_network_dialog)
368 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
369 tools_menu.addSeparator()
370 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
371 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
372 tools_menu.addSeparator()
374 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
375 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
376 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
378 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
379 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
380 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
381 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
383 help_menu = menubar.addMenu(_("&Help"))
384 help_menu.addAction(_("&About"), self.show_about)
385 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
386 help_menu.addSeparator()
387 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
388 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
390 self.setMenuBar(menubar)
392 def show_about(self):
393 QMessageBox.about(self, "Electrum",
394 _("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."))
396 def show_report_bug(self):
397 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
398 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
401 def notify_transactions(self):
402 if not self.network or not self.network.is_connected():
405 print_error("Notifying GUI")
406 if len(self.network.interface.pending_transactions_for_notifications) > 0:
407 # Combine the transactions if there are more then three
408 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
411 for tx in self.network.interface.pending_transactions_for_notifications:
412 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
416 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
417 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
419 self.network.interface.pending_transactions_for_notifications = []
421 for tx in self.network.interface.pending_transactions_for_notifications:
423 self.network.interface.pending_transactions_for_notifications.remove(tx)
424 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
426 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
428 def notify(self, message):
429 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
433 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
434 def getOpenFileName(self, title, filter = ""):
435 directory = self.config.get('io_dir', os.path.expanduser('~'))
436 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
437 if fileName and directory != os.path.dirname(fileName):
438 self.config.set_key('io_dir', os.path.dirname(fileName), True)
441 def getSaveFileName(self, title, filename, filter = ""):
442 directory = self.config.get('io_dir', os.path.expanduser('~'))
443 path = os.path.join( directory, filename )
444 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
445 if fileName and directory != os.path.dirname(fileName):
446 self.config.set_key('io_dir', os.path.dirname(fileName), True)
450 QMainWindow.close(self)
451 run_hook('close_main_window')
453 def connect_slots(self, sender):
454 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
455 self.previous_payto_e=''
457 def timer_actions(self):
458 if self.need_update.is_set():
460 self.need_update.clear()
461 run_hook('timer_actions')
463 def format_amount(self, x, is_diff=False, whitespaces=False):
464 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
466 def read_amount(self, x):
467 if x in['.', '']: return None
468 p = pow(10, self.decimal_point)
469 return int( p * Decimal(x) )
472 assert self.decimal_point in [5,8]
473 return "BTC" if self.decimal_point == 8 else "mBTC"
476 def update_status(self):
477 if self.network is None or not self.network.is_running():
479 icon = QIcon(":icons/status_disconnected.png")
481 elif self.network.is_connected():
482 if not self.wallet.up_to_date:
483 text = _("Synchronizing...")
484 icon = QIcon(":icons/status_waiting.png")
485 elif self.network.server_lag > 1:
486 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
487 icon = QIcon(":icons/status_lagging.png")
489 c, u = self.wallet.get_account_balance(self.current_account)
490 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
491 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
494 run_hook('set_quote_text', c+u, r)
497 text += " (%s)"%quote
499 self.tray.setToolTip(text)
500 icon = QIcon(":icons/status_connected.png")
502 text = _("Not connected")
503 icon = QIcon(":icons/status_disconnected.png")
505 self.balance_label.setText(text)
506 self.status_button.setIcon( icon )
509 def update_wallet(self):
511 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
512 self.update_history_tab()
513 self.update_receive_tab()
514 self.update_contacts_tab()
515 self.update_completions()
518 def create_history_tab(self):
519 self.history_list = l = MyTreeWidget(self)
521 for i,width in enumerate(self.column_widths['history']):
522 l.setColumnWidth(i, width)
523 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
524 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
525 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
527 l.customContextMenuRequested.connect(self.create_history_menu)
531 def create_history_menu(self, position):
532 self.history_list.selectedIndexes()
533 item = self.history_list.currentItem()
535 tx_hash = str(item.data(0, Qt.UserRole).toString())
536 if not tx_hash: return
538 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
539 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
540 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
541 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
542 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
545 def show_transaction(self, tx):
546 import transaction_dialog
547 d = transaction_dialog.TxDialog(tx, self)
550 def tx_label_clicked(self, item, column):
551 if column==2 and item.isSelected():
553 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
554 self.history_list.editItem( item, column )
555 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
558 def tx_label_changed(self, item, column):
562 tx_hash = str(item.data(0, Qt.UserRole).toString())
563 tx = self.wallet.transactions.get(tx_hash)
564 text = unicode( item.text(2) )
565 self.wallet.set_label(tx_hash, text)
567 item.setForeground(2, QBrush(QColor('black')))
569 text = self.wallet.get_default_label(tx_hash)
570 item.setText(2, text)
571 item.setForeground(2, QBrush(QColor('gray')))
575 def edit_label(self, is_recv):
576 l = self.receive_list if is_recv else self.contacts_list
577 item = l.currentItem()
578 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
579 l.editItem( item, 1 )
580 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
584 def address_label_clicked(self, item, column, l, column_addr, column_label):
585 if column == column_label and item.isSelected():
586 is_editable = item.data(0, 32).toBool()
589 addr = unicode( item.text(column_addr) )
590 label = unicode( item.text(column_label) )
591 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
592 l.editItem( item, column )
593 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
596 def address_label_changed(self, item, column, l, column_addr, column_label):
597 if column == column_label:
598 addr = unicode( item.text(column_addr) )
599 text = unicode( item.text(column_label) )
600 is_editable = item.data(0, 32).toBool()
604 changed = self.wallet.set_label(addr, text)
606 self.update_history_tab()
607 self.update_completions()
609 self.current_item_changed(item)
611 run_hook('item_changed', item, column)
614 def current_item_changed(self, a):
615 run_hook('current_item_changed', a)
619 def update_history_tab(self):
621 self.history_list.clear()
622 for item in self.wallet.get_tx_history(self.current_account):
623 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
624 time_str = _("unknown")
627 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
629 time_str = _("error")
632 time_str = 'unverified'
633 icon = QIcon(":icons/unconfirmed.png")
636 icon = QIcon(":icons/unconfirmed.png")
638 icon = QIcon(":icons/clock%d.png"%conf)
640 icon = QIcon(":icons/confirmed.png")
642 if value is not None:
643 v_str = self.format_amount(value, True, whitespaces=True)
647 balance_str = self.format_amount(balance, whitespaces=True)
650 label, is_default_label = self.wallet.get_label(tx_hash)
652 label = _('Pruned transaction outputs')
653 is_default_label = False
655 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
656 item.setFont(2, QFont(MONOSPACE_FONT))
657 item.setFont(3, QFont(MONOSPACE_FONT))
658 item.setFont(4, QFont(MONOSPACE_FONT))
660 item.setForeground(3, QBrush(QColor("#BC1E1E")))
662 item.setData(0, Qt.UserRole, tx_hash)
663 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
665 item.setForeground(2, QBrush(QColor('grey')))
667 item.setIcon(0, icon)
668 self.history_list.insertTopLevelItem(0,item)
671 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
672 run_hook('history_tab_update')
675 def create_send_tab(self):
680 grid.setColumnMinimumWidth(3,300)
681 grid.setColumnStretch(5,1)
684 self.payto_e = QLineEdit()
685 grid.addWidget(QLabel(_('Pay to')), 1, 0)
686 grid.addWidget(self.payto_e, 1, 1, 1, 3)
688 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)
690 completer = QCompleter()
691 completer.setCaseSensitivity(False)
692 self.payto_e.setCompleter(completer)
693 completer.setModel(self.completions)
695 self.message_e = QLineEdit()
696 grid.addWidget(QLabel(_('Description')), 2, 0)
697 grid.addWidget(self.message_e, 2, 1, 1, 3)
698 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)
700 self.from_label = QLabel(_('From'))
701 grid.addWidget(self.from_label, 3, 0)
702 self.from_list = QTreeWidget(self)
703 self.from_list.setColumnCount(2)
704 self.from_list.setColumnWidth(0, 350)
705 self.from_list.setColumnWidth(1, 50)
706 self.from_list.setHeaderHidden (True)
707 self.from_list.setMaximumHeight(80)
708 grid.addWidget(self.from_list, 3, 1, 1, 3)
709 self.set_pay_from([])
711 self.amount_e = AmountEdit(self.base_unit)
712 grid.addWidget(QLabel(_('Amount')), 4, 0)
713 grid.addWidget(self.amount_e, 4, 1, 1, 2)
714 grid.addWidget(HelpButton(
715 _('Amount to be sent.') + '\n\n' \
716 + _('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.') \
717 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
719 self.fee_e = AmountEdit(self.base_unit)
720 grid.addWidget(QLabel(_('Fee')), 5, 0)
721 grid.addWidget(self.fee_e, 5, 1, 1, 2)
722 grid.addWidget(HelpButton(
723 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
724 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
725 + _('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)
727 run_hook('exchange_rate_button', grid)
729 self.send_button = EnterButton(_("Send"), self.do_send)
730 grid.addWidget(self.send_button, 6, 1)
732 b = EnterButton(_("Clear"),self.do_clear)
733 grid.addWidget(b, 6, 2)
735 self.payto_sig = QLabel('')
736 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
738 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
739 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
748 def entry_changed( is_fee ):
749 self.funds_error = False
751 if self.amount_e.is_shortcut:
752 self.amount_e.is_shortcut = False
753 sendable = self.get_sendable_balance()
754 # there is only one output because we are completely spending inputs
755 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
756 fee = self.wallet.estimated_fee(inputs, 1)
758 self.amount_e.setText( self.format_amount(amount) )
759 self.fee_e.setText( self.format_amount( fee ) )
762 amount = self.read_amount(str(self.amount_e.text()))
763 fee = self.read_amount(str(self.fee_e.text()))
765 if not is_fee: fee = None
768 # assume that there will be 2 outputs (one for change)
769 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
771 self.fee_e.setText( self.format_amount( fee ) )
774 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
778 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
779 self.funds_error = True
780 text = _( "Not enough funds" )
781 c, u = self.wallet.get_frozen_balance()
782 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
784 self.statusBar().showMessage(text)
785 self.amount_e.setPalette(palette)
786 self.fee_e.setPalette(palette)
788 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
789 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
791 run_hook('create_send_tab', grid)
795 def set_pay_from(self, l):
797 self.from_list.clear()
798 self.from_label.setHidden(len(self.pay_from) == 0)
799 self.from_list.setHidden(len(self.pay_from) == 0)
800 for addr in self.pay_from:
801 c, u = self.wallet.get_addr_balance(addr)
802 balance = self.format_amount(c + u)
803 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
806 def update_completions(self):
808 for addr,label in self.wallet.labels.items():
809 if addr in self.wallet.addressbook:
810 l.append( label + ' <' + addr + '>')
812 run_hook('update_completions', l)
813 self.completions.setStringList(l)
817 return lambda s, *args: s.do_protect(func, args)
822 label = unicode( self.message_e.text() )
823 r = unicode( self.payto_e.text() )
826 # label or alias, with address in brackets
827 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
828 to_address = m.group(2) if m else r
830 if not is_valid(to_address):
831 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
835 amount = self.read_amount(unicode( self.amount_e.text()))
837 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
840 fee = self.read_amount(unicode( self.fee_e.text()))
842 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
845 confirm_amount = self.config.get('confirm_amount', 100000000)
846 if amount >= confirm_amount:
847 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
850 confirm_fee = self.config.get('confirm_fee', 100000)
851 if fee >= confirm_fee:
852 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()}):
855 self.send_tx(to_address, amount, fee, label)
859 def send_tx(self, to_address, amount, fee, label, password):
861 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
862 domain=self.get_payment_sources())
863 except Exception as e:
864 traceback.print_exc(file=sys.stdout)
865 self.show_message(str(e))
868 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
869 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
873 self.wallet.set_label(tx.hash(), label)
876 h = self.wallet.send_tx(tx)
877 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
878 status, msg = self.wallet.receive_tx( h, tx )
880 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
882 self.update_contacts_tab()
884 QMessageBox.warning(self, _('Error'), msg, _('OK'))
887 self.show_transaction(tx)
889 # add recipient to addressbook
890 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
891 self.wallet.addressbook.append(to_address)
896 def set_url(self, url):
897 address, amount, label, message, signature, identity, url = util.parse_url(url)
900 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
901 elif amount: amount = str(Decimal(amount))
904 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
907 self.mini.set_payment_fields(address, amount)
909 if label and self.wallet.labels.get(address) != label:
910 if self.question('Give label "%s" to address %s ?'%(label,address)):
911 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
912 self.wallet.addressbook.append(address)
913 self.wallet.set_label(address, label)
915 run_hook('set_url', url, self.show_message, self.question)
917 self.tabs.setCurrentIndex(1)
918 label = self.wallet.labels.get(address)
919 m_addr = label + ' <'+ address +'>' if label else address
920 self.payto_e.setText(m_addr)
922 self.message_e.setText(message)
924 self.amount_e.setText(amount)
927 self.set_frozen(self.payto_e,True)
928 self.set_frozen(self.amount_e,True)
929 self.set_frozen(self.message_e,True)
930 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
932 self.payto_sig.setVisible(False)
935 self.payto_sig.setVisible(False)
936 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
938 self.set_frozen(e,False)
940 self.set_pay_from([])
943 def set_frozen(self,entry,frozen):
945 entry.setReadOnly(True)
946 entry.setFrame(False)
948 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
949 entry.setPalette(palette)
951 entry.setReadOnly(False)
954 palette.setColor(entry.backgroundRole(), QColor('white'))
955 entry.setPalette(palette)
958 def set_addrs_frozen(self,addrs,freeze):
960 if not addr: continue
961 if addr in self.wallet.frozen_addresses and not freeze:
962 self.wallet.unfreeze(addr)
963 elif addr not in self.wallet.frozen_addresses and freeze:
964 self.wallet.freeze(addr)
965 self.update_receive_tab()
969 def create_list_tab(self, headers):
970 "generic tab creation method"
971 l = MyTreeWidget(self)
972 l.setColumnCount( len(headers) )
973 l.setHeaderLabels( headers )
983 vbox.addWidget(buttons)
988 buttons.setLayout(hbox)
993 def create_receive_tab(self):
994 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
995 l.setContextMenuPolicy(Qt.CustomContextMenu)
996 l.customContextMenuRequested.connect(self.create_receive_menu)
997 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
998 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
999 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1000 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1001 self.receive_list = l
1002 self.receive_buttons_hbox = hbox
1009 def save_column_widths(self):
1010 self.column_widths["receive"] = []
1011 for i in range(self.receive_list.columnCount() -1):
1012 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1014 self.column_widths["history"] = []
1015 for i in range(self.history_list.columnCount() - 1):
1016 self.column_widths["history"].append(self.history_list.columnWidth(i))
1018 self.column_widths["contacts"] = []
1019 for i in range(self.contacts_list.columnCount() - 1):
1020 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1022 self.config.set_key("column_widths_2", self.column_widths, True)
1025 def create_contacts_tab(self):
1026 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1027 l.setContextMenuPolicy(Qt.CustomContextMenu)
1028 l.customContextMenuRequested.connect(self.create_contact_menu)
1029 for i,width in enumerate(self.column_widths['contacts']):
1030 l.setColumnWidth(i, width)
1032 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1033 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1034 self.contacts_list = l
1035 self.contacts_buttons_hbox = hbox
1040 def delete_imported_key(self, addr):
1041 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1042 self.wallet.delete_imported_key(addr)
1043 self.update_receive_tab()
1044 self.update_history_tab()
1046 def edit_account_label(self, k):
1047 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1049 label = unicode(text)
1050 self.wallet.set_label(k,label)
1051 self.update_receive_tab()
1053 def account_set_expanded(self, item, k, b):
1055 self.accounts_expanded[k] = b
1057 def create_account_menu(self, position, k, item):
1059 if item.isExpanded():
1060 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1062 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1063 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1064 if self.wallet.seed_version > 4:
1065 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1066 if self.wallet.account_is_pending(k):
1067 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1068 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1070 def delete_pending_account(self, k):
1071 self.wallet.delete_pending_account(k)
1072 self.update_receive_tab()
1074 def create_receive_menu(self, position):
1075 # fixme: this function apparently has a side effect.
1076 # if it is not called the menu pops up several times
1077 #self.receive_list.selectedIndexes()
1079 selected = self.receive_list.selectedItems()
1080 multi_select = len(selected) > 1
1081 addrs = [unicode(item.text(0)) for item in selected]
1082 if not multi_select:
1083 item = self.receive_list.itemAt(position)
1087 if not is_valid(addr):
1088 k = str(item.data(0,32).toString())
1090 self.create_account_menu(position, k, item)
1092 item.setExpanded(not item.isExpanded())
1096 if not multi_select:
1097 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1098 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1099 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1100 if self.wallet.seed:
1101 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1102 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1103 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1104 if addr in self.wallet.imported_keys:
1105 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1107 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1108 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1109 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1110 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1112 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1113 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1115 run_hook('receive_menu', menu, addrs)
1116 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1119 def get_sendable_balance(self):
1120 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1123 def get_payment_sources(self):
1125 return self.pay_from
1127 return self.wallet.get_account_addresses(self.current_account)
1130 def send_from_addresses(self, addrs):
1131 self.set_pay_from( addrs )
1132 self.tabs.setCurrentIndex(1)
1135 def payto(self, addr):
1137 label = self.wallet.labels.get(addr)
1138 m_addr = label + ' <' + addr + '>' if label else addr
1139 self.tabs.setCurrentIndex(1)
1140 self.payto_e.setText(m_addr)
1141 self.amount_e.setFocus()
1144 def delete_contact(self, x):
1145 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1146 self.wallet.delete_contact(x)
1147 self.wallet.set_label(x, None)
1148 self.update_history_tab()
1149 self.update_contacts_tab()
1150 self.update_completions()
1153 def create_contact_menu(self, position):
1154 item = self.contacts_list.itemAt(position)
1157 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1159 addr = unicode(item.text(0))
1160 label = unicode(item.text(1))
1161 is_editable = item.data(0,32).toBool()
1162 payto_addr = item.data(0,33).toString()
1163 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1164 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1165 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1167 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1168 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1170 run_hook('create_contact_menu', menu, item)
1171 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1174 def update_receive_item(self, item):
1175 item.setFont(0, QFont(MONOSPACE_FONT))
1176 address = str(item.data(0,0).toString())
1177 label = self.wallet.labels.get(address,'')
1178 item.setData(1,0,label)
1179 item.setData(0,32, True) # is editable
1181 run_hook('update_receive_item', address, item)
1183 if not self.wallet.is_mine(address): return
1185 c, u = self.wallet.get_addr_balance(address)
1186 balance = self.format_amount(c + u)
1187 item.setData(2,0,balance)
1189 if address in self.wallet.frozen_addresses:
1190 item.setBackgroundColor(0, QColor('lightblue'))
1193 def update_receive_tab(self):
1194 l = self.receive_list
1197 l.setColumnHidden(2, False)
1198 l.setColumnHidden(3, False)
1199 for i,width in enumerate(self.column_widths['receive']):
1200 l.setColumnWidth(i, width)
1202 if self.current_account is None:
1203 account_items = self.wallet.accounts.items()
1204 elif self.current_account != -1:
1205 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1209 for k, account in account_items:
1210 name = self.wallet.get_account_name(k)
1211 c,u = self.wallet.get_account_balance(k)
1212 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1213 l.addTopLevelItem(account_item)
1214 account_item.setExpanded(self.accounts_expanded.get(k, True))
1215 account_item.setData(0, 32, k)
1217 if not self.wallet.is_seeded(k):
1218 icon = QIcon(":icons/key.png")
1219 account_item.setIcon(0, icon)
1221 for is_change in ([0,1]):
1222 name = _("Receiving") if not is_change else _("Change")
1223 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1224 account_item.addChild(seq_item)
1225 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1227 if not is_change: seq_item.setExpanded(True)
1232 for address in account.get_addresses(is_change):
1233 h = self.wallet.history.get(address,[])
1237 if gap > self.wallet.gap_limit:
1242 c, u = self.wallet.get_addr_balance(address)
1243 num_tx = '*' if h == ['*'] else "%d"%len(h)
1244 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1245 self.update_receive_item(item)
1247 item.setBackgroundColor(1, QColor('red'))
1248 if len(h) > 0 and c == -u:
1250 seq_item.insertChild(0,used_item)
1252 used_item.addChild(item)
1254 seq_item.addChild(item)
1257 for k, addr in self.wallet.get_pending_accounts():
1258 name = self.wallet.labels.get(k,'')
1259 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1260 self.update_receive_item(item)
1261 l.addTopLevelItem(account_item)
1262 account_item.setExpanded(True)
1263 account_item.setData(0, 32, k)
1264 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1265 account_item.addChild(item)
1266 self.update_receive_item(item)
1269 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1270 c,u = self.wallet.get_imported_balance()
1271 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1272 l.addTopLevelItem(account_item)
1273 account_item.setExpanded(True)
1274 for address in self.wallet.imported_keys.keys():
1275 item = QTreeWidgetItem( [ address, '', '', ''] )
1276 self.update_receive_item(item)
1277 account_item.addChild(item)
1280 # we use column 1 because column 0 may be hidden
1281 l.setCurrentItem(l.topLevelItem(0),1)
1284 def update_contacts_tab(self):
1285 l = self.contacts_list
1288 for address in self.wallet.addressbook:
1289 label = self.wallet.labels.get(address,'')
1290 n = self.wallet.get_num_tx(address)
1291 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1292 item.setFont(0, QFont(MONOSPACE_FONT))
1293 # 32 = label can be edited (bool)
1294 item.setData(0,32, True)
1296 item.setData(0,33, address)
1297 l.addTopLevelItem(item)
1299 run_hook('update_contacts_tab', l)
1300 l.setCurrentItem(l.topLevelItem(0))
1304 def create_console_tab(self):
1305 from console import Console
1306 self.console = console = Console()
1310 def update_console(self):
1311 console = self.console
1312 console.history = self.config.get("console-history",[])
1313 console.history_index = len(console.history)
1315 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1316 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1318 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1320 def mkfunc(f, method):
1321 return lambda *args: apply( f, (method, args, self.password_dialog ))
1323 if m[0]=='_' or m in ['network','wallet']: continue
1324 methods[m] = mkfunc(c._run, m)
1326 console.updateNamespace(methods)
1329 def change_account(self,s):
1330 if s == _("All accounts"):
1331 self.current_account = None
1333 accounts = self.wallet.get_account_names()
1334 for k, v in accounts.items():
1336 self.current_account = k
1337 self.update_history_tab()
1338 self.update_status()
1339 self.update_receive_tab()
1341 def create_status_bar(self):
1344 sb.setFixedHeight(35)
1345 qtVersion = qVersion()
1347 self.balance_label = QLabel("")
1348 sb.addWidget(self.balance_label)
1350 from version_getter import UpdateLabel
1351 self.updatelabel = UpdateLabel(self.config, sb)
1353 self.account_selector = QComboBox()
1354 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1355 sb.addPermanentWidget(self.account_selector)
1357 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1358 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1360 self.lock_icon = QIcon()
1361 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1362 sb.addPermanentWidget( self.password_button )
1364 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1365 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1366 sb.addPermanentWidget( self.seed_button )
1367 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1368 sb.addPermanentWidget( self.status_button )
1370 run_hook('create_status_bar', (sb,))
1372 self.setStatusBar(sb)
1375 def update_lock_icon(self):
1376 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1377 self.password_button.setIcon( icon )
1380 def update_buttons_on_seed(self):
1381 if not self.wallet.is_watching_only():
1382 self.seed_button.show()
1383 self.password_button.show()
1384 self.send_button.setText(_("Send"))
1386 self.password_button.hide()
1387 self.seed_button.hide()
1388 self.send_button.setText(_("Create unsigned transaction"))
1391 def change_password_dialog(self):
1392 from password_dialog import PasswordDialog
1393 d = PasswordDialog(self.wallet, self)
1395 self.update_lock_icon()
1398 def new_contact_dialog(self):
1401 vbox = QVBoxLayout(d)
1402 vbox.addWidget(QLabel(_('New Contact')+':'))
1404 grid = QGridLayout()
1407 grid.addWidget(QLabel(_("Address")), 1, 0)
1408 grid.addWidget(line1, 1, 1)
1409 grid.addWidget(QLabel(_("Name")), 2, 0)
1410 grid.addWidget(line2, 2, 1)
1412 vbox.addLayout(grid)
1413 vbox.addLayout(ok_cancel_buttons(d))
1418 address = str(line1.text())
1419 label = unicode(line2.text())
1421 if not is_valid(address):
1422 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1425 self.wallet.add_contact(address)
1427 self.wallet.set_label(address, label)
1429 self.update_contacts_tab()
1430 self.update_history_tab()
1431 self.update_completions()
1432 self.tabs.setCurrentIndex(3)
1435 def new_account_dialog(self):
1437 dialog = QDialog(self)
1439 dialog.setWindowTitle(_("New Account"))
1441 vbox = QVBoxLayout()
1442 vbox.addWidget(QLabel(_('Account name')+':'))
1445 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1446 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1451 vbox.addLayout(ok_cancel_buttons(dialog))
1452 dialog.setLayout(vbox)
1456 name = str(e.text())
1459 self.wallet.create_pending_account('1', name)
1460 self.update_receive_tab()
1461 self.tabs.setCurrentIndex(2)
1465 def show_master_public_key_old(self):
1466 dialog = QDialog(self)
1468 dialog.setWindowTitle(_("Master Public Key"))
1470 main_text = QTextEdit()
1471 main_text.setText(self.wallet.get_master_public_key())
1472 main_text.setReadOnly(True)
1473 main_text.setMaximumHeight(170)
1474 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1476 ok_button = QPushButton(_("OK"))
1477 ok_button.setDefault(True)
1478 ok_button.clicked.connect(dialog.accept)
1480 main_layout = QGridLayout()
1481 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1483 main_layout.addWidget(main_text, 1, 0)
1484 main_layout.addWidget(qrw, 1, 1 )
1486 vbox = QVBoxLayout()
1487 vbox.addLayout(main_layout)
1488 vbox.addLayout(close_button(dialog))
1489 dialog.setLayout(vbox)
1493 def show_master_public_key(self):
1495 if self.wallet.seed_version == 4:
1496 self.show_master_public_key_old()
1499 dialog = QDialog(self)
1501 dialog.setWindowTitle(_("Master Public Keys"))
1503 chain_text = QTextEdit()
1504 chain_text.setReadOnly(True)
1505 chain_text.setMaximumHeight(170)
1506 chain_qrw = QRCodeWidget()
1508 mpk_text = QTextEdit()
1509 mpk_text.setReadOnly(True)
1510 mpk_text.setMaximumHeight(170)
1511 mpk_qrw = QRCodeWidget()
1513 main_layout = QGridLayout()
1515 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1516 main_layout.addWidget(mpk_text, 1, 1)
1517 main_layout.addWidget(mpk_qrw, 1, 2)
1519 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1520 main_layout.addWidget(chain_text, 2, 1)
1521 main_layout.addWidget(chain_qrw, 2, 2)
1524 c, K, cK = self.wallet.master_public_keys[str(key)]
1525 chain_text.setText(c)
1526 chain_qrw.set_addr(c)
1527 chain_qrw.update_qr()
1532 key_selector = QComboBox()
1533 keys = sorted(self.wallet.master_public_keys.keys())
1534 key_selector.addItems(keys)
1536 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1537 main_layout.addWidget(key_selector, 0, 1)
1538 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1542 vbox = QVBoxLayout()
1543 vbox.addLayout(main_layout)
1544 vbox.addLayout(close_button(dialog))
1546 dialog.setLayout(vbox)
1551 def show_seed_dialog(self, password):
1552 if self.wallet.is_watching_only():
1553 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1556 if self.wallet.seed:
1558 mnemonic = self.wallet.get_mnemonic(password)
1560 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1562 from seed_dialog import SeedDialog
1563 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1567 for k in self.wallet.master_private_keys.keys():
1568 pk = self.wallet.get_master_private_key(k, password)
1570 from seed_dialog import PrivateKeysDialog
1571 d = PrivateKeysDialog(self,l)
1578 def show_qrcode(self, data, title = _("QR code")):
1582 d.setWindowTitle(title)
1583 d.setMinimumSize(270, 300)
1584 vbox = QVBoxLayout()
1585 qrw = QRCodeWidget(data)
1586 vbox.addWidget(qrw, 1)
1587 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1588 hbox = QHBoxLayout()
1591 filename = os.path.join(self.config.path, "qrcode.bmp")
1594 bmp.save_qrcode(qrw.qr, filename)
1595 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1597 def copy_to_clipboard():
1598 bmp.save_qrcode(qrw.qr, filename)
1599 self.app.clipboard().setImage(QImage(filename))
1600 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1602 b = QPushButton(_("Copy"))
1604 b.clicked.connect(copy_to_clipboard)
1606 b = QPushButton(_("Save"))
1608 b.clicked.connect(print_qr)
1610 b = QPushButton(_("Close"))
1612 b.clicked.connect(d.accept)
1615 vbox.addLayout(hbox)
1620 def do_protect(self, func, args):
1621 if self.wallet.use_encryption:
1622 password = self.password_dialog()
1628 if args != (False,):
1629 args = (self,) + args + (password,)
1631 args = (self,password)
1636 def show_private_key(self, address, password):
1637 if not address: return
1639 pk_list = self.wallet.get_private_key(address, password)
1640 except Exception as e:
1641 self.show_message(str(e))
1645 d.setMinimumSize(600, 200)
1647 vbox = QVBoxLayout()
1648 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1649 vbox.addWidget( QLabel(_("Private key") + ':'))
1651 keys.setReadOnly(True)
1652 keys.setText('\n'.join(pk_list))
1653 vbox.addWidget(keys)
1654 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1655 vbox.addLayout(close_button(d))
1661 def do_sign(self, address, message, signature, password):
1662 message = unicode(message.toPlainText())
1663 message = message.encode('utf-8')
1665 sig = self.wallet.sign_message(str(address.text()), message, password)
1666 signature.setText(sig)
1667 except Exception as e:
1668 self.show_message(str(e))
1670 def do_verify(self, address, message, signature):
1671 message = unicode(message.toPlainText())
1672 message = message.encode('utf-8')
1673 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1674 self.show_message(_("Signature verified"))
1676 self.show_message(_("Error: wrong signature"))
1679 def sign_verify_message(self, address=''):
1682 d.setWindowTitle(_('Sign/verify Message'))
1683 d.setMinimumSize(410, 290)
1685 layout = QGridLayout(d)
1687 message_e = QTextEdit()
1688 layout.addWidget(QLabel(_('Message')), 1, 0)
1689 layout.addWidget(message_e, 1, 1)
1690 layout.setRowStretch(2,3)
1692 address_e = QLineEdit()
1693 address_e.setText(address)
1694 layout.addWidget(QLabel(_('Address')), 2, 0)
1695 layout.addWidget(address_e, 2, 1)
1697 signature_e = QTextEdit()
1698 layout.addWidget(QLabel(_('Signature')), 3, 0)
1699 layout.addWidget(signature_e, 3, 1)
1700 layout.setRowStretch(3,1)
1702 hbox = QHBoxLayout()
1704 b = QPushButton(_("Sign"))
1705 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1708 b = QPushButton(_("Verify"))
1709 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1712 b = QPushButton(_("Close"))
1713 b.clicked.connect(d.accept)
1715 layout.addLayout(hbox, 4, 1)
1720 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1722 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1723 message_e.setText(decrypted)
1724 except Exception as e:
1725 self.show_message(str(e))
1728 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1729 message = unicode(message_e.toPlainText())
1730 message = message.encode('utf-8')
1732 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1733 encrypted_e.setText(encrypted)
1734 except Exception as e:
1735 self.show_message(str(e))
1739 def encrypt_message(self, address = ''):
1742 d.setWindowTitle(_('Encrypt/decrypt Message'))
1743 d.setMinimumSize(610, 490)
1745 layout = QGridLayout(d)
1747 message_e = QTextEdit()
1748 layout.addWidget(QLabel(_('Message')), 1, 0)
1749 layout.addWidget(message_e, 1, 1)
1750 layout.setRowStretch(2,3)
1752 pubkey_e = QLineEdit()
1754 pubkey = self.wallet.getpubkeys(address)[0]
1755 pubkey_e.setText(pubkey)
1756 layout.addWidget(QLabel(_('Public key')), 2, 0)
1757 layout.addWidget(pubkey_e, 2, 1)
1759 encrypted_e = QTextEdit()
1760 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1761 layout.addWidget(encrypted_e, 3, 1)
1762 layout.setRowStretch(3,1)
1764 hbox = QHBoxLayout()
1765 b = QPushButton(_("Encrypt"))
1766 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1769 b = QPushButton(_("Decrypt"))
1770 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1773 b = QPushButton(_("Close"))
1774 b.clicked.connect(d.accept)
1777 layout.addLayout(hbox, 4, 1)
1781 def question(self, msg):
1782 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1784 def show_message(self, msg):
1785 QMessageBox.information(self, _('Message'), msg, _('OK'))
1787 def password_dialog(self ):
1794 vbox = QVBoxLayout()
1795 msg = _('Please enter your password')
1796 vbox.addWidget(QLabel(msg))
1798 grid = QGridLayout()
1800 grid.addWidget(QLabel(_('Password')), 1, 0)
1801 grid.addWidget(pw, 1, 1)
1802 vbox.addLayout(grid)
1804 vbox.addLayout(ok_cancel_buttons(d))
1807 run_hook('password_dialog', pw, grid, 1)
1808 if not d.exec_(): return
1809 return unicode(pw.text())
1818 def tx_from_text(self, txt):
1819 "json or raw hexadecimal"
1822 tx = Transaction(txt)
1828 tx_dict = json.loads(str(txt))
1829 assert "hex" in tx_dict.keys()
1830 assert "complete" in tx_dict.keys()
1831 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1832 if not tx_dict["complete"]:
1833 assert "input_info" in tx_dict.keys()
1834 input_info = json.loads(tx_dict['input_info'])
1835 tx.add_input_info(input_info)
1840 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1844 def read_tx_from_file(self):
1845 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1849 with open(fileName, "r") as f:
1850 file_content = f.read()
1851 except (ValueError, IOError, os.error), reason:
1852 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1854 return self.tx_from_text(file_content)
1858 def sign_raw_transaction(self, tx, input_info, password):
1859 self.wallet.signrawtransaction(tx, input_info, [], password)
1861 def do_process_from_text(self):
1862 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1865 tx = self.tx_from_text(text)
1867 self.show_transaction(tx)
1869 def do_process_from_file(self):
1870 tx = self.read_tx_from_file()
1872 self.show_transaction(tx)
1874 def do_process_from_txid(self):
1875 from electrum import transaction
1876 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1878 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1880 tx = transaction.Transaction(r)
1882 self.show_transaction(tx)
1884 self.show_message("unknown transaction")
1886 def do_process_from_csvReader(self, csvReader):
1891 for position, row in enumerate(csvReader):
1893 if not is_valid(address):
1894 errors.append((position, address))
1896 amount = Decimal(row[1])
1897 amount = int(100000000*amount)
1898 outputs.append((address, amount))
1899 except (ValueError, IOError, os.error), reason:
1900 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1904 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1905 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1909 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1910 except Exception as e:
1911 self.show_message(str(e))
1914 self.show_transaction(tx)
1916 def do_process_from_csv_file(self):
1917 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1921 with open(fileName, "r") as f:
1922 csvReader = csv.reader(f)
1923 self.do_process_from_csvReader(csvReader)
1924 except (ValueError, IOError, os.error), reason:
1925 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1928 def do_process_from_csv_text(self):
1929 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1930 + _("Format: address, amount. One output per line"), _("Load CSV"))
1933 f = StringIO.StringIO(text)
1934 csvReader = csv.reader(f)
1935 self.do_process_from_csvReader(csvReader)
1940 def do_export_privkeys(self, password):
1941 if not self.wallet.seed:
1942 self.show_message(_("This wallet has no seed"))
1945 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.")))
1948 select_export = _('Select file to export your private keys to')
1949 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1951 with open(fileName, "w+") as csvfile:
1952 transaction = csv.writer(csvfile)
1953 transaction.writerow(["address", "private_key"])
1955 addresses = self.wallet.addresses(True)
1957 for addr in addresses:
1958 pk = "".join(self.wallet.get_private_key(addr, password))
1959 transaction.writerow(["%34s"%addr,pk])
1961 self.show_message(_("Private keys exported."))
1963 except (IOError, os.error), reason:
1964 export_error_label = _("Electrum was unable to produce a private key-export.")
1965 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1967 except Exception as e:
1968 self.show_message(str(e))
1972 def do_import_labels(self):
1973 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1974 if not labelsFile: return
1976 f = open(labelsFile, 'r')
1979 for key, value in json.loads(data).items():
1980 self.wallet.set_label(key, value)
1981 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1982 except (IOError, os.error), reason:
1983 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1986 def do_export_labels(self):
1987 labels = self.wallet.labels
1989 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1991 with open(fileName, 'w+') as f:
1992 json.dump(labels, f)
1993 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1994 except (IOError, os.error), reason:
1995 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1998 def do_export_history(self):
1999 from lite_window import csv_transaction
2000 csv_transaction(self.wallet)
2004 def do_import_privkey(self, password):
2005 if not self.wallet.imported_keys:
2006 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2007 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2008 + _('Are you sure you understand what you are doing?'), 3, 4)
2011 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2014 text = str(text).split()
2019 addr = self.wallet.import_key(key, password)
2020 except Exception as e:
2026 addrlist.append(addr)
2028 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2030 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2031 self.update_receive_tab()
2032 self.update_history_tab()
2035 def settings_dialog(self):
2037 d.setWindowTitle(_('Electrum Settings'))
2039 vbox = QVBoxLayout()
2040 grid = QGridLayout()
2041 grid.setColumnStretch(0,1)
2043 nz_label = QLabel(_('Display zeros') + ':')
2044 grid.addWidget(nz_label, 0, 0)
2045 nz_e = AmountEdit(None,True)
2046 nz_e.setText("%d"% self.num_zeros)
2047 grid.addWidget(nz_e, 0, 1)
2048 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2049 grid.addWidget(HelpButton(msg), 0, 2)
2050 if not self.config.is_modifiable('num_zeros'):
2051 for w in [nz_e, nz_label]: w.setEnabled(False)
2053 lang_label=QLabel(_('Language') + ':')
2054 grid.addWidget(lang_label, 1, 0)
2055 lang_combo = QComboBox()
2056 from electrum.i18n import languages
2057 lang_combo.addItems(languages.values())
2059 index = languages.keys().index(self.config.get("language",''))
2062 lang_combo.setCurrentIndex(index)
2063 grid.addWidget(lang_combo, 1, 1)
2064 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2065 if not self.config.is_modifiable('language'):
2066 for w in [lang_combo, lang_label]: w.setEnabled(False)
2069 fee_label = QLabel(_('Transaction fee') + ':')
2070 grid.addWidget(fee_label, 2, 0)
2071 fee_e = AmountEdit(self.base_unit)
2072 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2073 grid.addWidget(fee_e, 2, 1)
2074 msg = _('Fee per kilobyte of transaction.') + ' ' \
2075 + _('Recommended value') + ': ' + self.format_amount(20000)
2076 grid.addWidget(HelpButton(msg), 2, 2)
2077 if not self.config.is_modifiable('fee_per_kb'):
2078 for w in [fee_e, fee_label]: w.setEnabled(False)
2080 units = ['BTC', 'mBTC']
2081 unit_label = QLabel(_('Base unit') + ':')
2082 grid.addWidget(unit_label, 3, 0)
2083 unit_combo = QComboBox()
2084 unit_combo.addItems(units)
2085 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2086 grid.addWidget(unit_combo, 3, 1)
2087 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2088 + '\n1BTC=1000mBTC.\n' \
2089 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2091 usechange_cb = QCheckBox(_('Use change addresses'))
2092 usechange_cb.setChecked(self.wallet.use_change)
2093 grid.addWidget(usechange_cb, 4, 0)
2094 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2095 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2097 grid.setRowStretch(5,1)
2099 vbox.addLayout(grid)
2100 vbox.addLayout(ok_cancel_buttons(d))
2104 if not d.exec_(): return
2106 fee = unicode(fee_e.text())
2108 fee = self.read_amount(fee)
2110 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2113 self.wallet.set_fee(fee)
2115 nz = unicode(nz_e.text())
2120 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2123 if self.num_zeros != nz:
2125 self.config.set_key('num_zeros', nz, True)
2126 self.update_history_tab()
2127 self.update_receive_tab()
2129 usechange_result = usechange_cb.isChecked()
2130 if self.wallet.use_change != usechange_result:
2131 self.wallet.use_change = usechange_result
2132 self.wallet.storage.put('use_change', self.wallet.use_change)
2134 unit_result = units[unit_combo.currentIndex()]
2135 if self.base_unit() != unit_result:
2136 self.decimal_point = 8 if unit_result == 'BTC' else 5
2137 self.config.set_key('decimal_point', self.decimal_point, True)
2138 self.update_history_tab()
2139 self.update_status()
2141 need_restart = False
2143 lang_request = languages.keys()[lang_combo.currentIndex()]
2144 if lang_request != self.config.get('language'):
2145 self.config.set_key("language", lang_request, True)
2148 run_hook('close_settings_dialog')
2151 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2154 def run_network_dialog(self):
2155 if not self.network:
2157 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2159 def closeEvent(self, event):
2162 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2163 self.save_column_widths()
2164 self.config.set_key("console-history", self.console.history[-50:], True)
2165 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2169 def plugins_dialog(self):
2170 from electrum.plugins import plugins
2173 d.setWindowTitle(_('Electrum Plugins'))
2176 vbox = QVBoxLayout(d)
2179 scroll = QScrollArea()
2180 scroll.setEnabled(True)
2181 scroll.setWidgetResizable(True)
2182 scroll.setMinimumSize(400,250)
2183 vbox.addWidget(scroll)
2187 w.setMinimumHeight(len(plugins)*35)
2189 grid = QGridLayout()
2190 grid.setColumnStretch(0,1)
2193 def do_toggle(cb, p, w):
2196 if w: w.setEnabled(r)
2198 def mk_toggle(cb, p, w):
2199 return lambda: do_toggle(cb,p,w)
2201 for i, p in enumerate(plugins):
2203 cb = QCheckBox(p.fullname())
2204 cb.setDisabled(not p.is_available())
2205 cb.setChecked(p.is_enabled())
2206 grid.addWidget(cb, i, 0)
2207 if p.requires_settings():
2208 w = p.settings_widget(self)
2209 w.setEnabled( p.is_enabled() )
2210 grid.addWidget(w, i, 1)
2213 cb.clicked.connect(mk_toggle(cb,p,w))
2214 grid.addWidget(HelpButton(p.description()), i, 2)
2216 print_msg(_("Error: cannot display plugin"), p)
2217 traceback.print_exc(file=sys.stdout)
2218 grid.setRowStretch(i+1,1)
2220 vbox.addLayout(close_button(d))
2225 def show_account_details(self, k):
2227 d.setWindowTitle(_('Account Details'))
2230 vbox = QVBoxLayout(d)
2231 roots = self.wallet.get_roots(k)
2233 name = self.wallet.get_account_name(k)
2234 label = QLabel('Name: ' + name)
2235 vbox.addWidget(label)
2237 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2238 vbox.addWidget(QLabel('Type: ' + acctype))
2240 label = QLabel('Derivation: ' + k)
2241 vbox.addWidget(label)
2244 # mpk = self.wallet.master_public_keys[root]
2245 # text = QTextEdit()
2246 # text.setReadOnly(True)
2247 # text.setMaximumHeight(120)
2248 # text.setText(repr(mpk))
2249 # vbox.addWidget(text)
2251 vbox.addLayout(close_button(d))