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 d.setWindowTitle(_("New Contact"))
1402 vbox = QVBoxLayout(d)
1403 vbox.addWidget(QLabel(_('New Contact')+':'))
1405 grid = QGridLayout()
1408 grid.addWidget(QLabel(_("Address")), 1, 0)
1409 grid.addWidget(line1, 1, 1)
1410 grid.addWidget(QLabel(_("Name")), 2, 0)
1411 grid.addWidget(line2, 2, 1)
1413 vbox.addLayout(grid)
1414 vbox.addLayout(ok_cancel_buttons(d))
1419 address = str(line1.text())
1420 label = unicode(line2.text())
1422 if not is_valid(address):
1423 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1426 self.wallet.add_contact(address)
1428 self.wallet.set_label(address, label)
1430 self.update_contacts_tab()
1431 self.update_history_tab()
1432 self.update_completions()
1433 self.tabs.setCurrentIndex(3)
1436 def new_account_dialog(self):
1438 dialog = QDialog(self)
1440 dialog.setWindowTitle(_("New Account"))
1442 vbox = QVBoxLayout()
1443 vbox.addWidget(QLabel(_('Account name')+':'))
1446 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1447 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1452 vbox.addLayout(ok_cancel_buttons(dialog))
1453 dialog.setLayout(vbox)
1457 name = str(e.text())
1460 self.wallet.create_pending_account('1', name)
1461 self.update_receive_tab()
1462 self.tabs.setCurrentIndex(2)
1466 def show_master_public_key_old(self):
1467 dialog = QDialog(self)
1469 dialog.setWindowTitle(_("Master Public Key"))
1471 main_text = QTextEdit()
1472 main_text.setText(self.wallet.get_master_public_key())
1473 main_text.setReadOnly(True)
1474 main_text.setMaximumHeight(170)
1475 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1477 ok_button = QPushButton(_("OK"))
1478 ok_button.setDefault(True)
1479 ok_button.clicked.connect(dialog.accept)
1481 main_layout = QGridLayout()
1482 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1484 main_layout.addWidget(main_text, 1, 0)
1485 main_layout.addWidget(qrw, 1, 1 )
1487 vbox = QVBoxLayout()
1488 vbox.addLayout(main_layout)
1489 vbox.addLayout(close_button(dialog))
1490 dialog.setLayout(vbox)
1494 def show_master_public_key(self):
1496 if self.wallet.seed_version == 4:
1497 self.show_master_public_key_old()
1500 dialog = QDialog(self)
1502 dialog.setWindowTitle(_("Master Public Keys"))
1504 chain_text = QTextEdit()
1505 chain_text.setReadOnly(True)
1506 chain_text.setMaximumHeight(170)
1507 chain_qrw = QRCodeWidget()
1509 mpk_text = QTextEdit()
1510 mpk_text.setReadOnly(True)
1511 mpk_text.setMaximumHeight(170)
1512 mpk_qrw = QRCodeWidget()
1514 main_layout = QGridLayout()
1516 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1517 main_layout.addWidget(mpk_text, 1, 1)
1518 main_layout.addWidget(mpk_qrw, 1, 2)
1520 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1521 main_layout.addWidget(chain_text, 2, 1)
1522 main_layout.addWidget(chain_qrw, 2, 2)
1525 c, K, cK = self.wallet.master_public_keys[str(key)]
1526 chain_text.setText(c)
1527 chain_qrw.set_addr(c)
1528 chain_qrw.update_qr()
1533 key_selector = QComboBox()
1534 keys = sorted(self.wallet.master_public_keys.keys())
1535 key_selector.addItems(keys)
1537 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1538 main_layout.addWidget(key_selector, 0, 1)
1539 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1543 vbox = QVBoxLayout()
1544 vbox.addLayout(main_layout)
1545 vbox.addLayout(close_button(dialog))
1547 dialog.setLayout(vbox)
1552 def show_seed_dialog(self, password):
1553 if self.wallet.is_watching_only():
1554 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1557 if self.wallet.seed:
1559 mnemonic = self.wallet.get_mnemonic(password)
1561 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1563 from seed_dialog import SeedDialog
1564 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1568 for k in self.wallet.master_private_keys.keys():
1569 pk = self.wallet.get_master_private_key(k, password)
1571 from seed_dialog import PrivateKeysDialog
1572 d = PrivateKeysDialog(self,l)
1579 def show_qrcode(self, data, title = _("QR code")):
1583 d.setWindowTitle(title)
1584 d.setMinimumSize(270, 300)
1585 vbox = QVBoxLayout()
1586 qrw = QRCodeWidget(data)
1587 vbox.addWidget(qrw, 1)
1588 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1589 hbox = QHBoxLayout()
1592 filename = os.path.join(self.config.path, "qrcode.bmp")
1595 bmp.save_qrcode(qrw.qr, filename)
1596 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1598 def copy_to_clipboard():
1599 bmp.save_qrcode(qrw.qr, filename)
1600 self.app.clipboard().setImage(QImage(filename))
1601 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1603 b = QPushButton(_("Copy"))
1605 b.clicked.connect(copy_to_clipboard)
1607 b = QPushButton(_("Save"))
1609 b.clicked.connect(print_qr)
1611 b = QPushButton(_("Close"))
1613 b.clicked.connect(d.accept)
1616 vbox.addLayout(hbox)
1621 def do_protect(self, func, args):
1622 if self.wallet.use_encryption:
1623 password = self.password_dialog()
1629 if args != (False,):
1630 args = (self,) + args + (password,)
1632 args = (self,password)
1637 def show_private_key(self, address, password):
1638 if not address: return
1640 pk_list = self.wallet.get_private_key(address, password)
1641 except Exception as e:
1642 self.show_message(str(e))
1646 d.setMinimumSize(600, 200)
1648 vbox = QVBoxLayout()
1649 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1650 vbox.addWidget( QLabel(_("Private key") + ':'))
1652 keys.setReadOnly(True)
1653 keys.setText('\n'.join(pk_list))
1654 vbox.addWidget(keys)
1655 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1656 vbox.addLayout(close_button(d))
1662 def do_sign(self, address, message, signature, password):
1663 message = unicode(message.toPlainText())
1664 message = message.encode('utf-8')
1666 sig = self.wallet.sign_message(str(address.text()), message, password)
1667 signature.setText(sig)
1668 except Exception as e:
1669 self.show_message(str(e))
1671 def do_verify(self, address, message, signature):
1672 message = unicode(message.toPlainText())
1673 message = message.encode('utf-8')
1674 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1675 self.show_message(_("Signature verified"))
1677 self.show_message(_("Error: wrong signature"))
1680 def sign_verify_message(self, address=''):
1683 d.setWindowTitle(_('Sign/verify Message'))
1684 d.setMinimumSize(410, 290)
1686 layout = QGridLayout(d)
1688 message_e = QTextEdit()
1689 layout.addWidget(QLabel(_('Message')), 1, 0)
1690 layout.addWidget(message_e, 1, 1)
1691 layout.setRowStretch(2,3)
1693 address_e = QLineEdit()
1694 address_e.setText(address)
1695 layout.addWidget(QLabel(_('Address')), 2, 0)
1696 layout.addWidget(address_e, 2, 1)
1698 signature_e = QTextEdit()
1699 layout.addWidget(QLabel(_('Signature')), 3, 0)
1700 layout.addWidget(signature_e, 3, 1)
1701 layout.setRowStretch(3,1)
1703 hbox = QHBoxLayout()
1705 b = QPushButton(_("Sign"))
1706 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1709 b = QPushButton(_("Verify"))
1710 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1713 b = QPushButton(_("Close"))
1714 b.clicked.connect(d.accept)
1716 layout.addLayout(hbox, 4, 1)
1721 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1723 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1724 message_e.setText(decrypted)
1725 except Exception as e:
1726 self.show_message(str(e))
1729 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1730 message = unicode(message_e.toPlainText())
1731 message = message.encode('utf-8')
1733 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1734 encrypted_e.setText(encrypted)
1735 except Exception as e:
1736 self.show_message(str(e))
1740 def encrypt_message(self, address = ''):
1743 d.setWindowTitle(_('Encrypt/decrypt Message'))
1744 d.setMinimumSize(610, 490)
1746 layout = QGridLayout(d)
1748 message_e = QTextEdit()
1749 layout.addWidget(QLabel(_('Message')), 1, 0)
1750 layout.addWidget(message_e, 1, 1)
1751 layout.setRowStretch(2,3)
1753 pubkey_e = QLineEdit()
1755 pubkey = self.wallet.getpubkeys(address)[0]
1756 pubkey_e.setText(pubkey)
1757 layout.addWidget(QLabel(_('Public key')), 2, 0)
1758 layout.addWidget(pubkey_e, 2, 1)
1760 encrypted_e = QTextEdit()
1761 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1762 layout.addWidget(encrypted_e, 3, 1)
1763 layout.setRowStretch(3,1)
1765 hbox = QHBoxLayout()
1766 b = QPushButton(_("Encrypt"))
1767 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1770 b = QPushButton(_("Decrypt"))
1771 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1774 b = QPushButton(_("Close"))
1775 b.clicked.connect(d.accept)
1778 layout.addLayout(hbox, 4, 1)
1782 def question(self, msg):
1783 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1785 def show_message(self, msg):
1786 QMessageBox.information(self, _('Message'), msg, _('OK'))
1788 def password_dialog(self ):
1791 d.setWindowTitle(_("Enter Password"))
1796 vbox = QVBoxLayout()
1797 msg = _('Please enter your password')
1798 vbox.addWidget(QLabel(msg))
1800 grid = QGridLayout()
1802 grid.addWidget(QLabel(_('Password')), 1, 0)
1803 grid.addWidget(pw, 1, 1)
1804 vbox.addLayout(grid)
1806 vbox.addLayout(ok_cancel_buttons(d))
1809 run_hook('password_dialog', pw, grid, 1)
1810 if not d.exec_(): return
1811 return unicode(pw.text())
1820 def tx_from_text(self, txt):
1821 "json or raw hexadecimal"
1824 tx = Transaction(txt)
1830 tx_dict = json.loads(str(txt))
1831 assert "hex" in tx_dict.keys()
1832 assert "complete" in tx_dict.keys()
1833 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1834 if not tx_dict["complete"]:
1835 assert "input_info" in tx_dict.keys()
1836 input_info = json.loads(tx_dict['input_info'])
1837 tx.add_input_info(input_info)
1842 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1846 def read_tx_from_file(self):
1847 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1851 with open(fileName, "r") as f:
1852 file_content = f.read()
1853 except (ValueError, IOError, os.error), reason:
1854 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1856 return self.tx_from_text(file_content)
1860 def sign_raw_transaction(self, tx, input_info, password):
1861 self.wallet.signrawtransaction(tx, input_info, [], password)
1863 def do_process_from_text(self):
1864 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1867 tx = self.tx_from_text(text)
1869 self.show_transaction(tx)
1871 def do_process_from_file(self):
1872 tx = self.read_tx_from_file()
1874 self.show_transaction(tx)
1876 def do_process_from_txid(self):
1877 from electrum import transaction
1878 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1880 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1882 tx = transaction.Transaction(r)
1884 self.show_transaction(tx)
1886 self.show_message("unknown transaction")
1888 def do_process_from_csvReader(self, csvReader):
1893 for position, row in enumerate(csvReader):
1895 if not is_valid(address):
1896 errors.append((position, address))
1898 amount = Decimal(row[1])
1899 amount = int(100000000*amount)
1900 outputs.append((address, amount))
1901 except (ValueError, IOError, os.error), reason:
1902 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1906 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1907 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1911 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1912 except Exception as e:
1913 self.show_message(str(e))
1916 self.show_transaction(tx)
1918 def do_process_from_csv_file(self):
1919 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1923 with open(fileName, "r") as f:
1924 csvReader = csv.reader(f)
1925 self.do_process_from_csvReader(csvReader)
1926 except (ValueError, IOError, os.error), reason:
1927 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1930 def do_process_from_csv_text(self):
1931 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1932 + _("Format: address, amount. One output per line"), _("Load CSV"))
1935 f = StringIO.StringIO(text)
1936 csvReader = csv.reader(f)
1937 self.do_process_from_csvReader(csvReader)
1942 def do_export_privkeys(self, password):
1943 if not self.wallet.seed:
1944 self.show_message(_("This wallet has no seed"))
1947 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.")))
1950 select_export = _('Select file to export your private keys to')
1951 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1953 with open(fileName, "w+") as csvfile:
1954 transaction = csv.writer(csvfile)
1955 transaction.writerow(["address", "private_key"])
1957 addresses = self.wallet.addresses(True)
1959 for addr in addresses:
1960 pk = "".join(self.wallet.get_private_key(addr, password))
1961 transaction.writerow(["%34s"%addr,pk])
1963 self.show_message(_("Private keys exported."))
1965 except (IOError, os.error), reason:
1966 export_error_label = _("Electrum was unable to produce a private key-export.")
1967 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1969 except Exception as e:
1970 self.show_message(str(e))
1974 def do_import_labels(self):
1975 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1976 if not labelsFile: return
1978 f = open(labelsFile, 'r')
1981 for key, value in json.loads(data).items():
1982 self.wallet.set_label(key, value)
1983 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1984 except (IOError, os.error), reason:
1985 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1988 def do_export_labels(self):
1989 labels = self.wallet.labels
1991 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1993 with open(fileName, 'w+') as f:
1994 json.dump(labels, f)
1995 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1996 except (IOError, os.error), reason:
1997 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2000 def do_export_history(self):
2001 from lite_window import csv_transaction
2002 csv_transaction(self.wallet)
2006 def do_import_privkey(self, password):
2007 if not self.wallet.imported_keys:
2008 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2009 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2010 + _('Are you sure you understand what you are doing?'), 3, 4)
2013 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2016 text = str(text).split()
2021 addr = self.wallet.import_key(key, password)
2022 except Exception as e:
2028 addrlist.append(addr)
2030 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2032 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2033 self.update_receive_tab()
2034 self.update_history_tab()
2037 def settings_dialog(self):
2039 d.setWindowTitle(_('Electrum Settings'))
2041 vbox = QVBoxLayout()
2042 grid = QGridLayout()
2043 grid.setColumnStretch(0,1)
2045 nz_label = QLabel(_('Display zeros') + ':')
2046 grid.addWidget(nz_label, 0, 0)
2047 nz_e = AmountEdit(None,True)
2048 nz_e.setText("%d"% self.num_zeros)
2049 grid.addWidget(nz_e, 0, 1)
2050 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2051 grid.addWidget(HelpButton(msg), 0, 2)
2052 if not self.config.is_modifiable('num_zeros'):
2053 for w in [nz_e, nz_label]: w.setEnabled(False)
2055 lang_label=QLabel(_('Language') + ':')
2056 grid.addWidget(lang_label, 1, 0)
2057 lang_combo = QComboBox()
2058 from electrum.i18n import languages
2059 lang_combo.addItems(languages.values())
2061 index = languages.keys().index(self.config.get("language",''))
2064 lang_combo.setCurrentIndex(index)
2065 grid.addWidget(lang_combo, 1, 1)
2066 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2067 if not self.config.is_modifiable('language'):
2068 for w in [lang_combo, lang_label]: w.setEnabled(False)
2071 fee_label = QLabel(_('Transaction fee') + ':')
2072 grid.addWidget(fee_label, 2, 0)
2073 fee_e = AmountEdit(self.base_unit)
2074 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2075 grid.addWidget(fee_e, 2, 1)
2076 msg = _('Fee per kilobyte of transaction.') + ' ' \
2077 + _('Recommended value') + ': ' + self.format_amount(20000)
2078 grid.addWidget(HelpButton(msg), 2, 2)
2079 if not self.config.is_modifiable('fee_per_kb'):
2080 for w in [fee_e, fee_label]: w.setEnabled(False)
2082 units = ['BTC', 'mBTC']
2083 unit_label = QLabel(_('Base unit') + ':')
2084 grid.addWidget(unit_label, 3, 0)
2085 unit_combo = QComboBox()
2086 unit_combo.addItems(units)
2087 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2088 grid.addWidget(unit_combo, 3, 1)
2089 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2090 + '\n1BTC=1000mBTC.\n' \
2091 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2093 usechange_cb = QCheckBox(_('Use change addresses'))
2094 usechange_cb.setChecked(self.wallet.use_change)
2095 grid.addWidget(usechange_cb, 4, 0)
2096 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2097 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2099 grid.setRowStretch(5,1)
2101 vbox.addLayout(grid)
2102 vbox.addLayout(ok_cancel_buttons(d))
2106 if not d.exec_(): return
2108 fee = unicode(fee_e.text())
2110 fee = self.read_amount(fee)
2112 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2115 self.wallet.set_fee(fee)
2117 nz = unicode(nz_e.text())
2122 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2125 if self.num_zeros != nz:
2127 self.config.set_key('num_zeros', nz, True)
2128 self.update_history_tab()
2129 self.update_receive_tab()
2131 usechange_result = usechange_cb.isChecked()
2132 if self.wallet.use_change != usechange_result:
2133 self.wallet.use_change = usechange_result
2134 self.wallet.storage.put('use_change', self.wallet.use_change)
2136 unit_result = units[unit_combo.currentIndex()]
2137 if self.base_unit() != unit_result:
2138 self.decimal_point = 8 if unit_result == 'BTC' else 5
2139 self.config.set_key('decimal_point', self.decimal_point, True)
2140 self.update_history_tab()
2141 self.update_status()
2143 need_restart = False
2145 lang_request = languages.keys()[lang_combo.currentIndex()]
2146 if lang_request != self.config.get('language'):
2147 self.config.set_key("language", lang_request, True)
2150 run_hook('close_settings_dialog')
2153 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2156 def run_network_dialog(self):
2157 if not self.network:
2159 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2161 def closeEvent(self, event):
2164 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2165 self.save_column_widths()
2166 self.config.set_key("console-history", self.console.history[-50:], True)
2167 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2171 def plugins_dialog(self):
2172 from electrum.plugins import plugins
2175 d.setWindowTitle(_('Electrum Plugins'))
2178 vbox = QVBoxLayout(d)
2181 scroll = QScrollArea()
2182 scroll.setEnabled(True)
2183 scroll.setWidgetResizable(True)
2184 scroll.setMinimumSize(400,250)
2185 vbox.addWidget(scroll)
2189 w.setMinimumHeight(len(plugins)*35)
2191 grid = QGridLayout()
2192 grid.setColumnStretch(0,1)
2195 def do_toggle(cb, p, w):
2198 if w: w.setEnabled(r)
2200 def mk_toggle(cb, p, w):
2201 return lambda: do_toggle(cb,p,w)
2203 for i, p in enumerate(plugins):
2205 cb = QCheckBox(p.fullname())
2206 cb.setDisabled(not p.is_available())
2207 cb.setChecked(p.is_enabled())
2208 grid.addWidget(cb, i, 0)
2209 if p.requires_settings():
2210 w = p.settings_widget(self)
2211 w.setEnabled( p.is_enabled() )
2212 grid.addWidget(w, i, 1)
2215 cb.clicked.connect(mk_toggle(cb,p,w))
2216 grid.addWidget(HelpButton(p.description()), i, 2)
2218 print_msg(_("Error: cannot display plugin"), p)
2219 traceback.print_exc(file=sys.stdout)
2220 grid.setRowStretch(i+1,1)
2222 vbox.addLayout(close_button(d))
2227 def show_account_details(self, k):
2229 d.setWindowTitle(_('Account Details'))
2232 vbox = QVBoxLayout(d)
2233 roots = self.wallet.get_roots(k)
2235 name = self.wallet.get_account_name(k)
2236 label = QLabel('Name: ' + name)
2237 vbox.addWidget(label)
2239 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2240 vbox.addWidget(QLabel('Type: ' + acctype))
2242 label = QLabel('Derivation: ' + k)
2243 vbox.addWidget(label)
2246 # mpk = self.wallet.master_public_keys[root]
2247 # text = QTextEdit()
2248 # text.setReadOnly(True)
2249 # text.setMaximumHeight(120)
2250 # text.setText(repr(mpk))
2251 # vbox.addWidget(text)
2253 vbox.addLayout(close_button(d))