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)
728 self.send_button = EnterButton(_("Send"), self.do_send)
729 grid.addWidget(self.send_button, 6, 1)
731 b = EnterButton(_("Clear"),self.do_clear)
732 grid.addWidget(b, 6, 2)
734 self.payto_sig = QLabel('')
735 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
737 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
738 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
747 def entry_changed( is_fee ):
748 self.funds_error = False
750 if self.amount_e.is_shortcut:
751 self.amount_e.is_shortcut = False
752 sendable = self.get_sendable_balance()
753 # there is only one output because we are completely spending inputs
754 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
755 fee = self.wallet.estimated_fee(inputs, 1)
757 self.amount_e.setText( self.format_amount(amount) )
758 self.fee_e.setText( self.format_amount( fee ) )
761 amount = self.read_amount(str(self.amount_e.text()))
762 fee = self.read_amount(str(self.fee_e.text()))
764 if not is_fee: fee = None
767 # assume that there will be 2 outputs (one for change)
768 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
770 self.fee_e.setText( self.format_amount( fee ) )
773 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
777 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
778 self.funds_error = True
779 text = _( "Not enough funds" )
780 c, u = self.wallet.get_frozen_balance()
781 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
783 self.statusBar().showMessage(text)
784 self.amount_e.setPalette(palette)
785 self.fee_e.setPalette(palette)
787 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
788 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
790 run_hook('create_send_tab', grid)
794 def set_pay_from(self, l):
796 self.from_list.clear()
797 self.from_label.setHidden(len(self.pay_from) == 0)
798 self.from_list.setHidden(len(self.pay_from) == 0)
799 for addr in self.pay_from:
800 c, u = self.wallet.get_addr_balance(addr)
801 balance = self.format_amount(c + u)
802 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
805 def update_completions(self):
807 for addr,label in self.wallet.labels.items():
808 if addr in self.wallet.addressbook:
809 l.append( label + ' <' + addr + '>')
811 run_hook('update_completions', l)
812 self.completions.setStringList(l)
816 return lambda s, *args: s.do_protect(func, args)
821 label = unicode( self.message_e.text() )
822 r = unicode( self.payto_e.text() )
825 # label or alias, with address in brackets
826 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
827 to_address = m.group(2) if m else r
829 if not is_valid(to_address):
830 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
834 amount = self.read_amount(unicode( self.amount_e.text()))
836 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
839 fee = self.read_amount(unicode( self.fee_e.text()))
841 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
844 confirm_amount = self.config.get('confirm_amount', 100000000)
845 if amount >= confirm_amount:
846 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
849 confirm_fee = self.config.get('confirm_fee', 100000)
850 if fee >= confirm_fee:
851 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()}):
854 self.send_tx(to_address, amount, fee, label)
858 def send_tx(self, to_address, amount, fee, label, password):
860 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
861 domain=self.get_payment_sources())
862 except Exception as e:
863 traceback.print_exc(file=sys.stdout)
864 self.show_message(str(e))
867 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
868 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
872 self.wallet.set_label(tx.hash(), label)
875 h = self.wallet.send_tx(tx)
876 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
877 status, msg = self.wallet.receive_tx( h, tx )
879 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
881 self.update_contacts_tab()
883 QMessageBox.warning(self, _('Error'), msg, _('OK'))
886 self.show_transaction(tx)
888 # add recipient to addressbook
889 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
890 self.wallet.addressbook.append(to_address)
895 def set_url(self, url):
896 address, amount, label, message, signature, identity, url = util.parse_url(url)
899 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
900 elif amount: amount = str(Decimal(amount))
903 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
906 self.mini.set_payment_fields(address, amount)
908 if label and self.wallet.labels.get(address) != label:
909 if self.question('Give label "%s" to address %s ?'%(label,address)):
910 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
911 self.wallet.addressbook.append(address)
912 self.wallet.set_label(address, label)
914 run_hook('set_url', url, self.show_message, self.question)
916 self.tabs.setCurrentIndex(1)
917 label = self.wallet.labels.get(address)
918 m_addr = label + ' <'+ address +'>' if label else address
919 self.payto_e.setText(m_addr)
921 self.message_e.setText(message)
923 self.amount_e.setText(amount)
926 self.set_frozen(self.payto_e,True)
927 self.set_frozen(self.amount_e,True)
928 self.set_frozen(self.message_e,True)
929 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
931 self.payto_sig.setVisible(False)
934 self.payto_sig.setVisible(False)
935 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
937 self.set_frozen(e,False)
939 self.set_pay_from([])
942 def set_frozen(self,entry,frozen):
944 entry.setReadOnly(True)
945 entry.setFrame(False)
947 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
948 entry.setPalette(palette)
950 entry.setReadOnly(False)
953 palette.setColor(entry.backgroundRole(), QColor('white'))
954 entry.setPalette(palette)
957 def set_addrs_frozen(self,addrs,freeze):
959 if not addr: continue
960 if addr in self.wallet.frozen_addresses and not freeze:
961 self.wallet.unfreeze(addr)
962 elif addr not in self.wallet.frozen_addresses and freeze:
963 self.wallet.freeze(addr)
964 self.update_receive_tab()
968 def create_list_tab(self, headers):
969 "generic tab creation method"
970 l = MyTreeWidget(self)
971 l.setColumnCount( len(headers) )
972 l.setHeaderLabels( headers )
982 vbox.addWidget(buttons)
987 buttons.setLayout(hbox)
992 def create_receive_tab(self):
993 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
994 l.setContextMenuPolicy(Qt.CustomContextMenu)
995 l.customContextMenuRequested.connect(self.create_receive_menu)
996 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
997 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
998 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
999 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1000 self.receive_list = l
1001 self.receive_buttons_hbox = hbox
1008 def save_column_widths(self):
1009 self.column_widths["receive"] = []
1010 for i in range(self.receive_list.columnCount() -1):
1011 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1013 self.column_widths["history"] = []
1014 for i in range(self.history_list.columnCount() - 1):
1015 self.column_widths["history"].append(self.history_list.columnWidth(i))
1017 self.column_widths["contacts"] = []
1018 for i in range(self.contacts_list.columnCount() - 1):
1019 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1021 self.config.set_key("column_widths_2", self.column_widths, True)
1024 def create_contacts_tab(self):
1025 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1026 l.setContextMenuPolicy(Qt.CustomContextMenu)
1027 l.customContextMenuRequested.connect(self.create_contact_menu)
1028 for i,width in enumerate(self.column_widths['contacts']):
1029 l.setColumnWidth(i, width)
1031 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1032 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1033 self.contacts_list = l
1034 self.contacts_buttons_hbox = hbox
1039 def delete_imported_key(self, addr):
1040 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1041 self.wallet.delete_imported_key(addr)
1042 self.update_receive_tab()
1043 self.update_history_tab()
1045 def edit_account_label(self, k):
1046 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1048 label = unicode(text)
1049 self.wallet.set_label(k,label)
1050 self.update_receive_tab()
1052 def account_set_expanded(self, item, k, b):
1054 self.accounts_expanded[k] = b
1056 def create_account_menu(self, position, k, item):
1058 if item.isExpanded():
1059 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1061 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1062 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1063 if self.wallet.seed_version > 4:
1064 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1065 if self.wallet.account_is_pending(k):
1066 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1067 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1069 def delete_pending_account(self, k):
1070 self.wallet.delete_pending_account(k)
1071 self.update_receive_tab()
1073 def create_receive_menu(self, position):
1074 # fixme: this function apparently has a side effect.
1075 # if it is not called the menu pops up several times
1076 #self.receive_list.selectedIndexes()
1078 selected = self.receive_list.selectedItems()
1079 multi_select = len(selected) > 1
1080 addrs = [unicode(item.text(0)) for item in selected]
1081 if not multi_select:
1082 item = self.receive_list.itemAt(position)
1086 if not is_valid(addr):
1087 k = str(item.data(0,32).toString())
1089 self.create_account_menu(position, k, item)
1091 item.setExpanded(not item.isExpanded())
1095 if not multi_select:
1096 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1097 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1098 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1099 if self.wallet.seed:
1100 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1101 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1102 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1103 if addr in self.wallet.imported_keys:
1104 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1106 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1107 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1108 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1109 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1111 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1112 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1114 run_hook('receive_menu', menu, addrs)
1115 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1118 def get_sendable_balance(self):
1119 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1122 def get_payment_sources(self):
1124 return self.pay_from
1126 return self.wallet.get_account_addresses(self.current_account)
1129 def send_from_addresses(self, addrs):
1130 self.set_pay_from( addrs )
1131 self.tabs.setCurrentIndex(1)
1134 def payto(self, addr):
1136 label = self.wallet.labels.get(addr)
1137 m_addr = label + ' <' + addr + '>' if label else addr
1138 self.tabs.setCurrentIndex(1)
1139 self.payto_e.setText(m_addr)
1140 self.amount_e.setFocus()
1143 def delete_contact(self, x):
1144 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1145 self.wallet.delete_contact(x)
1146 self.wallet.set_label(x, None)
1147 self.update_history_tab()
1148 self.update_contacts_tab()
1149 self.update_completions()
1152 def create_contact_menu(self, position):
1153 item = self.contacts_list.itemAt(position)
1156 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1158 addr = unicode(item.text(0))
1159 label = unicode(item.text(1))
1160 is_editable = item.data(0,32).toBool()
1161 payto_addr = item.data(0,33).toString()
1162 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1163 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1164 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1166 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1167 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1169 run_hook('create_contact_menu', menu, item)
1170 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1173 def update_receive_item(self, item):
1174 item.setFont(0, QFont(MONOSPACE_FONT))
1175 address = str(item.data(0,0).toString())
1176 label = self.wallet.labels.get(address,'')
1177 item.setData(1,0,label)
1178 item.setData(0,32, True) # is editable
1180 run_hook('update_receive_item', address, item)
1182 if not self.wallet.is_mine(address): return
1184 c, u = self.wallet.get_addr_balance(address)
1185 balance = self.format_amount(c + u)
1186 item.setData(2,0,balance)
1188 if address in self.wallet.frozen_addresses:
1189 item.setBackgroundColor(0, QColor('lightblue'))
1192 def update_receive_tab(self):
1193 l = self.receive_list
1196 l.setColumnHidden(2, False)
1197 l.setColumnHidden(3, False)
1198 for i,width in enumerate(self.column_widths['receive']):
1199 l.setColumnWidth(i, width)
1201 if self.current_account is None:
1202 account_items = self.wallet.accounts.items()
1203 elif self.current_account != -1:
1204 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1208 for k, account in account_items:
1209 name = self.wallet.get_account_name(k)
1210 c,u = self.wallet.get_account_balance(k)
1211 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1212 l.addTopLevelItem(account_item)
1213 account_item.setExpanded(self.accounts_expanded.get(k, True))
1214 account_item.setData(0, 32, k)
1216 if not self.wallet.is_seeded(k):
1217 icon = QIcon(":icons/key.png")
1218 account_item.setIcon(0, icon)
1220 for is_change in ([0,1]):
1221 name = _("Receiving") if not is_change else _("Change")
1222 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1223 account_item.addChild(seq_item)
1224 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1226 if not is_change: seq_item.setExpanded(True)
1231 for address in account.get_addresses(is_change):
1232 h = self.wallet.history.get(address,[])
1236 if gap > self.wallet.gap_limit:
1241 c, u = self.wallet.get_addr_balance(address)
1242 num_tx = '*' if h == ['*'] else "%d"%len(h)
1243 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1244 self.update_receive_item(item)
1246 item.setBackgroundColor(1, QColor('red'))
1247 if len(h) > 0 and c == -u:
1249 seq_item.insertChild(0,used_item)
1251 used_item.addChild(item)
1253 seq_item.addChild(item)
1256 for k, addr in self.wallet.get_pending_accounts():
1257 name = self.wallet.labels.get(k,'')
1258 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1259 self.update_receive_item(item)
1260 l.addTopLevelItem(account_item)
1261 account_item.setExpanded(True)
1262 account_item.setData(0, 32, k)
1263 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1264 account_item.addChild(item)
1265 self.update_receive_item(item)
1268 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1269 c,u = self.wallet.get_imported_balance()
1270 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1271 l.addTopLevelItem(account_item)
1272 account_item.setExpanded(True)
1273 for address in self.wallet.imported_keys.keys():
1274 item = QTreeWidgetItem( [ address, '', '', ''] )
1275 self.update_receive_item(item)
1276 account_item.addChild(item)
1279 # we use column 1 because column 0 may be hidden
1280 l.setCurrentItem(l.topLevelItem(0),1)
1283 def update_contacts_tab(self):
1284 l = self.contacts_list
1287 for address in self.wallet.addressbook:
1288 label = self.wallet.labels.get(address,'')
1289 n = self.wallet.get_num_tx(address)
1290 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1291 item.setFont(0, QFont(MONOSPACE_FONT))
1292 # 32 = label can be edited (bool)
1293 item.setData(0,32, True)
1295 item.setData(0,33, address)
1296 l.addTopLevelItem(item)
1298 run_hook('update_contacts_tab', l)
1299 l.setCurrentItem(l.topLevelItem(0))
1303 def create_console_tab(self):
1304 from console import Console
1305 self.console = console = Console()
1309 def update_console(self):
1310 console = self.console
1311 console.history = self.config.get("console-history",[])
1312 console.history_index = len(console.history)
1314 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1315 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1317 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1319 def mkfunc(f, method):
1320 return lambda *args: apply( f, (method, args, self.password_dialog ))
1322 if m[0]=='_' or m in ['network','wallet']: continue
1323 methods[m] = mkfunc(c._run, m)
1325 console.updateNamespace(methods)
1328 def change_account(self,s):
1329 if s == _("All accounts"):
1330 self.current_account = None
1332 accounts = self.wallet.get_account_names()
1333 for k, v in accounts.items():
1335 self.current_account = k
1336 self.update_history_tab()
1337 self.update_status()
1338 self.update_receive_tab()
1340 def create_status_bar(self):
1343 sb.setFixedHeight(35)
1344 qtVersion = qVersion()
1346 self.balance_label = QLabel("")
1347 sb.addWidget(self.balance_label)
1349 from version_getter import UpdateLabel
1350 self.updatelabel = UpdateLabel(self.config, sb)
1352 self.account_selector = QComboBox()
1353 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1354 sb.addPermanentWidget(self.account_selector)
1356 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1357 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1359 self.lock_icon = QIcon()
1360 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1361 sb.addPermanentWidget( self.password_button )
1363 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1364 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1365 sb.addPermanentWidget( self.seed_button )
1366 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1367 sb.addPermanentWidget( self.status_button )
1369 run_hook('create_status_bar', (sb,))
1371 self.setStatusBar(sb)
1374 def update_lock_icon(self):
1375 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1376 self.password_button.setIcon( icon )
1379 def update_buttons_on_seed(self):
1380 if not self.wallet.is_watching_only():
1381 self.seed_button.show()
1382 self.password_button.show()
1383 self.send_button.setText(_("Send"))
1385 self.password_button.hide()
1386 self.seed_button.hide()
1387 self.send_button.setText(_("Create unsigned transaction"))
1390 def change_password_dialog(self):
1391 from password_dialog import PasswordDialog
1392 d = PasswordDialog(self.wallet, self)
1394 self.update_lock_icon()
1397 def new_contact_dialog(self):
1400 vbox = QVBoxLayout(d)
1401 vbox.addWidget(QLabel(_('New Contact')+':'))
1403 grid = QGridLayout()
1406 grid.addWidget(QLabel(_("Address")), 1, 0)
1407 grid.addWidget(line1, 1, 1)
1408 grid.addWidget(QLabel(_("Name")), 2, 0)
1409 grid.addWidget(line2, 2, 1)
1411 vbox.addLayout(grid)
1412 vbox.addLayout(ok_cancel_buttons(d))
1417 address = str(line1.text())
1418 label = unicode(line2.text())
1420 if not is_valid(address):
1421 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1424 self.wallet.add_contact(address)
1426 self.wallet.set_label(address, label)
1428 self.update_contacts_tab()
1429 self.update_history_tab()
1430 self.update_completions()
1431 self.tabs.setCurrentIndex(3)
1434 def new_account_dialog(self):
1436 dialog = QDialog(self)
1438 dialog.setWindowTitle(_("New Account"))
1440 vbox = QVBoxLayout()
1441 vbox.addWidget(QLabel(_('Account name')+':'))
1444 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1445 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1450 vbox.addLayout(ok_cancel_buttons(dialog))
1451 dialog.setLayout(vbox)
1455 name = str(e.text())
1458 self.wallet.create_pending_account('1', name)
1459 self.update_receive_tab()
1460 self.tabs.setCurrentIndex(2)
1464 def show_master_public_key_old(self):
1465 dialog = QDialog(self)
1467 dialog.setWindowTitle(_("Master Public Key"))
1469 main_text = QTextEdit()
1470 main_text.setText(self.wallet.get_master_public_key())
1471 main_text.setReadOnly(True)
1472 main_text.setMaximumHeight(170)
1473 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1475 ok_button = QPushButton(_("OK"))
1476 ok_button.setDefault(True)
1477 ok_button.clicked.connect(dialog.accept)
1479 main_layout = QGridLayout()
1480 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1482 main_layout.addWidget(main_text, 1, 0)
1483 main_layout.addWidget(qrw, 1, 1 )
1485 vbox = QVBoxLayout()
1486 vbox.addLayout(main_layout)
1487 vbox.addLayout(close_button(dialog))
1488 dialog.setLayout(vbox)
1492 def show_master_public_key(self):
1494 if self.wallet.seed_version == 4:
1495 self.show_master_public_key_old()
1498 dialog = QDialog(self)
1500 dialog.setWindowTitle(_("Master Public Keys"))
1502 chain_text = QTextEdit()
1503 chain_text.setReadOnly(True)
1504 chain_text.setMaximumHeight(170)
1505 chain_qrw = QRCodeWidget()
1507 mpk_text = QTextEdit()
1508 mpk_text.setReadOnly(True)
1509 mpk_text.setMaximumHeight(170)
1510 mpk_qrw = QRCodeWidget()
1512 main_layout = QGridLayout()
1514 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1515 main_layout.addWidget(mpk_text, 1, 1)
1516 main_layout.addWidget(mpk_qrw, 1, 2)
1518 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1519 main_layout.addWidget(chain_text, 2, 1)
1520 main_layout.addWidget(chain_qrw, 2, 2)
1523 c, K, cK = self.wallet.master_public_keys[str(key)]
1524 chain_text.setText(c)
1525 chain_qrw.set_addr(c)
1526 chain_qrw.update_qr()
1531 key_selector = QComboBox()
1532 keys = sorted(self.wallet.master_public_keys.keys())
1533 key_selector.addItems(keys)
1535 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1536 main_layout.addWidget(key_selector, 0, 1)
1537 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1541 vbox = QVBoxLayout()
1542 vbox.addLayout(main_layout)
1543 vbox.addLayout(close_button(dialog))
1545 dialog.setLayout(vbox)
1550 def show_seed_dialog(self, password):
1551 if self.wallet.is_watching_only():
1552 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1555 if self.wallet.seed:
1557 mnemonic = self.wallet.get_mnemonic(password)
1559 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1561 from seed_dialog import SeedDialog
1562 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1566 for k in self.wallet.master_private_keys.keys():
1567 pk = self.wallet.get_master_private_key(k, password)
1569 from seed_dialog import PrivateKeysDialog
1570 d = PrivateKeysDialog(self,l)
1577 def show_qrcode(self, data, title = _("QR code")):
1581 d.setWindowTitle(title)
1582 d.setMinimumSize(270, 300)
1583 vbox = QVBoxLayout()
1584 qrw = QRCodeWidget(data)
1585 vbox.addWidget(qrw, 1)
1586 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1587 hbox = QHBoxLayout()
1590 filename = os.path.join(self.config.path, "qrcode.bmp")
1593 bmp.save_qrcode(qrw.qr, filename)
1594 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1596 def copy_to_clipboard():
1597 bmp.save_qrcode(qrw.qr, filename)
1598 self.app.clipboard().setImage(QImage(filename))
1599 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1601 b = QPushButton(_("Copy"))
1603 b.clicked.connect(copy_to_clipboard)
1605 b = QPushButton(_("Save"))
1607 b.clicked.connect(print_qr)
1609 b = QPushButton(_("Close"))
1611 b.clicked.connect(d.accept)
1614 vbox.addLayout(hbox)
1619 def do_protect(self, func, args):
1620 if self.wallet.use_encryption:
1621 password = self.password_dialog()
1627 if args != (False,):
1628 args = (self,) + args + (password,)
1630 args = (self,password)
1635 def show_private_key(self, address, password):
1636 if not address: return
1638 pk_list = self.wallet.get_private_key(address, password)
1639 except Exception as e:
1640 self.show_message(str(e))
1644 d.setMinimumSize(600, 200)
1646 vbox = QVBoxLayout()
1647 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1648 vbox.addWidget( QLabel(_("Private key") + ':'))
1650 keys.setReadOnly(True)
1651 keys.setText('\n'.join(pk_list))
1652 vbox.addWidget(keys)
1653 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1654 vbox.addLayout(close_button(d))
1660 def do_sign(self, address, message, signature, password):
1661 message = unicode(message.toPlainText())
1662 message = message.encode('utf-8')
1664 sig = self.wallet.sign_message(str(address.text()), message, password)
1665 signature.setText(sig)
1666 except Exception as e:
1667 self.show_message(str(e))
1669 def do_verify(self, address, message, signature):
1670 message = unicode(message.toPlainText())
1671 message = message.encode('utf-8')
1672 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1673 self.show_message(_("Signature verified"))
1675 self.show_message(_("Error: wrong signature"))
1678 def sign_verify_message(self, address=''):
1681 d.setWindowTitle(_('Sign/verify Message'))
1682 d.setMinimumSize(410, 290)
1684 layout = QGridLayout(d)
1686 message_e = QTextEdit()
1687 layout.addWidget(QLabel(_('Message')), 1, 0)
1688 layout.addWidget(message_e, 1, 1)
1689 layout.setRowStretch(2,3)
1691 address_e = QLineEdit()
1692 address_e.setText(address)
1693 layout.addWidget(QLabel(_('Address')), 2, 0)
1694 layout.addWidget(address_e, 2, 1)
1696 signature_e = QTextEdit()
1697 layout.addWidget(QLabel(_('Signature')), 3, 0)
1698 layout.addWidget(signature_e, 3, 1)
1699 layout.setRowStretch(3,1)
1701 hbox = QHBoxLayout()
1703 b = QPushButton(_("Sign"))
1704 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1707 b = QPushButton(_("Verify"))
1708 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1711 b = QPushButton(_("Close"))
1712 b.clicked.connect(d.accept)
1714 layout.addLayout(hbox, 4, 1)
1719 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1721 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1722 message_e.setText(decrypted)
1723 except Exception as e:
1724 self.show_message(str(e))
1727 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1728 message = unicode(message_e.toPlainText())
1729 message = message.encode('utf-8')
1731 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1732 encrypted_e.setText(encrypted)
1733 except Exception as e:
1734 self.show_message(str(e))
1738 def encrypt_message(self, address = ''):
1741 d.setWindowTitle(_('Encrypt/decrypt Message'))
1742 d.setMinimumSize(610, 490)
1744 layout = QGridLayout(d)
1746 message_e = QTextEdit()
1747 layout.addWidget(QLabel(_('Message')), 1, 0)
1748 layout.addWidget(message_e, 1, 1)
1749 layout.setRowStretch(2,3)
1751 pubkey_e = QLineEdit()
1753 pubkey = self.wallet.getpubkeys(address)[0]
1754 pubkey_e.setText(pubkey)
1755 layout.addWidget(QLabel(_('Public key')), 2, 0)
1756 layout.addWidget(pubkey_e, 2, 1)
1758 encrypted_e = QTextEdit()
1759 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1760 layout.addWidget(encrypted_e, 3, 1)
1761 layout.setRowStretch(3,1)
1763 hbox = QHBoxLayout()
1764 b = QPushButton(_("Encrypt"))
1765 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1768 b = QPushButton(_("Decrypt"))
1769 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1772 b = QPushButton(_("Close"))
1773 b.clicked.connect(d.accept)
1776 layout.addLayout(hbox, 4, 1)
1780 def question(self, msg):
1781 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1783 def show_message(self, msg):
1784 QMessageBox.information(self, _('Message'), msg, _('OK'))
1786 def password_dialog(self ):
1793 vbox = QVBoxLayout()
1794 msg = _('Please enter your password')
1795 vbox.addWidget(QLabel(msg))
1797 grid = QGridLayout()
1799 grid.addWidget(QLabel(_('Password')), 1, 0)
1800 grid.addWidget(pw, 1, 1)
1801 vbox.addLayout(grid)
1803 vbox.addLayout(ok_cancel_buttons(d))
1806 run_hook('password_dialog', pw, grid, 1)
1807 if not d.exec_(): return
1808 return unicode(pw.text())
1817 def tx_from_text(self, txt):
1818 "json or raw hexadecimal"
1821 tx = Transaction(txt)
1827 tx_dict = json.loads(str(txt))
1828 assert "hex" in tx_dict.keys()
1829 assert "complete" in tx_dict.keys()
1830 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1831 if not tx_dict["complete"]:
1832 assert "input_info" in tx_dict.keys()
1833 input_info = json.loads(tx_dict['input_info'])
1834 tx.add_input_info(input_info)
1839 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1843 def read_tx_from_file(self):
1844 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1848 with open(fileName, "r") as f:
1849 file_content = f.read()
1850 except (ValueError, IOError, os.error), reason:
1851 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1853 return self.tx_from_text(file_content)
1857 def sign_raw_transaction(self, tx, input_info, password):
1858 self.wallet.signrawtransaction(tx, input_info, [], password)
1860 def do_process_from_text(self):
1861 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1864 tx = self.tx_from_text(text)
1866 self.show_transaction(tx)
1868 def do_process_from_file(self):
1869 tx = self.read_tx_from_file()
1871 self.show_transaction(tx)
1873 def do_process_from_txid(self):
1874 from electrum import transaction
1875 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1877 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1879 tx = transaction.Transaction(r)
1881 self.show_transaction(tx)
1883 self.show_message("unknown transaction")
1885 def do_process_from_csvReader(self, csvReader):
1890 for position, row in enumerate(csvReader):
1892 if not is_valid(address):
1893 errors.append((position, address))
1895 amount = Decimal(row[1])
1896 amount = int(100000000*amount)
1897 outputs.append((address, amount))
1898 except (ValueError, IOError, os.error), reason:
1899 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1903 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1904 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1908 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1909 except Exception as e:
1910 self.show_message(str(e))
1913 self.show_transaction(tx)
1915 def do_process_from_csv_file(self):
1916 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1920 with open(fileName, "r") as f:
1921 csvReader = csv.reader(f)
1922 self.do_process_from_csvReader(csvReader)
1923 except (ValueError, IOError, os.error), reason:
1924 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1927 def do_process_from_csv_text(self):
1928 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1929 + _("Format: address, amount. One output per line"), _("Load CSV"))
1932 f = StringIO.StringIO(text)
1933 csvReader = csv.reader(f)
1934 self.do_process_from_csvReader(csvReader)
1939 def do_export_privkeys(self, password):
1940 if not self.wallet.seed:
1941 self.show_message(_("This wallet has no seed"))
1944 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.")))
1947 select_export = _('Select file to export your private keys to')
1948 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1950 with open(fileName, "w+") as csvfile:
1951 transaction = csv.writer(csvfile)
1952 transaction.writerow(["address", "private_key"])
1954 addresses = self.wallet.addresses(True)
1956 for addr in addresses:
1957 pk = "".join(self.wallet.get_private_key(addr, password))
1958 transaction.writerow(["%34s"%addr,pk])
1960 self.show_message(_("Private keys exported."))
1962 except (IOError, os.error), reason:
1963 export_error_label = _("Electrum was unable to produce a private key-export.")
1964 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1966 except Exception as e:
1967 self.show_message(str(e))
1971 def do_import_labels(self):
1972 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1973 if not labelsFile: return
1975 f = open(labelsFile, 'r')
1978 for key, value in json.loads(data).items():
1979 self.wallet.set_label(key, value)
1980 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1981 except (IOError, os.error), reason:
1982 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1985 def do_export_labels(self):
1986 labels = self.wallet.labels
1988 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1990 with open(fileName, 'w+') as f:
1991 json.dump(labels, f)
1992 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1993 except (IOError, os.error), reason:
1994 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1997 def do_export_history(self):
1998 from lite_window import csv_transaction
1999 csv_transaction(self.wallet)
2003 def do_import_privkey(self, password):
2004 if not self.wallet.imported_keys:
2005 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2006 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2007 + _('Are you sure you understand what you are doing?'), 3, 4)
2010 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2013 text = str(text).split()
2018 addr = self.wallet.import_key(key, password)
2019 except Exception as e:
2025 addrlist.append(addr)
2027 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2029 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2030 self.update_receive_tab()
2031 self.update_history_tab()
2034 def settings_dialog(self):
2036 d.setWindowTitle(_('Electrum Settings'))
2038 vbox = QVBoxLayout()
2039 grid = QGridLayout()
2040 grid.setColumnStretch(0,1)
2042 nz_label = QLabel(_('Display zeros') + ':')
2043 grid.addWidget(nz_label, 0, 0)
2044 nz_e = AmountEdit(None,True)
2045 nz_e.setText("%d"% self.num_zeros)
2046 grid.addWidget(nz_e, 0, 1)
2047 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2048 grid.addWidget(HelpButton(msg), 0, 2)
2049 if not self.config.is_modifiable('num_zeros'):
2050 for w in [nz_e, nz_label]: w.setEnabled(False)
2052 lang_label=QLabel(_('Language') + ':')
2053 grid.addWidget(lang_label, 1, 0)
2054 lang_combo = QComboBox()
2055 from electrum.i18n import languages
2056 lang_combo.addItems(languages.values())
2058 index = languages.keys().index(self.config.get("language",''))
2061 lang_combo.setCurrentIndex(index)
2062 grid.addWidget(lang_combo, 1, 1)
2063 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2064 if not self.config.is_modifiable('language'):
2065 for w in [lang_combo, lang_label]: w.setEnabled(False)
2068 fee_label = QLabel(_('Transaction fee') + ':')
2069 grid.addWidget(fee_label, 2, 0)
2070 fee_e = AmountEdit(self.base_unit)
2071 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2072 grid.addWidget(fee_e, 2, 1)
2073 msg = _('Fee per kilobyte of transaction.') + ' ' \
2074 + _('Recommended value') + ': ' + self.format_amount(20000)
2075 grid.addWidget(HelpButton(msg), 2, 2)
2076 if not self.config.is_modifiable('fee_per_kb'):
2077 for w in [fee_e, fee_label]: w.setEnabled(False)
2079 units = ['BTC', 'mBTC']
2080 unit_label = QLabel(_('Base unit') + ':')
2081 grid.addWidget(unit_label, 3, 0)
2082 unit_combo = QComboBox()
2083 unit_combo.addItems(units)
2084 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2085 grid.addWidget(unit_combo, 3, 1)
2086 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2087 + '\n1BTC=1000mBTC.\n' \
2088 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2090 usechange_cb = QCheckBox(_('Use change addresses'))
2091 usechange_cb.setChecked(self.wallet.use_change)
2092 grid.addWidget(usechange_cb, 4, 0)
2093 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2094 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2096 grid.setRowStretch(5,1)
2098 vbox.addLayout(grid)
2099 vbox.addLayout(ok_cancel_buttons(d))
2103 if not d.exec_(): return
2105 fee = unicode(fee_e.text())
2107 fee = self.read_amount(fee)
2109 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2112 self.wallet.set_fee(fee)
2114 nz = unicode(nz_e.text())
2119 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2122 if self.num_zeros != nz:
2124 self.config.set_key('num_zeros', nz, True)
2125 self.update_history_tab()
2126 self.update_receive_tab()
2128 usechange_result = usechange_cb.isChecked()
2129 if self.wallet.use_change != usechange_result:
2130 self.wallet.use_change = usechange_result
2131 self.wallet.storage.put('use_change', self.wallet.use_change)
2133 unit_result = units[unit_combo.currentIndex()]
2134 if self.base_unit() != unit_result:
2135 self.decimal_point = 8 if unit_result == 'BTC' else 5
2136 self.config.set_key('decimal_point', self.decimal_point, True)
2137 self.update_history_tab()
2138 self.update_status()
2140 need_restart = False
2142 lang_request = languages.keys()[lang_combo.currentIndex()]
2143 if lang_request != self.config.get('language'):
2144 self.config.set_key("language", lang_request, True)
2147 run_hook('close_settings_dialog')
2150 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2153 def run_network_dialog(self):
2154 if not self.network:
2156 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2158 def closeEvent(self, event):
2161 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2162 self.save_column_widths()
2163 self.config.set_key("console-history", self.console.history[-50:], True)
2164 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2168 def plugins_dialog(self):
2169 from electrum.plugins import plugins
2172 d.setWindowTitle(_('Electrum Plugins'))
2175 vbox = QVBoxLayout(d)
2178 scroll = QScrollArea()
2179 scroll.setEnabled(True)
2180 scroll.setWidgetResizable(True)
2181 scroll.setMinimumSize(400,250)
2182 vbox.addWidget(scroll)
2186 w.setMinimumHeight(len(plugins)*35)
2188 grid = QGridLayout()
2189 grid.setColumnStretch(0,1)
2192 def do_toggle(cb, p, w):
2195 if w: w.setEnabled(r)
2197 def mk_toggle(cb, p, w):
2198 return lambda: do_toggle(cb,p,w)
2200 for i, p in enumerate(plugins):
2202 cb = QCheckBox(p.fullname())
2203 cb.setDisabled(not p.is_available())
2204 cb.setChecked(p.is_enabled())
2205 grid.addWidget(cb, i, 0)
2206 if p.requires_settings():
2207 w = p.settings_widget(self)
2208 w.setEnabled( p.is_enabled() )
2209 grid.addWidget(w, i, 1)
2212 cb.clicked.connect(mk_toggle(cb,p,w))
2213 grid.addWidget(HelpButton(p.description()), i, 2)
2215 print_msg(_("Error: cannot display plugin"), p)
2216 traceback.print_exc(file=sys.stdout)
2217 grid.setRowStretch(i+1,1)
2219 vbox.addLayout(close_button(d))
2224 def show_account_details(self, k):
2226 d.setWindowTitle(_('Account Details'))
2229 vbox = QVBoxLayout(d)
2230 roots = self.wallet.get_roots(k)
2232 name = self.wallet.get_account_name(k)
2233 label = QLabel('Name: ' + name)
2234 vbox.addWidget(label)
2236 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2237 vbox.addWidget(QLabel('Type: ' + acctype))
2239 label = QLabel('Derivation: ' + k)
2240 vbox.addWidget(label)
2243 # mpk = self.wallet.master_public_keys[root]
2244 # text = QTextEdit()
2245 # text.setReadOnly(True)
2246 # text.setMaximumHeight(120)
2247 # text.setText(repr(mpk))
2248 # vbox.addWidget(text)
2250 vbox.addLayout(close_button(d))