3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
33 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
34 from electrum.plugins import run_hook
38 from electrum.wallet import format_satoshis
39 from electrum import Transaction
40 from electrum import mnemonic
41 from electrum import util, bitcoin, commands, Interface, Wallet
42 from electrum import SimpleConfig, Wallet, WalletStorage
45 from electrum import bmp, pyqrnative
47 from amountedit import AmountEdit
48 from network_dialog import NetworkDialog
49 from qrcodewidget import QRCodeWidget
51 from decimal import Decimal
59 if platform.system() == 'Windows':
60 MONOSPACE_FONT = 'Lucida Console'
61 elif platform.system() == 'Darwin':
62 MONOSPACE_FONT = 'Monaco'
64 MONOSPACE_FONT = 'monospace'
66 from electrum import ELECTRUM_VERSION
76 class StatusBarButton(QPushButton):
77 def __init__(self, icon, tooltip, func):
78 QPushButton.__init__(self, icon, '')
79 self.setToolTip(tooltip)
81 self.setMaximumWidth(25)
82 self.clicked.connect(func)
84 self.setIconSize(QSize(25,25))
86 def keyPressEvent(self, e):
87 if e.key() == QtCore.Qt.Key_Return:
99 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
101 class ElectrumWindow(QMainWindow):
102 def build_menu(self):
104 m.addAction(_("Show/Hide"), self.show_or_hide)
106 m.addAction(_("Exit Electrum"), self.close)
107 self.tray.setContextMenu(m)
109 def show_or_hide(self):
110 self.tray_activated(QSystemTrayIcon.DoubleClick)
112 def tray_activated(self, reason):
113 if reason == QSystemTrayIcon.DoubleClick:
114 if self.isMinimized() or self.isHidden():
119 def __init__(self, config, network):
120 QMainWindow.__init__(self)
123 self.network = network
125 self._close_electrum = False
128 if sys.platform == 'darwin':
129 self.icon = QIcon(":icons/electrum_dark_icon.png")
130 #self.icon = QIcon(":icons/lock.png")
132 self.icon = QIcon(':icons/electrum_light_icon.png')
134 self.tray = QSystemTrayIcon(self.icon, self)
135 self.tray.setToolTip('Electrum')
136 self.tray.activated.connect(self.tray_activated)
140 self.create_status_bar()
142 self.need_update = threading.Event()
144 self.decimal_point = config.get('decimal_point', 8)
145 self.num_zeros = int(config.get('num_zeros',0))
147 set_language(config.get('language'))
149 self.funds_error = False
150 self.completions = QStringListModel()
152 self.tabs = tabs = QTabWidget(self)
153 self.column_widths = self.config.get("column_widths_2", default_column_widths )
154 tabs.addTab(self.create_history_tab(), _('History') )
155 tabs.addTab(self.create_send_tab(), _('Send') )
156 tabs.addTab(self.create_receive_tab(), _('Receive') )
157 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
158 tabs.addTab(self.create_console_tab(), _('Console') )
159 tabs.setMinimumSize(600, 400)
160 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
161 self.setCentralWidget(tabs)
163 g = self.config.get("winpos-qt",[100, 100, 840, 400])
164 self.setGeometry(g[0], g[1], g[2], g[3])
166 self.setWindowIcon(QIcon(":icons/electrum.png"))
169 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
170 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
171 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
172 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
173 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
175 for i in range(tabs.count()):
176 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
178 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
179 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
180 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
182 self.history_list.setFocus(True)
186 self.network.register_callback('updated', lambda: self.need_update.set())
187 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
188 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
189 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
190 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
192 # set initial message
193 self.console.showMessage(self.network.banner)
200 self.config.set_key('lite_mode', False, True)
205 self.config.set_key('lite_mode', True, True)
212 if not self.check_qt_version():
213 if self.config.get('lite_mode') is True:
214 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
215 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
216 self.config.set_key('lite_mode', False, True)
222 actuator = lite_window.MiniActuator(self)
224 # Should probably not modify the current path but instead
225 # change the behaviour of rsrc(...)
226 old_path = QDir.currentPath()
227 actuator.load_theme()
229 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
231 driver = lite_window.MiniDriver(self, self.mini)
233 # Reset path back to original value now that loading the GUI
235 QDir.setCurrent(old_path)
237 if self.config.get('lite_mode') is True:
243 def check_qt_version(self):
244 qtVersion = qVersion()
245 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
248 def update_account_selector(self):
250 accounts = self.wallet.get_account_names()
251 self.account_selector.clear()
252 if len(accounts) > 1:
253 self.account_selector.addItems([_("All accounts")] + accounts.values())
254 self.account_selector.setCurrentIndex(0)
255 self.account_selector.show()
257 self.account_selector.hide()
260 def load_wallet(self, wallet):
263 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
264 self.current_account = self.wallet.storage.get("current_account", None)
266 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
267 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
268 self.setWindowTitle( title )
270 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
271 self.notify_transactions()
272 self.update_account_selector()
273 self.new_account.setEnabled(self.wallet.seed_version>4)
274 self.update_lock_icon()
275 self.update_buttons_on_seed()
276 self.update_console()
278 run_hook('load_wallet', wallet)
281 def open_wallet(self):
282 wallet_folder = self.wallet.storage.path
283 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
287 storage = WalletStorage({'wallet_path': filename})
288 if not storage.file_exists:
289 self.show_message("file not found "+ filename)
292 self.wallet.stop_threads()
295 wallet = Wallet(storage)
296 wallet.start_threads(self.network)
298 self.load_wallet(wallet)
302 def backup_wallet(self):
304 path = self.wallet.storage.path
305 wallet_folder = os.path.dirname(path)
306 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
310 new_path = os.path.join(wallet_folder, filename)
313 shutil.copy2(path, new_path)
314 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
315 except (IOError, os.error), reason:
316 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
319 def new_wallet(self):
322 wallet_folder = os.path.dirname(self.wallet.storage.path)
323 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
326 filename = os.path.join(wallet_folder, filename)
328 storage = WalletStorage({'wallet_path': filename})
329 if storage.file_exists:
330 QMessageBox.critical(None, "Error", _("File exists"))
333 wizard = installwizard.InstallWizard(self.config, self.network, storage)
334 wallet = wizard.run()
336 self.load_wallet(wallet)
340 def init_menubar(self):
343 file_menu = menubar.addMenu(_("&File"))
344 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
345 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
346 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
347 file_menu.addAction(_("&Quit"), self.close)
349 wallet_menu = menubar.addMenu(_("&Wallet"))
350 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
351 self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
353 wallet_menu.addSeparator()
355 wallet_menu.addAction(_("&Password"), self.change_password_dialog)
356 wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
357 wallet_menu.addAction(_("&Master Public Key"), self.show_master_public_key)
359 wallet_menu.addSeparator()
360 labels_menu = wallet_menu.addMenu(_("&Labels"))
361 labels_menu.addAction(_("&Import"), self.do_import_labels)
362 labels_menu.addAction(_("&Export"), self.do_export_labels)
364 keys_menu = wallet_menu.addMenu(_("&Private keys"))
365 keys_menu.addAction(_("&Import"), self.do_import_privkey)
366 keys_menu.addAction(_("&Export"), self.do_export_privkeys)
368 wallet_menu.addAction(_("&Export History"), self.do_export_history)
370 tools_menu = menubar.addMenu(_("&Tools"))
372 # Settings / Preferences are all reserved keywords in OSX using this as work around
373 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
374 tools_menu.addAction(_("&Network"), self.run_network_dialog)
375 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
376 tools_menu.addSeparator()
377 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
378 tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
379 tools_menu.addSeparator()
381 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
382 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
383 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
385 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
386 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
387 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
388 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
390 help_menu = menubar.addMenu(_("&Help"))
391 help_menu.addAction(_("&About"), self.show_about)
392 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
393 help_menu.addSeparator()
394 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
395 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
397 self.setMenuBar(menubar)
399 def show_about(self):
400 QMessageBox.about(self, "Electrum",
401 _("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."))
403 def show_report_bug(self):
404 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
405 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
408 def notify_transactions(self):
409 if not self.network or not self.network.is_connected():
412 print_error("Notifying GUI")
413 if len(self.network.interface.pending_transactions_for_notifications) > 0:
414 # Combine the transactions if there are more then three
415 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
418 for tx in self.network.interface.pending_transactions_for_notifications:
419 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
423 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
424 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
426 self.network.interface.pending_transactions_for_notifications = []
428 for tx in self.network.interface.pending_transactions_for_notifications:
430 self.network.interface.pending_transactions_for_notifications.remove(tx)
431 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
433 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
435 def notify(self, message):
436 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
440 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
441 def getOpenFileName(self, title, filter = ""):
442 directory = self.config.get('io_dir', os.path.expanduser('~'))
443 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
444 if fileName and directory != os.path.dirname(fileName):
445 self.config.set_key('io_dir', os.path.dirname(fileName), True)
448 def getSaveFileName(self, title, filename, filter = ""):
449 directory = self.config.get('io_dir', os.path.expanduser('~'))
450 path = os.path.join( directory, filename )
451 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
452 if fileName and directory != os.path.dirname(fileName):
453 self.config.set_key('io_dir', os.path.dirname(fileName), True)
457 QMainWindow.close(self)
458 run_hook('close_main_window')
460 def connect_slots(self, sender):
461 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
462 self.previous_payto_e=''
464 def timer_actions(self):
465 if self.need_update.is_set():
467 self.need_update.clear()
468 run_hook('timer_actions')
470 def format_amount(self, x, is_diff=False, whitespaces=False):
471 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
473 def read_amount(self, x):
474 if x in['.', '']: return None
475 p = pow(10, self.decimal_point)
476 return int( p * Decimal(x) )
479 assert self.decimal_point in [5,8]
480 return "BTC" if self.decimal_point == 8 else "mBTC"
483 def update_status(self):
484 if self.network is None or not self.network.is_running():
486 icon = QIcon(":icons/status_disconnected.png")
488 elif self.network.is_connected():
489 if not self.wallet.up_to_date:
490 text = _("Synchronizing...")
491 icon = QIcon(":icons/status_waiting.png")
492 elif self.network.server_lag > 1:
493 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
494 icon = QIcon(":icons/status_lagging.png")
496 c, u = self.wallet.get_account_balance(self.current_account)
497 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
498 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
501 run_hook('set_quote_text', c+u, r)
504 text += " (%s)"%quote
506 self.tray.setToolTip(text)
507 icon = QIcon(":icons/status_connected.png")
509 text = _("Not connected")
510 icon = QIcon(":icons/status_disconnected.png")
512 self.balance_label.setText(text)
513 self.status_button.setIcon( icon )
516 def update_wallet(self):
518 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
519 self.update_history_tab()
520 self.update_receive_tab()
521 self.update_contacts_tab()
522 self.update_completions()
525 def create_history_tab(self):
526 self.history_list = l = MyTreeWidget(self)
528 for i,width in enumerate(self.column_widths['history']):
529 l.setColumnWidth(i, width)
530 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
531 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
532 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
534 l.customContextMenuRequested.connect(self.create_history_menu)
538 def create_history_menu(self, position):
539 self.history_list.selectedIndexes()
540 item = self.history_list.currentItem()
542 tx_hash = str(item.data(0, Qt.UserRole).toString())
543 if not tx_hash: return
545 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
546 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
547 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
548 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
549 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
552 def show_transaction(self, tx):
553 import transaction_dialog
554 d = transaction_dialog.TxDialog(tx, self)
557 def tx_label_clicked(self, item, column):
558 if column==2 and item.isSelected():
560 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
561 self.history_list.editItem( item, column )
562 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
565 def tx_label_changed(self, item, column):
569 tx_hash = str(item.data(0, Qt.UserRole).toString())
570 tx = self.wallet.transactions.get(tx_hash)
571 text = unicode( item.text(2) )
572 self.wallet.set_label(tx_hash, text)
574 item.setForeground(2, QBrush(QColor('black')))
576 text = self.wallet.get_default_label(tx_hash)
577 item.setText(2, text)
578 item.setForeground(2, QBrush(QColor('gray')))
582 def edit_label(self, is_recv):
583 l = self.receive_list if is_recv else self.contacts_list
584 item = l.currentItem()
585 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
586 l.editItem( item, 1 )
587 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
591 def address_label_clicked(self, item, column, l, column_addr, column_label):
592 if column == column_label and item.isSelected():
593 is_editable = item.data(0, 32).toBool()
596 addr = unicode( item.text(column_addr) )
597 label = unicode( item.text(column_label) )
598 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
599 l.editItem( item, column )
600 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
603 def address_label_changed(self, item, column, l, column_addr, column_label):
604 if column == column_label:
605 addr = unicode( item.text(column_addr) )
606 text = unicode( item.text(column_label) )
607 is_editable = item.data(0, 32).toBool()
611 changed = self.wallet.set_label(addr, text)
613 self.update_history_tab()
614 self.update_completions()
616 self.current_item_changed(item)
618 run_hook('item_changed', item, column)
621 def current_item_changed(self, a):
622 run_hook('current_item_changed', a)
626 def update_history_tab(self):
628 self.history_list.clear()
629 for item in self.wallet.get_tx_history(self.current_account):
630 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
631 time_str = _("unknown")
634 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
636 time_str = _("error")
639 time_str = 'unverified'
640 icon = QIcon(":icons/unconfirmed.png")
643 icon = QIcon(":icons/unconfirmed.png")
645 icon = QIcon(":icons/clock%d.png"%conf)
647 icon = QIcon(":icons/confirmed.png")
649 if value is not None:
650 v_str = self.format_amount(value, True, whitespaces=True)
654 balance_str = self.format_amount(balance, whitespaces=True)
657 label, is_default_label = self.wallet.get_label(tx_hash)
659 label = _('Pruned transaction outputs')
660 is_default_label = False
662 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
663 item.setFont(2, QFont(MONOSPACE_FONT))
664 item.setFont(3, QFont(MONOSPACE_FONT))
665 item.setFont(4, QFont(MONOSPACE_FONT))
667 item.setForeground(3, QBrush(QColor("#BC1E1E")))
669 item.setData(0, Qt.UserRole, tx_hash)
670 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
672 item.setForeground(2, QBrush(QColor('grey')))
674 item.setIcon(0, icon)
675 self.history_list.insertTopLevelItem(0,item)
678 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
679 run_hook('history_tab_update')
682 def create_send_tab(self):
687 grid.setColumnMinimumWidth(3,300)
688 grid.setColumnStretch(5,1)
691 self.payto_e = QLineEdit()
692 grid.addWidget(QLabel(_('Pay to')), 1, 0)
693 grid.addWidget(self.payto_e, 1, 1, 1, 3)
695 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)
697 completer = QCompleter()
698 completer.setCaseSensitivity(False)
699 self.payto_e.setCompleter(completer)
700 completer.setModel(self.completions)
702 self.message_e = QLineEdit()
703 grid.addWidget(QLabel(_('Description')), 2, 0)
704 grid.addWidget(self.message_e, 2, 1, 1, 3)
705 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)
707 self.from_label = QLabel(_('From'))
708 grid.addWidget(self.from_label, 3, 0)
709 self.from_list = QTreeWidget(self)
710 self.from_list.setColumnCount(2)
711 self.from_list.setColumnWidth(0, 350)
712 self.from_list.setColumnWidth(1, 50)
713 self.from_list.setHeaderHidden (True)
714 self.from_list.setMaximumHeight(80)
715 grid.addWidget(self.from_list, 3, 1, 1, 3)
716 self.set_pay_from([])
718 self.amount_e = AmountEdit(self.base_unit)
719 grid.addWidget(QLabel(_('Amount')), 4, 0)
720 grid.addWidget(self.amount_e, 4, 1, 1, 2)
721 grid.addWidget(HelpButton(
722 _('Amount to be sent.') + '\n\n' \
723 + _('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.') \
724 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
726 self.fee_e = AmountEdit(self.base_unit)
727 grid.addWidget(QLabel(_('Fee')), 5, 0)
728 grid.addWidget(self.fee_e, 5, 1, 1, 2)
729 grid.addWidget(HelpButton(
730 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
731 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
732 + _('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)
735 self.send_button = EnterButton(_("Send"), self.do_send)
736 grid.addWidget(self.send_button, 6, 1)
738 b = EnterButton(_("Clear"),self.do_clear)
739 grid.addWidget(b, 6, 2)
741 self.payto_sig = QLabel('')
742 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
744 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
745 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
754 def entry_changed( is_fee ):
755 self.funds_error = False
757 if self.amount_e.is_shortcut:
758 self.amount_e.is_shortcut = False
759 sendable = self.get_sendable_balance()
760 # there is only one output because we are completely spending inputs
761 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
762 fee = self.wallet.estimated_fee(inputs, 1)
764 self.amount_e.setText( self.format_amount(amount) )
765 self.fee_e.setText( self.format_amount( fee ) )
768 amount = self.read_amount(str(self.amount_e.text()))
769 fee = self.read_amount(str(self.fee_e.text()))
771 if not is_fee: fee = None
774 # assume that there will be 2 outputs (one for change)
775 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
777 self.fee_e.setText( self.format_amount( fee ) )
780 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
784 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
785 self.funds_error = True
786 text = _( "Not enough funds" )
787 c, u = self.wallet.get_frozen_balance()
788 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
790 self.statusBar().showMessage(text)
791 self.amount_e.setPalette(palette)
792 self.fee_e.setPalette(palette)
794 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
795 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
797 run_hook('create_send_tab', grid)
801 def set_pay_from(self, l):
803 self.from_list.clear()
804 self.from_label.setHidden(len(self.pay_from) == 0)
805 self.from_list.setHidden(len(self.pay_from) == 0)
806 for addr in self.pay_from:
807 c, u = self.wallet.get_addr_balance(addr)
808 balance = self.format_amount(c + u)
809 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
812 def update_completions(self):
814 for addr,label in self.wallet.labels.items():
815 if addr in self.wallet.addressbook:
816 l.append( label + ' <' + addr + '>')
818 run_hook('update_completions', l)
819 self.completions.setStringList(l)
823 return lambda s, *args: s.do_protect(func, args)
828 label = unicode( self.message_e.text() )
829 r = unicode( self.payto_e.text() )
832 # label or alias, with address in brackets
833 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
834 to_address = m.group(2) if m else r
836 if not is_valid(to_address):
837 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
841 amount = self.read_amount(unicode( self.amount_e.text()))
843 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
846 fee = self.read_amount(unicode( self.fee_e.text()))
848 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
851 confirm_amount = self.config.get('confirm_amount', 100000000)
852 if amount >= confirm_amount:
853 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
856 confirm_fee = self.config.get('confirm_fee', 100000)
857 if fee >= confirm_fee:
858 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()}):
861 self.send_tx(to_address, amount, fee, label)
865 def send_tx(self, to_address, amount, fee, label, password):
867 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
868 domain=self.get_payment_sources())
869 except Exception as e:
870 traceback.print_exc(file=sys.stdout)
871 self.show_message(str(e))
874 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
875 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
879 self.wallet.set_label(tx.hash(), label)
882 h = self.wallet.send_tx(tx)
883 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
884 status, msg = self.wallet.receive_tx( h, tx )
886 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
888 self.update_contacts_tab()
890 QMessageBox.warning(self, _('Error'), msg, _('OK'))
893 self.show_transaction(tx)
895 # add recipient to addressbook
896 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
897 self.wallet.addressbook.append(to_address)
902 def set_url(self, url):
903 address, amount, label, message, signature, identity, url = util.parse_url(url)
906 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
907 elif amount: amount = str(Decimal(amount))
910 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
913 self.mini.set_payment_fields(address, amount)
915 if label and self.wallet.labels.get(address) != label:
916 if self.question('Give label "%s" to address %s ?'%(label,address)):
917 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
918 self.wallet.addressbook.append(address)
919 self.wallet.set_label(address, label)
921 run_hook('set_url', url, self.show_message, self.question)
923 self.tabs.setCurrentIndex(1)
924 label = self.wallet.labels.get(address)
925 m_addr = label + ' <'+ address +'>' if label else address
926 self.payto_e.setText(m_addr)
928 self.message_e.setText(message)
930 self.amount_e.setText(amount)
933 self.set_frozen(self.payto_e,True)
934 self.set_frozen(self.amount_e,True)
935 self.set_frozen(self.message_e,True)
936 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
938 self.payto_sig.setVisible(False)
941 self.payto_sig.setVisible(False)
942 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
944 self.set_frozen(e,False)
946 self.set_pay_from([])
949 def set_frozen(self,entry,frozen):
951 entry.setReadOnly(True)
952 entry.setFrame(False)
954 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
955 entry.setPalette(palette)
957 entry.setReadOnly(False)
960 palette.setColor(entry.backgroundRole(), QColor('white'))
961 entry.setPalette(palette)
964 def set_addrs_frozen(self,addrs,freeze):
966 if not addr: continue
967 if addr in self.wallet.frozen_addresses and not freeze:
968 self.wallet.unfreeze(addr)
969 elif addr not in self.wallet.frozen_addresses and freeze:
970 self.wallet.freeze(addr)
971 self.update_receive_tab()
975 def create_list_tab(self, headers):
976 "generic tab creation method"
977 l = MyTreeWidget(self)
978 l.setColumnCount( len(headers) )
979 l.setHeaderLabels( headers )
989 vbox.addWidget(buttons)
994 buttons.setLayout(hbox)
999 def create_receive_tab(self):
1000 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1001 l.setContextMenuPolicy(Qt.CustomContextMenu)
1002 l.customContextMenuRequested.connect(self.create_receive_menu)
1003 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1004 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1005 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1006 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1007 self.receive_list = l
1008 self.receive_buttons_hbox = hbox
1015 def save_column_widths(self):
1016 self.column_widths["receive"] = []
1017 for i in range(self.receive_list.columnCount() -1):
1018 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1020 self.column_widths["history"] = []
1021 for i in range(self.history_list.columnCount() - 1):
1022 self.column_widths["history"].append(self.history_list.columnWidth(i))
1024 self.column_widths["contacts"] = []
1025 for i in range(self.contacts_list.columnCount() - 1):
1026 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1028 self.config.set_key("column_widths_2", self.column_widths, True)
1031 def create_contacts_tab(self):
1032 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1033 l.setContextMenuPolicy(Qt.CustomContextMenu)
1034 l.customContextMenuRequested.connect(self.create_contact_menu)
1035 for i,width in enumerate(self.column_widths['contacts']):
1036 l.setColumnWidth(i, width)
1038 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1039 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1040 self.contacts_list = l
1041 self.contacts_buttons_hbox = hbox
1046 def delete_imported_key(self, addr):
1047 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1048 self.wallet.delete_imported_key(addr)
1049 self.update_receive_tab()
1050 self.update_history_tab()
1052 def edit_account_label(self, k):
1053 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1055 label = unicode(text)
1056 self.wallet.set_label(k,label)
1057 self.update_receive_tab()
1059 def account_set_expanded(self, item, k, b):
1061 self.accounts_expanded[k] = b
1063 def create_account_menu(self, position, k, item):
1065 if item.isExpanded():
1066 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1068 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1069 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1070 if self.wallet.seed_version > 4:
1071 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1072 if self.wallet.account_is_pending(k):
1073 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1074 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1076 def delete_pending_account(self, k):
1077 self.wallet.delete_pending_account(k)
1078 self.update_receive_tab()
1080 def create_receive_menu(self, position):
1081 # fixme: this function apparently has a side effect.
1082 # if it is not called the menu pops up several times
1083 #self.receive_list.selectedIndexes()
1085 selected = self.receive_list.selectedItems()
1086 multi_select = len(selected) > 1
1087 addrs = [unicode(item.text(0)) for item in selected]
1088 if not multi_select:
1089 item = self.receive_list.itemAt(position)
1093 if not is_valid(addr):
1094 k = str(item.data(0,32).toString())
1096 self.create_account_menu(position, k, item)
1098 item.setExpanded(not item.isExpanded())
1102 if not multi_select:
1103 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1104 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1105 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1106 if self.wallet.seed:
1107 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1108 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1109 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1110 if addr in self.wallet.imported_keys:
1111 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1113 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1114 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1115 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1116 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1118 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1119 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1121 run_hook('receive_menu', menu, addrs)
1122 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1125 def get_sendable_balance(self):
1126 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1129 def get_payment_sources(self):
1131 return self.pay_from
1133 return self.wallet.get_account_addresses(self.current_account)
1136 def send_from_addresses(self, addrs):
1137 self.set_pay_from( addrs )
1138 self.tabs.setCurrentIndex(1)
1141 def payto(self, addr):
1143 label = self.wallet.labels.get(addr)
1144 m_addr = label + ' <' + addr + '>' if label else addr
1145 self.tabs.setCurrentIndex(1)
1146 self.payto_e.setText(m_addr)
1147 self.amount_e.setFocus()
1150 def delete_contact(self, x):
1151 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1152 self.wallet.delete_contact(x)
1153 self.wallet.set_label(x, None)
1154 self.update_history_tab()
1155 self.update_contacts_tab()
1156 self.update_completions()
1159 def create_contact_menu(self, position):
1160 item = self.contacts_list.itemAt(position)
1163 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1165 addr = unicode(item.text(0))
1166 label = unicode(item.text(1))
1167 is_editable = item.data(0,32).toBool()
1168 payto_addr = item.data(0,33).toString()
1169 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1170 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1171 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1173 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1174 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1176 run_hook('create_contact_menu', menu, item)
1177 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1180 def update_receive_item(self, item):
1181 item.setFont(0, QFont(MONOSPACE_FONT))
1182 address = str(item.data(0,0).toString())
1183 label = self.wallet.labels.get(address,'')
1184 item.setData(1,0,label)
1185 item.setData(0,32, True) # is editable
1187 run_hook('update_receive_item', address, item)
1189 if not self.wallet.is_mine(address): return
1191 c, u = self.wallet.get_addr_balance(address)
1192 balance = self.format_amount(c + u)
1193 item.setData(2,0,balance)
1195 if address in self.wallet.frozen_addresses:
1196 item.setBackgroundColor(0, QColor('lightblue'))
1199 def update_receive_tab(self):
1200 l = self.receive_list
1203 l.setColumnHidden(2, False)
1204 l.setColumnHidden(3, False)
1205 for i,width in enumerate(self.column_widths['receive']):
1206 l.setColumnWidth(i, width)
1208 if self.current_account is None:
1209 account_items = self.wallet.accounts.items()
1210 elif self.current_account != -1:
1211 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1215 for k, account in account_items:
1216 name = self.wallet.get_account_name(k)
1217 c,u = self.wallet.get_account_balance(k)
1218 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1219 l.addTopLevelItem(account_item)
1220 account_item.setExpanded(self.accounts_expanded.get(k, True))
1221 account_item.setData(0, 32, k)
1223 if not self.wallet.is_seeded(k):
1224 icon = QIcon(":icons/key.png")
1225 account_item.setIcon(0, icon)
1227 for is_change in ([0,1]):
1228 name = _("Receiving") if not is_change else _("Change")
1229 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1230 account_item.addChild(seq_item)
1231 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1233 if not is_change: seq_item.setExpanded(True)
1238 for address in account.get_addresses(is_change):
1239 h = self.wallet.history.get(address,[])
1243 if gap > self.wallet.gap_limit:
1248 c, u = self.wallet.get_addr_balance(address)
1249 num_tx = '*' if h == ['*'] else "%d"%len(h)
1250 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1251 self.update_receive_item(item)
1253 item.setBackgroundColor(1, QColor('red'))
1254 if len(h) > 0 and c == -u:
1256 seq_item.insertChild(0,used_item)
1258 used_item.addChild(item)
1260 seq_item.addChild(item)
1263 for k, addr in self.wallet.get_pending_accounts():
1264 name = self.wallet.labels.get(k,'')
1265 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1266 self.update_receive_item(item)
1267 l.addTopLevelItem(account_item)
1268 account_item.setExpanded(True)
1269 account_item.setData(0, 32, k)
1270 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1271 account_item.addChild(item)
1272 self.update_receive_item(item)
1275 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1276 c,u = self.wallet.get_imported_balance()
1277 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1278 l.addTopLevelItem(account_item)
1279 account_item.setExpanded(True)
1280 for address in self.wallet.imported_keys.keys():
1281 item = QTreeWidgetItem( [ address, '', '', ''] )
1282 self.update_receive_item(item)
1283 account_item.addChild(item)
1286 # we use column 1 because column 0 may be hidden
1287 l.setCurrentItem(l.topLevelItem(0),1)
1290 def update_contacts_tab(self):
1291 l = self.contacts_list
1294 for address in self.wallet.addressbook:
1295 label = self.wallet.labels.get(address,'')
1296 n = self.wallet.get_num_tx(address)
1297 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1298 item.setFont(0, QFont(MONOSPACE_FONT))
1299 # 32 = label can be edited (bool)
1300 item.setData(0,32, True)
1302 item.setData(0,33, address)
1303 l.addTopLevelItem(item)
1305 run_hook('update_contacts_tab', l)
1306 l.setCurrentItem(l.topLevelItem(0))
1310 def create_console_tab(self):
1311 from console import Console
1312 self.console = console = Console()
1316 def update_console(self):
1317 console = self.console
1318 console.history = self.config.get("console-history",[])
1319 console.history_index = len(console.history)
1321 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1322 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1324 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1326 def mkfunc(f, method):
1327 return lambda *args: apply( f, (method, args, self.password_dialog ))
1329 if m[0]=='_' or m in ['network','wallet']: continue
1330 methods[m] = mkfunc(c._run, m)
1332 console.updateNamespace(methods)
1335 def change_account(self,s):
1336 if s == _("All accounts"):
1337 self.current_account = None
1339 accounts = self.wallet.get_account_names()
1340 for k, v in accounts.items():
1342 self.current_account = k
1343 self.update_history_tab()
1344 self.update_status()
1345 self.update_receive_tab()
1347 def create_status_bar(self):
1350 sb.setFixedHeight(35)
1351 qtVersion = qVersion()
1353 self.balance_label = QLabel("")
1354 sb.addWidget(self.balance_label)
1356 from version_getter import UpdateLabel
1357 self.updatelabel = UpdateLabel(self.config, sb)
1359 self.account_selector = QComboBox()
1360 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1361 sb.addPermanentWidget(self.account_selector)
1363 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1364 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1366 self.lock_icon = QIcon()
1367 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1368 sb.addPermanentWidget( self.password_button )
1370 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1371 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1372 sb.addPermanentWidget( self.seed_button )
1373 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1374 sb.addPermanentWidget( self.status_button )
1376 run_hook('create_status_bar', (sb,))
1378 self.setStatusBar(sb)
1381 def update_lock_icon(self):
1382 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1383 self.password_button.setIcon( icon )
1386 def update_buttons_on_seed(self):
1387 if not self.wallet.is_watching_only():
1388 self.seed_button.show()
1389 self.password_button.show()
1390 self.send_button.setText(_("Send"))
1392 self.password_button.hide()
1393 self.seed_button.hide()
1394 self.send_button.setText(_("Create unsigned transaction"))
1397 def change_password_dialog(self):
1398 from password_dialog import PasswordDialog
1399 d = PasswordDialog(self.wallet, self)
1401 self.update_lock_icon()
1404 def new_contact_dialog(self):
1407 vbox = QVBoxLayout(d)
1408 vbox.addWidget(QLabel(_('New Contact')+':'))
1410 grid = QGridLayout()
1413 grid.addWidget(QLabel(_("Address")), 1, 0)
1414 grid.addWidget(line1, 1, 1)
1415 grid.addWidget(QLabel(_("Name")), 2, 0)
1416 grid.addWidget(line2, 2, 1)
1418 vbox.addLayout(grid)
1419 vbox.addLayout(ok_cancel_buttons(d))
1424 address = str(line1.text())
1425 label = unicode(line2.text())
1427 if not is_valid(address):
1428 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1431 self.wallet.add_contact(address)
1433 self.wallet.set_label(address, label)
1435 self.update_contacts_tab()
1436 self.update_history_tab()
1437 self.update_completions()
1438 self.tabs.setCurrentIndex(3)
1441 def new_account_dialog(self):
1443 dialog = QDialog(self)
1445 dialog.setWindowTitle(_("New Account"))
1447 vbox = QVBoxLayout()
1448 vbox.addWidget(QLabel(_('Account name')+':'))
1451 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1452 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1457 vbox.addLayout(ok_cancel_buttons(dialog))
1458 dialog.setLayout(vbox)
1462 name = str(e.text())
1465 self.wallet.create_pending_account('1', name)
1466 self.update_receive_tab()
1467 self.tabs.setCurrentIndex(2)
1471 def show_master_public_key_old(self):
1472 dialog = QDialog(self)
1474 dialog.setWindowTitle(_("Master Public Key"))
1476 main_text = QTextEdit()
1477 main_text.setText(self.wallet.get_master_public_key())
1478 main_text.setReadOnly(True)
1479 main_text.setMaximumHeight(170)
1480 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1482 ok_button = QPushButton(_("OK"))
1483 ok_button.setDefault(True)
1484 ok_button.clicked.connect(dialog.accept)
1486 main_layout = QGridLayout()
1487 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1489 main_layout.addWidget(main_text, 1, 0)
1490 main_layout.addWidget(qrw, 1, 1 )
1492 vbox = QVBoxLayout()
1493 vbox.addLayout(main_layout)
1494 vbox.addLayout(close_button(dialog))
1495 dialog.setLayout(vbox)
1499 def show_master_public_key(self):
1501 if self.wallet.seed_version == 4:
1502 self.show_master_public_key_old()
1505 dialog = QDialog(self)
1507 dialog.setWindowTitle(_("Master Public Keys"))
1509 chain_text = QTextEdit()
1510 chain_text.setReadOnly(True)
1511 chain_text.setMaximumHeight(170)
1512 chain_qrw = QRCodeWidget()
1514 mpk_text = QTextEdit()
1515 mpk_text.setReadOnly(True)
1516 mpk_text.setMaximumHeight(170)
1517 mpk_qrw = QRCodeWidget()
1519 main_layout = QGridLayout()
1521 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1522 main_layout.addWidget(mpk_text, 1, 1)
1523 main_layout.addWidget(mpk_qrw, 1, 2)
1525 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1526 main_layout.addWidget(chain_text, 2, 1)
1527 main_layout.addWidget(chain_qrw, 2, 2)
1530 c, K, cK = self.wallet.master_public_keys[str(key)]
1531 chain_text.setText(c)
1532 chain_qrw.set_addr(c)
1533 chain_qrw.update_qr()
1538 key_selector = QComboBox()
1539 keys = sorted(self.wallet.master_public_keys.keys())
1540 key_selector.addItems(keys)
1542 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1543 main_layout.addWidget(key_selector, 0, 1)
1544 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1548 vbox = QVBoxLayout()
1549 vbox.addLayout(main_layout)
1550 vbox.addLayout(close_button(dialog))
1552 dialog.setLayout(vbox)
1557 def show_seed_dialog(self, password):
1558 if self.wallet.is_watching_only():
1559 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1562 if self.wallet.seed:
1564 mnemonic = self.wallet.get_mnemonic(password)
1566 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1568 from seed_dialog import SeedDialog
1569 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1573 for k in self.wallet.master_private_keys.keys():
1574 pk = self.wallet.get_master_private_key(k, password)
1576 from seed_dialog import PrivateKeysDialog
1577 d = PrivateKeysDialog(self,l)
1584 def show_qrcode(self, data, title = _("QR code")):
1588 d.setWindowTitle(title)
1589 d.setMinimumSize(270, 300)
1590 vbox = QVBoxLayout()
1591 qrw = QRCodeWidget(data)
1592 vbox.addWidget(qrw, 1)
1593 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1594 hbox = QHBoxLayout()
1597 filename = os.path.join(self.config.path, "qrcode.bmp")
1600 bmp.save_qrcode(qrw.qr, filename)
1601 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1603 def copy_to_clipboard():
1604 bmp.save_qrcode(qrw.qr, filename)
1605 self.app.clipboard().setImage(QImage(filename))
1606 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1608 b = QPushButton(_("Copy"))
1610 b.clicked.connect(copy_to_clipboard)
1612 b = QPushButton(_("Save"))
1614 b.clicked.connect(print_qr)
1616 b = QPushButton(_("Close"))
1618 b.clicked.connect(d.accept)
1621 vbox.addLayout(hbox)
1626 def do_protect(self, func, args):
1627 if self.wallet.use_encryption:
1628 password = self.password_dialog()
1634 if args != (False,):
1635 args = (self,) + args + (password,)
1637 args = (self,password)
1642 def show_private_key(self, address, password):
1643 if not address: return
1645 pk_list = self.wallet.get_private_key(address, password)
1646 except Exception as e:
1647 self.show_message(str(e))
1651 d.setMinimumSize(600, 200)
1653 vbox = QVBoxLayout()
1654 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1655 vbox.addWidget( QLabel(_("Private key") + ':'))
1657 keys.setReadOnly(True)
1658 keys.setText('\n'.join(pk_list))
1659 vbox.addWidget(keys)
1660 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1661 vbox.addLayout(close_button(d))
1667 def do_sign(self, address, message, signature, password):
1668 message = unicode(message.toPlainText())
1669 message = message.encode('utf-8')
1671 sig = self.wallet.sign_message(str(address.text()), message, password)
1672 signature.setText(sig)
1673 except Exception as e:
1674 self.show_message(str(e))
1676 def do_verify(self, address, message, signature):
1677 message = unicode(message.toPlainText())
1678 message = message.encode('utf-8')
1679 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1680 self.show_message(_("Signature verified"))
1682 self.show_message(_("Error: wrong signature"))
1685 def sign_verify_message(self, address=''):
1688 d.setWindowTitle(_('Sign/verify Message'))
1689 d.setMinimumSize(410, 290)
1691 layout = QGridLayout(d)
1693 message_e = QTextEdit()
1694 layout.addWidget(QLabel(_('Message')), 1, 0)
1695 layout.addWidget(message_e, 1, 1)
1696 layout.setRowStretch(2,3)
1698 address_e = QLineEdit()
1699 address_e.setText(address)
1700 layout.addWidget(QLabel(_('Address')), 2, 0)
1701 layout.addWidget(address_e, 2, 1)
1703 signature_e = QTextEdit()
1704 layout.addWidget(QLabel(_('Signature')), 3, 0)
1705 layout.addWidget(signature_e, 3, 1)
1706 layout.setRowStretch(3,1)
1708 hbox = QHBoxLayout()
1710 b = QPushButton(_("Sign"))
1711 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1714 b = QPushButton(_("Verify"))
1715 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1718 b = QPushButton(_("Close"))
1719 b.clicked.connect(d.accept)
1721 layout.addLayout(hbox, 4, 1)
1726 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1728 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1729 message_e.setText(decrypted)
1730 except Exception as e:
1731 self.show_message(str(e))
1734 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1735 message = unicode(message_e.toPlainText())
1736 message = message.encode('utf-8')
1738 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1739 encrypted_e.setText(encrypted)
1740 except Exception as e:
1741 self.show_message(str(e))
1745 def encrypt_message(self, address = ''):
1748 d.setWindowTitle(_('Encrypt/decrypt Message'))
1749 d.setMinimumSize(610, 490)
1751 layout = QGridLayout(d)
1753 message_e = QTextEdit()
1754 layout.addWidget(QLabel(_('Message')), 1, 0)
1755 layout.addWidget(message_e, 1, 1)
1756 layout.setRowStretch(2,3)
1758 pubkey_e = QLineEdit()
1760 pubkey = self.wallet.getpubkeys(address)[0]
1761 pubkey_e.setText(pubkey)
1762 layout.addWidget(QLabel(_('Public key')), 2, 0)
1763 layout.addWidget(pubkey_e, 2, 1)
1765 encrypted_e = QTextEdit()
1766 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1767 layout.addWidget(encrypted_e, 3, 1)
1768 layout.setRowStretch(3,1)
1770 hbox = QHBoxLayout()
1771 b = QPushButton(_("Encrypt"))
1772 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1775 b = QPushButton(_("Decrypt"))
1776 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1779 b = QPushButton(_("Close"))
1780 b.clicked.connect(d.accept)
1783 layout.addLayout(hbox, 4, 1)
1787 def question(self, msg):
1788 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1790 def show_message(self, msg):
1791 QMessageBox.information(self, _('Message'), msg, _('OK'))
1793 def password_dialog(self ):
1800 vbox = QVBoxLayout()
1801 msg = _('Please enter your password')
1802 vbox.addWidget(QLabel(msg))
1804 grid = QGridLayout()
1806 grid.addWidget(QLabel(_('Password')), 1, 0)
1807 grid.addWidget(pw, 1, 1)
1808 vbox.addLayout(grid)
1810 vbox.addLayout(ok_cancel_buttons(d))
1813 run_hook('password_dialog', pw, grid, 1)
1814 if not d.exec_(): return
1815 return unicode(pw.text())
1824 def tx_from_text(self, txt):
1825 "json or raw hexadecimal"
1828 tx = Transaction(txt)
1834 tx_dict = json.loads(str(txt))
1835 assert "hex" in tx_dict.keys()
1836 assert "complete" in tx_dict.keys()
1837 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1838 if not tx_dict["complete"]:
1839 assert "input_info" in tx_dict.keys()
1840 input_info = json.loads(tx_dict['input_info'])
1841 tx.add_input_info(input_info)
1846 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1850 def read_tx_from_file(self):
1851 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1855 with open(fileName, "r") as f:
1856 file_content = f.read()
1857 except (ValueError, IOError, os.error), reason:
1858 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1860 return self.tx_from_text(file_content)
1864 def sign_raw_transaction(self, tx, input_info, password):
1865 self.wallet.signrawtransaction(tx, input_info, [], password)
1867 def do_process_from_text(self):
1868 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1871 tx = self.tx_from_text(text)
1873 self.show_transaction(tx)
1875 def do_process_from_file(self):
1876 tx = self.read_tx_from_file()
1878 self.show_transaction(tx)
1880 def do_process_from_txid(self):
1881 from electrum import transaction
1882 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1884 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1886 tx = transaction.Transaction(r)
1888 self.show_transaction(tx)
1890 self.show_message("unknown transaction")
1892 def do_process_from_csvReader(self, csvReader):
1897 for position, row in enumerate(csvReader):
1899 if not is_valid(address):
1900 errors.append((position, address))
1902 amount = Decimal(row[1])
1903 amount = int(100000000*amount)
1904 outputs.append((address, amount))
1905 except (ValueError, IOError, os.error), reason:
1906 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1910 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1911 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1915 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1916 except Exception as e:
1917 self.show_message(str(e))
1920 self.show_transaction(tx)
1922 def do_process_from_csv_file(self):
1923 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1927 with open(fileName, "r") as f:
1928 csvReader = csv.reader(f)
1929 self.do_process_from_csvReader(csvReader)
1930 except (ValueError, IOError, os.error), reason:
1931 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1934 def do_process_from_csv_text(self):
1935 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1936 + _("Format: address, amount. One output per line"), _("Load CSV"))
1939 f = StringIO.StringIO(text)
1940 csvReader = csv.reader(f)
1941 self.do_process_from_csvReader(csvReader)
1946 def do_export_privkeys(self, password):
1947 if not self.wallet.seed:
1948 self.show_message(_("This wallet has no seed"))
1951 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.")))
1954 select_export = _('Select file to export your private keys to')
1955 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1957 with open(fileName, "w+") as csvfile:
1958 transaction = csv.writer(csvfile)
1959 transaction.writerow(["address", "private_key"])
1961 addresses = self.wallet.addresses(True)
1963 for addr in addresses:
1964 pk = "".join(self.wallet.get_private_key(addr, password))
1965 transaction.writerow(["%34s"%addr,pk])
1967 self.show_message(_("Private keys exported."))
1969 except (IOError, os.error), reason:
1970 export_error_label = _("Electrum was unable to produce a private key-export.")
1971 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1973 except Exception as e:
1974 self.show_message(str(e))
1978 def do_import_labels(self):
1979 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1980 if not labelsFile: return
1982 f = open(labelsFile, 'r')
1985 for key, value in json.loads(data).items():
1986 self.wallet.set_label(key, value)
1987 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1988 except (IOError, os.error), reason:
1989 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1992 def do_export_labels(self):
1993 labels = self.wallet.labels
1995 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1997 with open(fileName, 'w+') as f:
1998 json.dump(labels, f)
1999 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2000 except (IOError, os.error), reason:
2001 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2004 def do_export_history(self):
2005 from lite_window import csv_transaction
2006 csv_transaction(self.wallet)
2010 def do_import_privkey(self, password):
2011 if not self.wallet.imported_keys:
2012 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2013 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2014 + _('Are you sure you understand what you are doing?'), 3, 4)
2017 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2020 text = str(text).split()
2025 addr = self.wallet.import_key(key, password)
2026 except Exception as e:
2032 addrlist.append(addr)
2034 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2036 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2037 self.update_receive_tab()
2038 self.update_history_tab()
2041 def settings_dialog(self):
2043 d.setWindowTitle(_('Electrum Settings'))
2045 vbox = QVBoxLayout()
2046 grid = QGridLayout()
2047 grid.setColumnStretch(0,1)
2049 nz_label = QLabel(_('Display zeros') + ':')
2050 grid.addWidget(nz_label, 0, 0)
2051 nz_e = AmountEdit(None,True)
2052 nz_e.setText("%d"% self.num_zeros)
2053 grid.addWidget(nz_e, 0, 1)
2054 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2055 grid.addWidget(HelpButton(msg), 0, 2)
2056 if not self.config.is_modifiable('num_zeros'):
2057 for w in [nz_e, nz_label]: w.setEnabled(False)
2059 lang_label=QLabel(_('Language') + ':')
2060 grid.addWidget(lang_label, 1, 0)
2061 lang_combo = QComboBox()
2062 from electrum.i18n import languages
2063 lang_combo.addItems(languages.values())
2065 index = languages.keys().index(self.config.get("language",''))
2068 lang_combo.setCurrentIndex(index)
2069 grid.addWidget(lang_combo, 1, 1)
2070 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2071 if not self.config.is_modifiable('language'):
2072 for w in [lang_combo, lang_label]: w.setEnabled(False)
2075 fee_label = QLabel(_('Transaction fee') + ':')
2076 grid.addWidget(fee_label, 2, 0)
2077 fee_e = AmountEdit(self.base_unit)
2078 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2079 grid.addWidget(fee_e, 2, 1)
2080 msg = _('Fee per kilobyte of transaction.') + ' ' \
2081 + _('Recommended value') + ': ' + self.format_amount(20000)
2082 grid.addWidget(HelpButton(msg), 2, 2)
2083 if not self.config.is_modifiable('fee_per_kb'):
2084 for w in [fee_e, fee_label]: w.setEnabled(False)
2086 units = ['BTC', 'mBTC']
2087 unit_label = QLabel(_('Base unit') + ':')
2088 grid.addWidget(unit_label, 3, 0)
2089 unit_combo = QComboBox()
2090 unit_combo.addItems(units)
2091 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2092 grid.addWidget(unit_combo, 3, 1)
2093 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2094 + '\n1BTC=1000mBTC.\n' \
2095 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2097 usechange_cb = QCheckBox(_('Use change addresses'))
2098 usechange_cb.setChecked(self.wallet.use_change)
2099 grid.addWidget(usechange_cb, 4, 0)
2100 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2101 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2103 grid.setRowStretch(5,1)
2105 vbox.addLayout(grid)
2106 vbox.addLayout(ok_cancel_buttons(d))
2110 if not d.exec_(): return
2112 fee = unicode(fee_e.text())
2114 fee = self.read_amount(fee)
2116 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2119 self.wallet.set_fee(fee)
2121 nz = unicode(nz_e.text())
2126 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2129 if self.num_zeros != nz:
2131 self.config.set_key('num_zeros', nz, True)
2132 self.update_history_tab()
2133 self.update_receive_tab()
2135 usechange_result = usechange_cb.isChecked()
2136 if self.wallet.use_change != usechange_result:
2137 self.wallet.use_change = usechange_result
2138 self.wallet.storage.put('use_change', self.wallet.use_change)
2140 unit_result = units[unit_combo.currentIndex()]
2141 if self.base_unit() != unit_result:
2142 self.decimal_point = 8 if unit_result == 'BTC' else 5
2143 self.config.set_key('decimal_point', self.decimal_point, True)
2144 self.update_history_tab()
2145 self.update_status()
2147 need_restart = False
2149 lang_request = languages.keys()[lang_combo.currentIndex()]
2150 if lang_request != self.config.get('language'):
2151 self.config.set_key("language", lang_request, True)
2154 run_hook('close_settings_dialog')
2157 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2160 def run_network_dialog(self):
2161 if not self.network:
2163 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2165 def closeEvent(self, event):
2168 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2169 self.save_column_widths()
2170 self.config.set_key("console-history", self.console.history[-50:], True)
2171 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2175 def plugins_dialog(self):
2176 from electrum.plugins import plugins
2179 d.setWindowTitle(_('Electrum Plugins'))
2182 vbox = QVBoxLayout(d)
2185 scroll = QScrollArea()
2186 scroll.setEnabled(True)
2187 scroll.setWidgetResizable(True)
2188 scroll.setMinimumSize(400,250)
2189 vbox.addWidget(scroll)
2193 w.setMinimumHeight(len(plugins)*35)
2195 grid = QGridLayout()
2196 grid.setColumnStretch(0,1)
2199 def do_toggle(cb, p, w):
2202 if w: w.setEnabled(r)
2204 def mk_toggle(cb, p, w):
2205 return lambda: do_toggle(cb,p,w)
2207 for i, p in enumerate(plugins):
2209 cb = QCheckBox(p.fullname())
2210 cb.setDisabled(not p.is_available())
2211 cb.setChecked(p.is_enabled())
2212 grid.addWidget(cb, i, 0)
2213 if p.requires_settings():
2214 w = p.settings_widget(self)
2215 w.setEnabled( p.is_enabled() )
2216 grid.addWidget(w, i, 1)
2219 cb.clicked.connect(mk_toggle(cb,p,w))
2220 grid.addWidget(HelpButton(p.description()), i, 2)
2222 print_msg(_("Error: cannot display plugin"), p)
2223 traceback.print_exc(file=sys.stdout)
2224 grid.setRowStretch(i+1,1)
2226 vbox.addLayout(close_button(d))
2231 def show_account_details(self, k):
2233 d.setWindowTitle(_('Account Details'))
2236 vbox = QVBoxLayout(d)
2237 roots = self.wallet.get_roots(k)
2239 name = self.wallet.get_account_name(k)
2240 label = QLabel('Name: ' + name)
2241 vbox.addWidget(label)
2243 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2244 vbox.addWidget(QLabel('Type: ' + acctype))
2246 label = QLabel('Derivation: ' + k)
2247 vbox.addWidget(label)
2250 # mpk = self.wallet.master_public_keys[root]
2251 # text = QTextEdit()
2252 # text.setReadOnly(True)
2253 # text.setMaximumHeight(120)
2254 # text.setText(repr(mpk))
2255 # vbox.addWidget(text)
2257 vbox.addLayout(close_button(d))