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() )
493 # append fiat balance and price from exchange rate plugin
495 run_hook('get_fiat_status_text', c+u, r)
500 self.tray.setToolTip(text)
501 icon = QIcon(":icons/status_connected.png")
503 text = _("Not connected")
504 icon = QIcon(":icons/status_disconnected.png")
506 self.balance_label.setText(text)
507 self.status_button.setIcon( icon )
510 def update_wallet(self):
512 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
513 self.update_history_tab()
514 self.update_receive_tab()
515 self.update_contacts_tab()
516 self.update_completions()
519 def create_history_tab(self):
520 self.history_list = l = MyTreeWidget(self)
522 for i,width in enumerate(self.column_widths['history']):
523 l.setColumnWidth(i, width)
524 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
525 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
526 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
528 l.customContextMenuRequested.connect(self.create_history_menu)
532 def create_history_menu(self, position):
533 self.history_list.selectedIndexes()
534 item = self.history_list.currentItem()
536 tx_hash = str(item.data(0, Qt.UserRole).toString())
537 if not tx_hash: return
539 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
540 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
541 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
542 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
543 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
546 def show_transaction(self, tx):
547 import transaction_dialog
548 d = transaction_dialog.TxDialog(tx, self)
551 def tx_label_clicked(self, item, column):
552 if column==2 and item.isSelected():
554 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
555 self.history_list.editItem( item, column )
556 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
559 def tx_label_changed(self, item, column):
563 tx_hash = str(item.data(0, Qt.UserRole).toString())
564 tx = self.wallet.transactions.get(tx_hash)
565 text = unicode( item.text(2) )
566 self.wallet.set_label(tx_hash, text)
568 item.setForeground(2, QBrush(QColor('black')))
570 text = self.wallet.get_default_label(tx_hash)
571 item.setText(2, text)
572 item.setForeground(2, QBrush(QColor('gray')))
576 def edit_label(self, is_recv):
577 l = self.receive_list if is_recv else self.contacts_list
578 item = l.currentItem()
579 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
580 l.editItem( item, 1 )
581 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
585 def address_label_clicked(self, item, column, l, column_addr, column_label):
586 if column == column_label and item.isSelected():
587 is_editable = item.data(0, 32).toBool()
590 addr = unicode( item.text(column_addr) )
591 label = unicode( item.text(column_label) )
592 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
593 l.editItem( item, column )
594 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
597 def address_label_changed(self, item, column, l, column_addr, column_label):
598 if column == column_label:
599 addr = unicode( item.text(column_addr) )
600 text = unicode( item.text(column_label) )
601 is_editable = item.data(0, 32).toBool()
605 changed = self.wallet.set_label(addr, text)
607 self.update_history_tab()
608 self.update_completions()
610 self.current_item_changed(item)
612 run_hook('item_changed', item, column)
615 def current_item_changed(self, a):
616 run_hook('current_item_changed', a)
620 def update_history_tab(self):
622 self.history_list.clear()
623 for item in self.wallet.get_tx_history(self.current_account):
624 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
625 time_str = _("unknown")
628 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
630 time_str = _("error")
633 time_str = 'unverified'
634 icon = QIcon(":icons/unconfirmed.png")
637 icon = QIcon(":icons/unconfirmed.png")
639 icon = QIcon(":icons/clock%d.png"%conf)
641 icon = QIcon(":icons/confirmed.png")
643 if value is not None:
644 v_str = self.format_amount(value, True, whitespaces=True)
648 balance_str = self.format_amount(balance, whitespaces=True)
651 label, is_default_label = self.wallet.get_label(tx_hash)
653 label = _('Pruned transaction outputs')
654 is_default_label = False
656 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
657 item.setFont(2, QFont(MONOSPACE_FONT))
658 item.setFont(3, QFont(MONOSPACE_FONT))
659 item.setFont(4, QFont(MONOSPACE_FONT))
661 item.setForeground(3, QBrush(QColor("#BC1E1E")))
663 item.setData(0, Qt.UserRole, tx_hash)
664 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
666 item.setForeground(2, QBrush(QColor('grey')))
668 item.setIcon(0, icon)
669 self.history_list.insertTopLevelItem(0,item)
672 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
673 run_hook('history_tab_update')
676 def create_send_tab(self):
681 grid.setColumnMinimumWidth(3,300)
682 grid.setColumnStretch(5,1)
685 self.payto_e = QLineEdit()
686 grid.addWidget(QLabel(_('Pay to')), 1, 0)
687 grid.addWidget(self.payto_e, 1, 1, 1, 3)
689 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)
691 completer = QCompleter()
692 completer.setCaseSensitivity(False)
693 self.payto_e.setCompleter(completer)
694 completer.setModel(self.completions)
696 self.message_e = QLineEdit()
697 grid.addWidget(QLabel(_('Description')), 2, 0)
698 grid.addWidget(self.message_e, 2, 1, 1, 3)
699 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)
701 self.from_label = QLabel(_('From'))
702 grid.addWidget(self.from_label, 3, 0)
703 self.from_list = QTreeWidget(self)
704 self.from_list.setColumnCount(2)
705 self.from_list.setColumnWidth(0, 350)
706 self.from_list.setColumnWidth(1, 50)
707 self.from_list.setHeaderHidden (True)
708 self.from_list.setMaximumHeight(80)
709 grid.addWidget(self.from_list, 3, 1, 1, 3)
710 self.set_pay_from([])
712 self.amount_e = AmountEdit(self.base_unit)
713 grid.addWidget(QLabel(_('Amount')), 4, 0)
714 grid.addWidget(self.amount_e, 4, 1, 1, 2)
715 grid.addWidget(HelpButton(
716 _('Amount to be sent.') + '\n\n' \
717 + _('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.') \
718 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
720 self.fee_e = AmountEdit(self.base_unit)
721 grid.addWidget(QLabel(_('Fee')), 5, 0)
722 grid.addWidget(self.fee_e, 5, 1, 1, 2)
723 grid.addWidget(HelpButton(
724 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
725 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
726 + _('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 run_hook('exchange_rate_button', grid)
730 self.send_button = EnterButton(_("Send"), self.do_send)
731 grid.addWidget(self.send_button, 6, 1)
733 b = EnterButton(_("Clear"),self.do_clear)
734 grid.addWidget(b, 6, 2)
736 self.payto_sig = QLabel('')
737 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
739 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
740 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
749 def entry_changed( is_fee ):
750 self.funds_error = False
752 if self.amount_e.is_shortcut:
753 self.amount_e.is_shortcut = False
754 sendable = self.get_sendable_balance()
755 # there is only one output because we are completely spending inputs
756 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
757 fee = self.wallet.estimated_fee(inputs, 1)
759 self.amount_e.setText( self.format_amount(amount) )
760 self.fee_e.setText( self.format_amount( fee ) )
763 amount = self.read_amount(str(self.amount_e.text()))
764 fee = self.read_amount(str(self.fee_e.text()))
766 if not is_fee: fee = None
769 # assume that there will be 2 outputs (one for change)
770 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
772 self.fee_e.setText( self.format_amount( fee ) )
775 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
779 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
780 self.funds_error = True
781 text = _( "Not enough funds" )
782 c, u = self.wallet.get_frozen_balance()
783 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
785 self.statusBar().showMessage(text)
786 self.amount_e.setPalette(palette)
787 self.fee_e.setPalette(palette)
789 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
790 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
792 run_hook('create_send_tab', grid)
796 def set_pay_from(self, l):
798 self.from_list.clear()
799 self.from_label.setHidden(len(self.pay_from) == 0)
800 self.from_list.setHidden(len(self.pay_from) == 0)
801 for addr in self.pay_from:
802 c, u = self.wallet.get_addr_balance(addr)
803 balance = self.format_amount(c + u)
804 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
807 def update_completions(self):
809 for addr,label in self.wallet.labels.items():
810 if addr in self.wallet.addressbook:
811 l.append( label + ' <' + addr + '>')
813 run_hook('update_completions', l)
814 self.completions.setStringList(l)
818 return lambda s, *args: s.do_protect(func, args)
823 label = unicode( self.message_e.text() )
824 r = unicode( self.payto_e.text() )
827 # label or alias, with address in brackets
828 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
829 to_address = m.group(2) if m else r
831 if not is_valid(to_address):
832 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
836 amount = self.read_amount(unicode( self.amount_e.text()))
838 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
841 fee = self.read_amount(unicode( self.fee_e.text()))
843 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
846 confirm_amount = self.config.get('confirm_amount', 100000000)
847 if amount >= confirm_amount:
848 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
851 confirm_fee = self.config.get('confirm_fee', 100000)
852 if fee >= confirm_fee:
853 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()}):
856 self.send_tx(to_address, amount, fee, label)
860 def send_tx(self, to_address, amount, fee, label, password):
862 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
863 domain=self.get_payment_sources())
864 except Exception as e:
865 traceback.print_exc(file=sys.stdout)
866 self.show_message(str(e))
869 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
870 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
874 self.wallet.set_label(tx.hash(), label)
877 h = self.wallet.send_tx(tx)
878 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
879 status, msg = self.wallet.receive_tx( h, tx )
881 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
883 self.update_contacts_tab()
885 QMessageBox.warning(self, _('Error'), msg, _('OK'))
888 self.show_transaction(tx)
890 # add recipient to addressbook
891 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
892 self.wallet.addressbook.append(to_address)
897 def set_url(self, url):
898 address, amount, label, message, signature, identity, url = util.parse_url(url)
901 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
902 elif amount: amount = str(Decimal(amount))
905 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
908 self.mini.set_payment_fields(address, amount)
910 if label and self.wallet.labels.get(address) != label:
911 if self.question('Give label "%s" to address %s ?'%(label,address)):
912 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
913 self.wallet.addressbook.append(address)
914 self.wallet.set_label(address, label)
916 run_hook('set_url', url, self.show_message, self.question)
918 self.tabs.setCurrentIndex(1)
919 label = self.wallet.labels.get(address)
920 m_addr = label + ' <'+ address +'>' if label else address
921 self.payto_e.setText(m_addr)
923 self.message_e.setText(message)
925 self.amount_e.setText(amount)
928 self.set_frozen(self.payto_e,True)
929 self.set_frozen(self.amount_e,True)
930 self.set_frozen(self.message_e,True)
931 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
933 self.payto_sig.setVisible(False)
936 self.payto_sig.setVisible(False)
937 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
939 self.set_frozen(e,False)
941 self.set_pay_from([])
944 def set_frozen(self,entry,frozen):
946 entry.setReadOnly(True)
947 entry.setFrame(False)
949 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
950 entry.setPalette(palette)
952 entry.setReadOnly(False)
955 palette.setColor(entry.backgroundRole(), QColor('white'))
956 entry.setPalette(palette)
959 def set_addrs_frozen(self,addrs,freeze):
961 if not addr: continue
962 if addr in self.wallet.frozen_addresses and not freeze:
963 self.wallet.unfreeze(addr)
964 elif addr not in self.wallet.frozen_addresses and freeze:
965 self.wallet.freeze(addr)
966 self.update_receive_tab()
970 def create_list_tab(self, headers):
971 "generic tab creation method"
972 l = MyTreeWidget(self)
973 l.setColumnCount( len(headers) )
974 l.setHeaderLabels( headers )
984 vbox.addWidget(buttons)
989 buttons.setLayout(hbox)
994 def create_receive_tab(self):
995 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
996 l.setContextMenuPolicy(Qt.CustomContextMenu)
997 l.customContextMenuRequested.connect(self.create_receive_menu)
998 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
999 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1000 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1001 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1002 self.receive_list = l
1003 self.receive_buttons_hbox = hbox
1010 def save_column_widths(self):
1011 self.column_widths["receive"] = []
1012 for i in range(self.receive_list.columnCount() -1):
1013 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1015 self.column_widths["history"] = []
1016 for i in range(self.history_list.columnCount() - 1):
1017 self.column_widths["history"].append(self.history_list.columnWidth(i))
1019 self.column_widths["contacts"] = []
1020 for i in range(self.contacts_list.columnCount() - 1):
1021 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1023 self.config.set_key("column_widths_2", self.column_widths, True)
1026 def create_contacts_tab(self):
1027 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1028 l.setContextMenuPolicy(Qt.CustomContextMenu)
1029 l.customContextMenuRequested.connect(self.create_contact_menu)
1030 for i,width in enumerate(self.column_widths['contacts']):
1031 l.setColumnWidth(i, width)
1033 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1034 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1035 self.contacts_list = l
1036 self.contacts_buttons_hbox = hbox
1041 def delete_imported_key(self, addr):
1042 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1043 self.wallet.delete_imported_key(addr)
1044 self.update_receive_tab()
1045 self.update_history_tab()
1047 def edit_account_label(self, k):
1048 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1050 label = unicode(text)
1051 self.wallet.set_label(k,label)
1052 self.update_receive_tab()
1054 def account_set_expanded(self, item, k, b):
1056 self.accounts_expanded[k] = b
1058 def create_account_menu(self, position, k, item):
1060 if item.isExpanded():
1061 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1063 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1064 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1065 if self.wallet.seed_version > 4:
1066 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1067 if self.wallet.account_is_pending(k):
1068 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1069 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1071 def delete_pending_account(self, k):
1072 self.wallet.delete_pending_account(k)
1073 self.update_receive_tab()
1075 def create_receive_menu(self, position):
1076 # fixme: this function apparently has a side effect.
1077 # if it is not called the menu pops up several times
1078 #self.receive_list.selectedIndexes()
1080 selected = self.receive_list.selectedItems()
1081 multi_select = len(selected) > 1
1082 addrs = [unicode(item.text(0)) for item in selected]
1083 if not multi_select:
1084 item = self.receive_list.itemAt(position)
1088 if not is_valid(addr):
1089 k = str(item.data(0,32).toString())
1091 self.create_account_menu(position, k, item)
1093 item.setExpanded(not item.isExpanded())
1097 if not multi_select:
1098 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1099 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1100 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1101 if self.wallet.seed:
1102 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1103 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1104 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1105 if addr in self.wallet.imported_keys:
1106 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1108 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1109 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1110 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1111 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1113 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1114 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1116 run_hook('receive_menu', menu, addrs)
1117 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1120 def get_sendable_balance(self):
1121 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1124 def get_payment_sources(self):
1126 return self.pay_from
1128 return self.wallet.get_account_addresses(self.current_account)
1131 def send_from_addresses(self, addrs):
1132 self.set_pay_from( addrs )
1133 self.tabs.setCurrentIndex(1)
1136 def payto(self, addr):
1138 label = self.wallet.labels.get(addr)
1139 m_addr = label + ' <' + addr + '>' if label else addr
1140 self.tabs.setCurrentIndex(1)
1141 self.payto_e.setText(m_addr)
1142 self.amount_e.setFocus()
1145 def delete_contact(self, x):
1146 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1147 self.wallet.delete_contact(x)
1148 self.wallet.set_label(x, None)
1149 self.update_history_tab()
1150 self.update_contacts_tab()
1151 self.update_completions()
1154 def create_contact_menu(self, position):
1155 item = self.contacts_list.itemAt(position)
1158 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1160 addr = unicode(item.text(0))
1161 label = unicode(item.text(1))
1162 is_editable = item.data(0,32).toBool()
1163 payto_addr = item.data(0,33).toString()
1164 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1165 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1166 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1168 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1169 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1171 run_hook('create_contact_menu', menu, item)
1172 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1175 def update_receive_item(self, item):
1176 item.setFont(0, QFont(MONOSPACE_FONT))
1177 address = str(item.data(0,0).toString())
1178 label = self.wallet.labels.get(address,'')
1179 item.setData(1,0,label)
1180 item.setData(0,32, True) # is editable
1182 run_hook('update_receive_item', address, item)
1184 if not self.wallet.is_mine(address): return
1186 c, u = self.wallet.get_addr_balance(address)
1187 balance = self.format_amount(c + u)
1188 item.setData(2,0,balance)
1190 if address in self.wallet.frozen_addresses:
1191 item.setBackgroundColor(0, QColor('lightblue'))
1194 def update_receive_tab(self):
1195 l = self.receive_list
1198 l.setColumnHidden(2, False)
1199 l.setColumnHidden(3, False)
1200 for i,width in enumerate(self.column_widths['receive']):
1201 l.setColumnWidth(i, width)
1203 if self.current_account is None:
1204 account_items = self.wallet.accounts.items()
1205 elif self.current_account != -1:
1206 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1210 for k, account in account_items:
1211 name = self.wallet.get_account_name(k)
1212 c,u = self.wallet.get_account_balance(k)
1213 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1214 l.addTopLevelItem(account_item)
1215 account_item.setExpanded(self.accounts_expanded.get(k, True))
1216 account_item.setData(0, 32, k)
1218 if not self.wallet.is_seeded(k):
1219 icon = QIcon(":icons/key.png")
1220 account_item.setIcon(0, icon)
1222 for is_change in ([0,1]):
1223 name = _("Receiving") if not is_change else _("Change")
1224 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1225 account_item.addChild(seq_item)
1226 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1228 if not is_change: seq_item.setExpanded(True)
1233 for address in account.get_addresses(is_change):
1234 h = self.wallet.history.get(address,[])
1238 if gap > self.wallet.gap_limit:
1243 c, u = self.wallet.get_addr_balance(address)
1244 num_tx = '*' if h == ['*'] else "%d"%len(h)
1245 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1246 self.update_receive_item(item)
1248 item.setBackgroundColor(1, QColor('red'))
1249 if len(h) > 0 and c == -u:
1251 seq_item.insertChild(0,used_item)
1253 used_item.addChild(item)
1255 seq_item.addChild(item)
1258 for k, addr in self.wallet.get_pending_accounts():
1259 name = self.wallet.labels.get(k,'')
1260 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1261 self.update_receive_item(item)
1262 l.addTopLevelItem(account_item)
1263 account_item.setExpanded(True)
1264 account_item.setData(0, 32, k)
1265 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1266 account_item.addChild(item)
1267 self.update_receive_item(item)
1270 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1271 c,u = self.wallet.get_imported_balance()
1272 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1273 l.addTopLevelItem(account_item)
1274 account_item.setExpanded(True)
1275 for address in self.wallet.imported_keys.keys():
1276 item = QTreeWidgetItem( [ address, '', '', ''] )
1277 self.update_receive_item(item)
1278 account_item.addChild(item)
1281 # we use column 1 because column 0 may be hidden
1282 l.setCurrentItem(l.topLevelItem(0),1)
1285 def update_contacts_tab(self):
1286 l = self.contacts_list
1289 for address in self.wallet.addressbook:
1290 label = self.wallet.labels.get(address,'')
1291 n = self.wallet.get_num_tx(address)
1292 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1293 item.setFont(0, QFont(MONOSPACE_FONT))
1294 # 32 = label can be edited (bool)
1295 item.setData(0,32, True)
1297 item.setData(0,33, address)
1298 l.addTopLevelItem(item)
1300 run_hook('update_contacts_tab', l)
1301 l.setCurrentItem(l.topLevelItem(0))
1305 def create_console_tab(self):
1306 from console import Console
1307 self.console = console = Console()
1311 def update_console(self):
1312 console = self.console
1313 console.history = self.config.get("console-history",[])
1314 console.history_index = len(console.history)
1316 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1317 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1319 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1321 def mkfunc(f, method):
1322 return lambda *args: apply( f, (method, args, self.password_dialog ))
1324 if m[0]=='_' or m in ['network','wallet']: continue
1325 methods[m] = mkfunc(c._run, m)
1327 console.updateNamespace(methods)
1330 def change_account(self,s):
1331 if s == _("All accounts"):
1332 self.current_account = None
1334 accounts = self.wallet.get_account_names()
1335 for k, v in accounts.items():
1337 self.current_account = k
1338 self.update_history_tab()
1339 self.update_status()
1340 self.update_receive_tab()
1342 def create_status_bar(self):
1345 sb.setFixedHeight(35)
1346 qtVersion = qVersion()
1348 self.balance_label = QLabel("")
1349 sb.addWidget(self.balance_label)
1351 from version_getter import UpdateLabel
1352 self.updatelabel = UpdateLabel(self.config, sb)
1354 self.account_selector = QComboBox()
1355 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1356 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1357 sb.addPermanentWidget(self.account_selector)
1359 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1360 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1362 self.lock_icon = QIcon()
1363 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1364 sb.addPermanentWidget( self.password_button )
1366 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1367 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1368 sb.addPermanentWidget( self.seed_button )
1369 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1370 sb.addPermanentWidget( self.status_button )
1372 run_hook('create_status_bar', (sb,))
1374 self.setStatusBar(sb)
1377 def update_lock_icon(self):
1378 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1379 self.password_button.setIcon( icon )
1382 def update_buttons_on_seed(self):
1383 if not self.wallet.is_watching_only():
1384 self.seed_button.show()
1385 self.password_button.show()
1386 self.send_button.setText(_("Send"))
1388 self.password_button.hide()
1389 self.seed_button.hide()
1390 self.send_button.setText(_("Create unsigned transaction"))
1393 def change_password_dialog(self):
1394 from password_dialog import PasswordDialog
1395 d = PasswordDialog(self.wallet, self)
1397 self.update_lock_icon()
1400 def new_contact_dialog(self):
1403 d.setWindowTitle(_("New Contact"))
1404 vbox = QVBoxLayout(d)
1405 vbox.addWidget(QLabel(_('New Contact')+':'))
1407 grid = QGridLayout()
1410 grid.addWidget(QLabel(_("Address")), 1, 0)
1411 grid.addWidget(line1, 1, 1)
1412 grid.addWidget(QLabel(_("Name")), 2, 0)
1413 grid.addWidget(line2, 2, 1)
1415 vbox.addLayout(grid)
1416 vbox.addLayout(ok_cancel_buttons(d))
1421 address = str(line1.text())
1422 label = unicode(line2.text())
1424 if not is_valid(address):
1425 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1428 self.wallet.add_contact(address)
1430 self.wallet.set_label(address, label)
1432 self.update_contacts_tab()
1433 self.update_history_tab()
1434 self.update_completions()
1435 self.tabs.setCurrentIndex(3)
1438 def new_account_dialog(self):
1440 dialog = QDialog(self)
1442 dialog.setWindowTitle(_("New Account"))
1444 vbox = QVBoxLayout()
1445 vbox.addWidget(QLabel(_('Account name')+':'))
1448 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1449 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1454 vbox.addLayout(ok_cancel_buttons(dialog))
1455 dialog.setLayout(vbox)
1459 name = str(e.text())
1462 self.wallet.create_pending_account('1', name)
1463 self.update_receive_tab()
1464 self.tabs.setCurrentIndex(2)
1468 def show_master_public_key_old(self):
1469 dialog = QDialog(self)
1471 dialog.setWindowTitle(_("Master Public Key"))
1473 main_text = QTextEdit()
1474 main_text.setText(self.wallet.get_master_public_key())
1475 main_text.setReadOnly(True)
1476 main_text.setMaximumHeight(170)
1477 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1479 ok_button = QPushButton(_("OK"))
1480 ok_button.setDefault(True)
1481 ok_button.clicked.connect(dialog.accept)
1483 main_layout = QGridLayout()
1484 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1486 main_layout.addWidget(main_text, 1, 0)
1487 main_layout.addWidget(qrw, 1, 1 )
1489 vbox = QVBoxLayout()
1490 vbox.addLayout(main_layout)
1491 vbox.addLayout(close_button(dialog))
1492 dialog.setLayout(vbox)
1496 def show_master_public_key(self):
1498 if self.wallet.seed_version == 4:
1499 self.show_master_public_key_old()
1502 dialog = QDialog(self)
1504 dialog.setWindowTitle(_("Master Public Keys"))
1506 chain_text = QTextEdit()
1507 chain_text.setReadOnly(True)
1508 chain_text.setMaximumHeight(170)
1509 chain_qrw = QRCodeWidget()
1511 mpk_text = QTextEdit()
1512 mpk_text.setReadOnly(True)
1513 mpk_text.setMaximumHeight(170)
1514 mpk_qrw = QRCodeWidget()
1516 main_layout = QGridLayout()
1518 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1519 main_layout.addWidget(mpk_text, 1, 1)
1520 main_layout.addWidget(mpk_qrw, 1, 2)
1522 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1523 main_layout.addWidget(chain_text, 2, 1)
1524 main_layout.addWidget(chain_qrw, 2, 2)
1527 c, K, cK = self.wallet.master_public_keys[str(key)]
1528 chain_text.setText(c)
1529 chain_qrw.set_addr(c)
1530 chain_qrw.update_qr()
1535 key_selector = QComboBox()
1536 keys = sorted(self.wallet.master_public_keys.keys())
1537 key_selector.addItems(keys)
1539 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1540 main_layout.addWidget(key_selector, 0, 1)
1541 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1545 vbox = QVBoxLayout()
1546 vbox.addLayout(main_layout)
1547 vbox.addLayout(close_button(dialog))
1549 dialog.setLayout(vbox)
1554 def show_seed_dialog(self, password):
1555 if self.wallet.is_watching_only():
1556 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1559 if self.wallet.seed:
1561 mnemonic = self.wallet.get_mnemonic(password)
1563 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1565 from seed_dialog import SeedDialog
1566 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1570 for k in self.wallet.master_private_keys.keys():
1571 pk = self.wallet.get_master_private_key(k, password)
1573 from seed_dialog import PrivateKeysDialog
1574 d = PrivateKeysDialog(self,l)
1581 def show_qrcode(self, data, title = _("QR code")):
1585 d.setWindowTitle(title)
1586 d.setMinimumSize(270, 300)
1587 vbox = QVBoxLayout()
1588 qrw = QRCodeWidget(data)
1589 vbox.addWidget(qrw, 1)
1590 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1591 hbox = QHBoxLayout()
1594 filename = os.path.join(self.config.path, "qrcode.bmp")
1597 bmp.save_qrcode(qrw.qr, filename)
1598 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1600 def copy_to_clipboard():
1601 bmp.save_qrcode(qrw.qr, filename)
1602 self.app.clipboard().setImage(QImage(filename))
1603 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1605 b = QPushButton(_("Copy"))
1607 b.clicked.connect(copy_to_clipboard)
1609 b = QPushButton(_("Save"))
1611 b.clicked.connect(print_qr)
1613 b = QPushButton(_("Close"))
1615 b.clicked.connect(d.accept)
1618 vbox.addLayout(hbox)
1623 def do_protect(self, func, args):
1624 if self.wallet.use_encryption:
1625 password = self.password_dialog()
1631 if args != (False,):
1632 args = (self,) + args + (password,)
1634 args = (self,password)
1639 def show_private_key(self, address, password):
1640 if not address: return
1642 pk_list = self.wallet.get_private_key(address, password)
1643 except Exception as e:
1644 self.show_message(str(e))
1648 d.setMinimumSize(600, 200)
1650 vbox = QVBoxLayout()
1651 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1652 vbox.addWidget( QLabel(_("Private key") + ':'))
1654 keys.setReadOnly(True)
1655 keys.setText('\n'.join(pk_list))
1656 vbox.addWidget(keys)
1657 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1658 vbox.addLayout(close_button(d))
1664 def do_sign(self, address, message, signature, password):
1665 message = unicode(message.toPlainText())
1666 message = message.encode('utf-8')
1668 sig = self.wallet.sign_message(str(address.text()), message, password)
1669 signature.setText(sig)
1670 except Exception as e:
1671 self.show_message(str(e))
1673 def do_verify(self, address, message, signature):
1674 message = unicode(message.toPlainText())
1675 message = message.encode('utf-8')
1676 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1677 self.show_message(_("Signature verified"))
1679 self.show_message(_("Error: wrong signature"))
1682 def sign_verify_message(self, address=''):
1685 d.setWindowTitle(_('Sign/verify Message'))
1686 d.setMinimumSize(410, 290)
1688 layout = QGridLayout(d)
1690 message_e = QTextEdit()
1691 layout.addWidget(QLabel(_('Message')), 1, 0)
1692 layout.addWidget(message_e, 1, 1)
1693 layout.setRowStretch(2,3)
1695 address_e = QLineEdit()
1696 address_e.setText(address)
1697 layout.addWidget(QLabel(_('Address')), 2, 0)
1698 layout.addWidget(address_e, 2, 1)
1700 signature_e = QTextEdit()
1701 layout.addWidget(QLabel(_('Signature')), 3, 0)
1702 layout.addWidget(signature_e, 3, 1)
1703 layout.setRowStretch(3,1)
1705 hbox = QHBoxLayout()
1707 b = QPushButton(_("Sign"))
1708 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1711 b = QPushButton(_("Verify"))
1712 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1715 b = QPushButton(_("Close"))
1716 b.clicked.connect(d.accept)
1718 layout.addLayout(hbox, 4, 1)
1723 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1725 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1726 message_e.setText(decrypted)
1727 except Exception as e:
1728 self.show_message(str(e))
1731 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1732 message = unicode(message_e.toPlainText())
1733 message = message.encode('utf-8')
1735 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1736 encrypted_e.setText(encrypted)
1737 except Exception as e:
1738 self.show_message(str(e))
1742 def encrypt_message(self, address = ''):
1745 d.setWindowTitle(_('Encrypt/decrypt Message'))
1746 d.setMinimumSize(610, 490)
1748 layout = QGridLayout(d)
1750 message_e = QTextEdit()
1751 layout.addWidget(QLabel(_('Message')), 1, 0)
1752 layout.addWidget(message_e, 1, 1)
1753 layout.setRowStretch(2,3)
1755 pubkey_e = QLineEdit()
1757 pubkey = self.wallet.getpubkeys(address)[0]
1758 pubkey_e.setText(pubkey)
1759 layout.addWidget(QLabel(_('Public key')), 2, 0)
1760 layout.addWidget(pubkey_e, 2, 1)
1762 encrypted_e = QTextEdit()
1763 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1764 layout.addWidget(encrypted_e, 3, 1)
1765 layout.setRowStretch(3,1)
1767 hbox = QHBoxLayout()
1768 b = QPushButton(_("Encrypt"))
1769 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1772 b = QPushButton(_("Decrypt"))
1773 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1776 b = QPushButton(_("Close"))
1777 b.clicked.connect(d.accept)
1780 layout.addLayout(hbox, 4, 1)
1784 def question(self, msg):
1785 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1787 def show_message(self, msg):
1788 QMessageBox.information(self, _('Message'), msg, _('OK'))
1790 def password_dialog(self ):
1793 d.setWindowTitle(_("Enter Password"))
1798 vbox = QVBoxLayout()
1799 msg = _('Please enter your password')
1800 vbox.addWidget(QLabel(msg))
1802 grid = QGridLayout()
1804 grid.addWidget(QLabel(_('Password')), 1, 0)
1805 grid.addWidget(pw, 1, 1)
1806 vbox.addLayout(grid)
1808 vbox.addLayout(ok_cancel_buttons(d))
1811 run_hook('password_dialog', pw, grid, 1)
1812 if not d.exec_(): return
1813 return unicode(pw.text())
1822 def tx_from_text(self, txt):
1823 "json or raw hexadecimal"
1826 tx = Transaction(txt)
1832 tx_dict = json.loads(str(txt))
1833 assert "hex" in tx_dict.keys()
1834 assert "complete" in tx_dict.keys()
1835 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1836 if not tx_dict["complete"]:
1837 assert "input_info" in tx_dict.keys()
1838 input_info = json.loads(tx_dict['input_info'])
1839 tx.add_input_info(input_info)
1844 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1848 def read_tx_from_file(self):
1849 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1853 with open(fileName, "r") as f:
1854 file_content = f.read()
1855 except (ValueError, IOError, os.error), reason:
1856 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1858 return self.tx_from_text(file_content)
1862 def sign_raw_transaction(self, tx, input_info, password):
1863 self.wallet.signrawtransaction(tx, input_info, [], password)
1865 def do_process_from_text(self):
1866 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1869 tx = self.tx_from_text(text)
1871 self.show_transaction(tx)
1873 def do_process_from_file(self):
1874 tx = self.read_tx_from_file()
1876 self.show_transaction(tx)
1878 def do_process_from_txid(self):
1879 from electrum import transaction
1880 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1882 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1884 tx = transaction.Transaction(r)
1886 self.show_transaction(tx)
1888 self.show_message("unknown transaction")
1890 def do_process_from_csvReader(self, csvReader):
1895 for position, row in enumerate(csvReader):
1897 if not is_valid(address):
1898 errors.append((position, address))
1900 amount = Decimal(row[1])
1901 amount = int(100000000*amount)
1902 outputs.append((address, amount))
1903 except (ValueError, IOError, os.error), reason:
1904 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1908 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1909 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1913 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1914 except Exception as e:
1915 self.show_message(str(e))
1918 self.show_transaction(tx)
1920 def do_process_from_csv_file(self):
1921 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1925 with open(fileName, "r") as f:
1926 csvReader = csv.reader(f)
1927 self.do_process_from_csvReader(csvReader)
1928 except (ValueError, IOError, os.error), reason:
1929 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1932 def do_process_from_csv_text(self):
1933 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1934 + _("Format: address, amount. One output per line"), _("Load CSV"))
1937 f = StringIO.StringIO(text)
1938 csvReader = csv.reader(f)
1939 self.do_process_from_csvReader(csvReader)
1944 def do_export_privkeys(self, password):
1945 if not self.wallet.seed:
1946 self.show_message(_("This wallet has no seed"))
1949 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.")))
1952 select_export = _('Select file to export your private keys to')
1953 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1955 with open(fileName, "w+") as csvfile:
1956 transaction = csv.writer(csvfile)
1957 transaction.writerow(["address", "private_key"])
1959 addresses = self.wallet.addresses(True)
1961 for addr in addresses:
1962 pk = "".join(self.wallet.get_private_key(addr, password))
1963 transaction.writerow(["%34s"%addr,pk])
1965 self.show_message(_("Private keys exported."))
1967 except (IOError, os.error), reason:
1968 export_error_label = _("Electrum was unable to produce a private key-export.")
1969 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1971 except Exception as e:
1972 self.show_message(str(e))
1976 def do_import_labels(self):
1977 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1978 if not labelsFile: return
1980 f = open(labelsFile, 'r')
1983 for key, value in json.loads(data).items():
1984 self.wallet.set_label(key, value)
1985 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1986 except (IOError, os.error), reason:
1987 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1990 def do_export_labels(self):
1991 labels = self.wallet.labels
1993 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1995 with open(fileName, 'w+') as f:
1996 json.dump(labels, f)
1997 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1998 except (IOError, os.error), reason:
1999 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2002 def do_export_history(self):
2003 from lite_window import csv_transaction
2004 csv_transaction(self.wallet)
2008 def do_import_privkey(self, password):
2009 if not self.wallet.imported_keys:
2010 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2011 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2012 + _('Are you sure you understand what you are doing?'), 3, 4)
2015 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2018 text = str(text).split()
2023 addr = self.wallet.import_key(key, password)
2024 except Exception as e:
2030 addrlist.append(addr)
2032 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2034 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2035 self.update_receive_tab()
2036 self.update_history_tab()
2039 def settings_dialog(self):
2041 d.setWindowTitle(_('Electrum Settings'))
2043 vbox = QVBoxLayout()
2044 grid = QGridLayout()
2045 grid.setColumnStretch(0,1)
2047 nz_label = QLabel(_('Display zeros') + ':')
2048 grid.addWidget(nz_label, 0, 0)
2049 nz_e = AmountEdit(None,True)
2050 nz_e.setText("%d"% self.num_zeros)
2051 grid.addWidget(nz_e, 0, 1)
2052 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2053 grid.addWidget(HelpButton(msg), 0, 2)
2054 if not self.config.is_modifiable('num_zeros'):
2055 for w in [nz_e, nz_label]: w.setEnabled(False)
2057 lang_label=QLabel(_('Language') + ':')
2058 grid.addWidget(lang_label, 1, 0)
2059 lang_combo = QComboBox()
2060 from electrum.i18n import languages
2061 lang_combo.addItems(languages.values())
2063 index = languages.keys().index(self.config.get("language",''))
2066 lang_combo.setCurrentIndex(index)
2067 grid.addWidget(lang_combo, 1, 1)
2068 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2069 if not self.config.is_modifiable('language'):
2070 for w in [lang_combo, lang_label]: w.setEnabled(False)
2073 fee_label = QLabel(_('Transaction fee') + ':')
2074 grid.addWidget(fee_label, 2, 0)
2075 fee_e = AmountEdit(self.base_unit)
2076 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2077 grid.addWidget(fee_e, 2, 1)
2078 msg = _('Fee per kilobyte of transaction.') + ' ' \
2079 + _('Recommended value') + ': ' + self.format_amount(20000)
2080 grid.addWidget(HelpButton(msg), 2, 2)
2081 if not self.config.is_modifiable('fee_per_kb'):
2082 for w in [fee_e, fee_label]: w.setEnabled(False)
2084 units = ['BTC', 'mBTC']
2085 unit_label = QLabel(_('Base unit') + ':')
2086 grid.addWidget(unit_label, 3, 0)
2087 unit_combo = QComboBox()
2088 unit_combo.addItems(units)
2089 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2090 grid.addWidget(unit_combo, 3, 1)
2091 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2092 + '\n1BTC=1000mBTC.\n' \
2093 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2095 usechange_cb = QCheckBox(_('Use change addresses'))
2096 usechange_cb.setChecked(self.wallet.use_change)
2097 grid.addWidget(usechange_cb, 4, 0)
2098 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2099 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2101 grid.setRowStretch(5,1)
2103 vbox.addLayout(grid)
2104 vbox.addLayout(ok_cancel_buttons(d))
2108 if not d.exec_(): return
2110 fee = unicode(fee_e.text())
2112 fee = self.read_amount(fee)
2114 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2117 self.wallet.set_fee(fee)
2119 nz = unicode(nz_e.text())
2124 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2127 if self.num_zeros != nz:
2129 self.config.set_key('num_zeros', nz, True)
2130 self.update_history_tab()
2131 self.update_receive_tab()
2133 usechange_result = usechange_cb.isChecked()
2134 if self.wallet.use_change != usechange_result:
2135 self.wallet.use_change = usechange_result
2136 self.wallet.storage.put('use_change', self.wallet.use_change)
2138 unit_result = units[unit_combo.currentIndex()]
2139 if self.base_unit() != unit_result:
2140 self.decimal_point = 8 if unit_result == 'BTC' else 5
2141 self.config.set_key('decimal_point', self.decimal_point, True)
2142 self.update_history_tab()
2143 self.update_status()
2145 need_restart = False
2147 lang_request = languages.keys()[lang_combo.currentIndex()]
2148 if lang_request != self.config.get('language'):
2149 self.config.set_key("language", lang_request, True)
2152 run_hook('close_settings_dialog')
2155 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2158 def run_network_dialog(self):
2159 if not self.network:
2161 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2163 def closeEvent(self, event):
2166 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2167 self.save_column_widths()
2168 self.config.set_key("console-history", self.console.history[-50:], True)
2169 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2173 def plugins_dialog(self):
2174 from electrum.plugins import plugins
2177 d.setWindowTitle(_('Electrum Plugins'))
2180 vbox = QVBoxLayout(d)
2183 scroll = QScrollArea()
2184 scroll.setEnabled(True)
2185 scroll.setWidgetResizable(True)
2186 scroll.setMinimumSize(400,250)
2187 vbox.addWidget(scroll)
2191 w.setMinimumHeight(len(plugins)*35)
2193 grid = QGridLayout()
2194 grid.setColumnStretch(0,1)
2197 def do_toggle(cb, p, w):
2200 if w: w.setEnabled(r)
2202 def mk_toggle(cb, p, w):
2203 return lambda: do_toggle(cb,p,w)
2205 for i, p in enumerate(plugins):
2207 cb = QCheckBox(p.fullname())
2208 cb.setDisabled(not p.is_available())
2209 cb.setChecked(p.is_enabled())
2210 grid.addWidget(cb, i, 0)
2211 if p.requires_settings():
2212 w = p.settings_widget(self)
2213 w.setEnabled( p.is_enabled() )
2214 grid.addWidget(w, i, 1)
2217 cb.clicked.connect(mk_toggle(cb,p,w))
2218 grid.addWidget(HelpButton(p.description()), i, 2)
2220 print_msg(_("Error: cannot display plugin"), p)
2221 traceback.print_exc(file=sys.stdout)
2222 grid.setRowStretch(i+1,1)
2224 vbox.addLayout(close_button(d))
2229 def show_account_details(self, k):
2231 d.setWindowTitle(_('Account Details'))
2234 vbox = QVBoxLayout(d)
2235 roots = self.wallet.get_roots(k)
2237 name = self.wallet.get_account_name(k)
2238 label = QLabel('Name: ' + name)
2239 vbox.addWidget(label)
2241 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2242 vbox.addWidget(QLabel('Type: ' + acctype))
2244 label = QLabel('Derivation: ' + k)
2245 vbox.addWidget(label)
2248 # mpk = self.wallet.master_public_keys[root]
2249 # text = QTextEdit()
2250 # text.setReadOnly(True)
2251 # text.setMaximumHeight(120)
2252 # text.setText(repr(mpk))
2253 # vbox.addWidget(text)
2255 vbox.addLayout(close_button(d))