3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
33 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
34 from electrum.plugins import run_hook
38 from electrum.wallet import format_satoshis
39 from electrum import Transaction
40 from electrum import mnemonic
41 from electrum import util, bitcoin, commands, Interface, Wallet
42 from electrum import SimpleConfig, Wallet, WalletStorage
45 from electrum import bmp, pyqrnative
47 from amountedit import AmountEdit
48 from network_dialog import NetworkDialog
49 from qrcodewidget import QRCodeWidget
51 from decimal import Decimal
59 if platform.system() == 'Windows':
60 MONOSPACE_FONT = 'Lucida Console'
61 elif platform.system() == 'Darwin':
62 MONOSPACE_FONT = 'Monaco'
64 MONOSPACE_FONT = 'monospace'
66 from electrum import ELECTRUM_VERSION
76 class StatusBarButton(QPushButton):
77 def __init__(self, icon, tooltip, func):
78 QPushButton.__init__(self, icon, '')
79 self.setToolTip(tooltip)
81 self.setMaximumWidth(25)
82 self.clicked.connect(func)
84 self.setIconSize(QSize(25,25))
86 def keyPressEvent(self, e):
87 if e.key() == QtCore.Qt.Key_Return:
99 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
101 class ElectrumWindow(QMainWindow):
102 def build_menu(self):
104 m.addAction(_("Show/Hide"), self.show_or_hide)
106 m.addAction(_("Exit Electrum"), self.close)
107 self.tray.setContextMenu(m)
109 def show_or_hide(self):
110 self.tray_activated(QSystemTrayIcon.DoubleClick)
112 def tray_activated(self, reason):
113 if reason == QSystemTrayIcon.DoubleClick:
114 if self.isMinimized() or self.isHidden():
119 def __init__(self, config, network):
120 QMainWindow.__init__(self)
123 self.network = network
125 self._close_electrum = False
128 if sys.platform == 'darwin':
129 self.icon = QIcon(":icons/electrum_dark_icon.png")
130 #self.icon = QIcon(":icons/lock.png")
132 self.icon = QIcon(':icons/electrum_light_icon.png')
134 self.tray = QSystemTrayIcon(self.icon, self)
135 self.tray.setToolTip('Electrum')
136 self.tray.activated.connect(self.tray_activated)
140 self.create_status_bar()
142 self.need_update = threading.Event()
144 self.decimal_point = config.get('decimal_point', 8)
145 self.num_zeros = int(config.get('num_zeros',0))
147 set_language(config.get('language'))
149 self.funds_error = False
150 self.completions = QStringListModel()
152 self.tabs = tabs = QTabWidget(self)
153 self.column_widths = self.config.get("column_widths_2", default_column_widths )
154 tabs.addTab(self.create_history_tab(), _('History') )
155 tabs.addTab(self.create_send_tab(), _('Send') )
156 tabs.addTab(self.create_receive_tab(), _('Receive') )
157 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
158 tabs.addTab(self.create_console_tab(), _('Console') )
159 tabs.setMinimumSize(600, 400)
160 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
161 self.setCentralWidget(tabs)
163 g = self.config.get("winpos-qt",[100, 100, 840, 400])
164 self.setGeometry(g[0], g[1], g[2], g[3])
166 self.setWindowIcon(QIcon(":icons/electrum.png"))
169 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
170 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
171 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
172 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
173 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
175 for i in range(tabs.count()):
176 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
178 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
179 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
180 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
182 self.history_list.setFocus(True)
186 self.network.register_callback('updated', lambda: self.need_update.set())
187 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
188 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
189 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
190 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
192 # set initial message
193 self.console.showMessage(self.network.banner)
200 self.config.set_key('lite_mode', False, True)
205 self.config.set_key('lite_mode', True, True)
212 if not self.check_qt_version():
213 if self.config.get('lite_mode') is True:
214 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
215 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
216 self.config.set_key('lite_mode', False, True)
222 actuator = lite_window.MiniActuator(self)
224 actuator.load_theme()
226 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
228 driver = lite_window.MiniDriver(self, self.mini)
230 if self.config.get('lite_mode') is True:
236 def check_qt_version(self):
237 qtVersion = qVersion()
238 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
241 def update_account_selector(self):
243 accounts = self.wallet.get_account_names()
244 self.account_selector.clear()
245 if len(accounts) > 1:
246 self.account_selector.addItems([_("All accounts")] + accounts.values())
247 self.account_selector.setCurrentIndex(0)
248 self.account_selector.show()
250 self.account_selector.hide()
253 def load_wallet(self, wallet):
256 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
257 self.current_account = self.wallet.storage.get("current_account", None)
259 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
260 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
261 self.setWindowTitle( title )
263 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
264 self.notify_transactions()
265 self.update_account_selector()
266 self.new_account.setEnabled(self.wallet.seed_version>4)
267 self.update_lock_icon()
268 self.update_buttons_on_seed()
269 self.update_console()
271 run_hook('load_wallet', wallet)
274 def open_wallet(self):
275 wallet_folder = self.wallet.storage.path
276 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
280 storage = WalletStorage({'wallet_path': filename})
281 if not storage.file_exists:
282 self.show_message("file not found "+ filename)
285 self.wallet.stop_threads()
288 wallet = Wallet(storage)
289 wallet.start_threads(self.network)
291 self.load_wallet(wallet)
295 def backup_wallet(self):
297 path = self.wallet.storage.path
298 wallet_folder = os.path.dirname(path)
299 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
303 new_path = os.path.join(wallet_folder, filename)
306 shutil.copy2(path, new_path)
307 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
308 except (IOError, os.error), reason:
309 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
312 def new_wallet(self):
315 wallet_folder = os.path.dirname(self.wallet.storage.path)
316 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
319 filename = os.path.join(wallet_folder, filename)
321 storage = WalletStorage({'wallet_path': filename})
322 if storage.file_exists:
323 QMessageBox.critical(None, "Error", _("File exists"))
326 wizard = installwizard.InstallWizard(self.config, self.network, storage)
327 wallet = wizard.run()
329 self.load_wallet(wallet)
333 def init_menubar(self):
336 file_menu = menubar.addMenu(_("&File"))
337 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
338 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
339 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
340 file_menu.addAction(_("&Quit"), self.close)
342 wallet_menu = menubar.addMenu(_("&Wallet"))
343 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
344 self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
346 wallet_menu.addSeparator()
348 wallet_menu.addAction(_("&Password"), self.change_password_dialog)
349 wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
350 wallet_menu.addAction(_("&Master Public Key"), self.show_master_public_key)
352 wallet_menu.addSeparator()
353 labels_menu = wallet_menu.addMenu(_("&Labels"))
354 labels_menu.addAction(_("&Import"), self.do_import_labels)
355 labels_menu.addAction(_("&Export"), self.do_export_labels)
357 keys_menu = wallet_menu.addMenu(_("&Private keys"))
358 keys_menu.addAction(_("&Import"), self.do_import_privkey)
359 keys_menu.addAction(_("&Export"), self.do_export_privkeys)
361 wallet_menu.addAction(_("&Export History"), self.do_export_history)
363 tools_menu = menubar.addMenu(_("&Tools"))
365 # Settings / Preferences are all reserved keywords in OSX using this as work around
366 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
367 tools_menu.addAction(_("&Network"), self.run_network_dialog)
368 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
369 tools_menu.addSeparator()
370 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
371 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
372 tools_menu.addSeparator()
374 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
375 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
376 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
378 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
379 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
380 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
381 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
383 help_menu = menubar.addMenu(_("&Help"))
384 help_menu.addAction(_("&About"), self.show_about)
385 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
386 help_menu.addSeparator()
387 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
388 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
390 self.setMenuBar(menubar)
392 def show_about(self):
393 QMessageBox.about(self, "Electrum",
394 _("Version")+" %s" % (self.wallet.electrum_version) + "\n\n" + _("Electrum's focus is speed, with low resource usage and simplifying Bitcoin. You do not need to perform regular backups, because your wallet can be recovered from a secret phrase that you can memorize or write on paper. Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin system."))
396 def show_report_bug(self):
397 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
398 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
401 def notify_transactions(self):
402 if not self.network or not self.network.is_connected():
405 print_error("Notifying GUI")
406 if len(self.network.interface.pending_transactions_for_notifications) > 0:
407 # Combine the transactions if there are more then three
408 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
411 for tx in self.network.interface.pending_transactions_for_notifications:
412 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
416 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
417 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
419 self.network.interface.pending_transactions_for_notifications = []
421 for tx in self.network.interface.pending_transactions_for_notifications:
423 self.network.interface.pending_transactions_for_notifications.remove(tx)
424 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
426 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
428 def notify(self, message):
429 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
433 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
434 def getOpenFileName(self, title, filter = ""):
435 directory = self.config.get('io_dir', os.path.expanduser('~'))
436 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
437 if fileName and directory != os.path.dirname(fileName):
438 self.config.set_key('io_dir', os.path.dirname(fileName), True)
441 def getSaveFileName(self, title, filename, filter = ""):
442 directory = self.config.get('io_dir', os.path.expanduser('~'))
443 path = os.path.join( directory, filename )
444 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
445 if fileName and directory != os.path.dirname(fileName):
446 self.config.set_key('io_dir', os.path.dirname(fileName), True)
450 QMainWindow.close(self)
451 run_hook('close_main_window')
453 def connect_slots(self, sender):
454 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
455 self.previous_payto_e=''
457 def timer_actions(self):
458 if self.need_update.is_set():
460 self.need_update.clear()
461 run_hook('timer_actions')
463 def format_amount(self, x, is_diff=False, whitespaces=False):
464 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
466 def read_amount(self, x):
467 if x in['.', '']: return None
468 p = pow(10, self.decimal_point)
469 return int( p * Decimal(x) )
472 assert self.decimal_point in [5,8]
473 return "BTC" if self.decimal_point == 8 else "mBTC"
476 def update_status(self):
477 if self.network is None or not self.network.is_running():
479 icon = QIcon(":icons/status_disconnected.png")
481 elif self.network.is_connected():
482 if not self.wallet.up_to_date:
483 text = _("Synchronizing...")
484 icon = QIcon(":icons/status_waiting.png")
485 elif self.network.server_lag > 1:
486 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
487 icon = QIcon(":icons/status_lagging.png")
489 c, u = self.wallet.get_account_balance(self.current_account)
490 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
491 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
494 run_hook('set_quote_text', c+u, r)
497 text += " (%s)"%quote
499 self.tray.setToolTip(text)
500 icon = QIcon(":icons/status_connected.png")
502 text = _("Not connected")
503 icon = QIcon(":icons/status_disconnected.png")
505 self.balance_label.setText(text)
506 self.status_button.setIcon( icon )
509 def update_wallet(self):
511 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
512 self.update_history_tab()
513 self.update_receive_tab()
514 self.update_contacts_tab()
515 self.update_completions()
518 def create_history_tab(self):
519 self.history_list = l = MyTreeWidget(self)
521 for i,width in enumerate(self.column_widths['history']):
522 l.setColumnWidth(i, width)
523 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
524 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
525 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
527 l.customContextMenuRequested.connect(self.create_history_menu)
531 def create_history_menu(self, position):
532 self.history_list.selectedIndexes()
533 item = self.history_list.currentItem()
535 tx_hash = str(item.data(0, Qt.UserRole).toString())
536 if not tx_hash: return
538 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
539 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
540 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
541 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
542 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
545 def show_transaction(self, tx):
546 import transaction_dialog
547 d = transaction_dialog.TxDialog(tx, self)
550 def tx_label_clicked(self, item, column):
551 if column==2 and item.isSelected():
553 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
554 self.history_list.editItem( item, column )
555 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
558 def tx_label_changed(self, item, column):
562 tx_hash = str(item.data(0, Qt.UserRole).toString())
563 tx = self.wallet.transactions.get(tx_hash)
564 text = unicode( item.text(2) )
565 self.wallet.set_label(tx_hash, text)
567 item.setForeground(2, QBrush(QColor('black')))
569 text = self.wallet.get_default_label(tx_hash)
570 item.setText(2, text)
571 item.setForeground(2, QBrush(QColor('gray')))
575 def edit_label(self, is_recv):
576 l = self.receive_list if is_recv else self.contacts_list
577 item = l.currentItem()
578 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
579 l.editItem( item, 1 )
580 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
584 def address_label_clicked(self, item, column, l, column_addr, column_label):
585 if column == column_label and item.isSelected():
586 is_editable = item.data(0, 32).toBool()
589 addr = unicode( item.text(column_addr) )
590 label = unicode( item.text(column_label) )
591 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
592 l.editItem( item, column )
593 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
596 def address_label_changed(self, item, column, l, column_addr, column_label):
597 if column == column_label:
598 addr = unicode( item.text(column_addr) )
599 text = unicode( item.text(column_label) )
600 is_editable = item.data(0, 32).toBool()
604 changed = self.wallet.set_label(addr, text)
606 self.update_history_tab()
607 self.update_completions()
609 self.current_item_changed(item)
611 run_hook('item_changed', item, column)
614 def current_item_changed(self, a):
615 run_hook('current_item_changed', a)
619 def update_history_tab(self):
621 self.history_list.clear()
622 for item in self.wallet.get_tx_history(self.current_account):
623 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
624 time_str = _("unknown")
627 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
629 time_str = _("error")
632 time_str = 'unverified'
633 icon = QIcon(":icons/unconfirmed.png")
636 icon = QIcon(":icons/unconfirmed.png")
638 icon = QIcon(":icons/clock%d.png"%conf)
640 icon = QIcon(":icons/confirmed.png")
642 if value is not None:
643 v_str = self.format_amount(value, True, whitespaces=True)
647 balance_str = self.format_amount(balance, whitespaces=True)
650 label, is_default_label = self.wallet.get_label(tx_hash)
652 label = _('Pruned transaction outputs')
653 is_default_label = False
655 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
656 item.setFont(2, QFont(MONOSPACE_FONT))
657 item.setFont(3, QFont(MONOSPACE_FONT))
658 item.setFont(4, QFont(MONOSPACE_FONT))
660 item.setForeground(3, QBrush(QColor("#BC1E1E")))
662 item.setData(0, Qt.UserRole, tx_hash)
663 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
665 item.setForeground(2, QBrush(QColor('grey')))
667 item.setIcon(0, icon)
668 self.history_list.insertTopLevelItem(0,item)
671 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
672 run_hook('history_tab_update')
675 def create_send_tab(self):
680 grid.setColumnMinimumWidth(3,300)
681 grid.setColumnStretch(5,1)
684 self.payto_e = QLineEdit()
685 grid.addWidget(QLabel(_('Pay to')), 1, 0)
686 grid.addWidget(self.payto_e, 1, 1, 1, 3)
688 grid.addWidget(HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')), 1, 4)
690 completer = QCompleter()
691 completer.setCaseSensitivity(False)
692 self.payto_e.setCompleter(completer)
693 completer.setModel(self.completions)
695 self.message_e = QLineEdit()
696 grid.addWidget(QLabel(_('Description')), 2, 0)
697 grid.addWidget(self.message_e, 2, 1, 1, 3)
698 grid.addWidget(HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')), 2, 4)
700 self.from_label = QLabel(_('From'))
701 grid.addWidget(self.from_label, 3, 0)
702 self.from_list = QTreeWidget(self)
703 self.from_list.setColumnCount(2)
704 self.from_list.setColumnWidth(0, 350)
705 self.from_list.setColumnWidth(1, 50)
706 self.from_list.setHeaderHidden (True)
707 self.from_list.setMaximumHeight(80)
708 grid.addWidget(self.from_list, 3, 1, 1, 3)
709 self.set_pay_from([])
711 self.amount_e = AmountEdit(self.base_unit)
712 grid.addWidget(QLabel(_('Amount')), 4, 0)
713 grid.addWidget(self.amount_e, 4, 1, 1, 2)
714 grid.addWidget(HelpButton(
715 _('Amount to be sent.') + '\n\n' \
716 + _('The amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') \
717 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
719 self.fee_e = AmountEdit(self.base_unit)
720 grid.addWidget(QLabel(_('Fee')), 5, 0)
721 grid.addWidget(self.fee_e, 5, 1, 1, 2)
722 grid.addWidget(HelpButton(
723 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
724 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
725 + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 5, 3)
727 run_hook('exchange_rate_button', grid)
729 self.send_button = EnterButton(_("Send"), self.do_send)
730 grid.addWidget(self.send_button, 6, 1)
732 b = EnterButton(_("Clear"),self.do_clear)
733 grid.addWidget(b, 6, 2)
735 self.payto_sig = QLabel('')
736 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
738 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
739 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
748 def entry_changed( is_fee ):
749 self.funds_error = False
751 if self.amount_e.is_shortcut:
752 self.amount_e.is_shortcut = False
753 sendable = self.get_sendable_balance()
754 # there is only one output because we are completely spending inputs
755 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
756 fee = self.wallet.estimated_fee(inputs, 1)
758 self.amount_e.setText( self.format_amount(amount) )
759 self.fee_e.setText( self.format_amount( fee ) )
762 amount = self.read_amount(str(self.amount_e.text()))
763 fee = self.read_amount(str(self.fee_e.text()))
765 if not is_fee: fee = None
768 # assume that there will be 2 outputs (one for change)
769 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
771 self.fee_e.setText( self.format_amount( fee ) )
774 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
778 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
779 self.funds_error = True
780 text = _( "Not enough funds" )
781 c, u = self.wallet.get_frozen_balance()
782 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
784 self.statusBar().showMessage(text)
785 self.amount_e.setPalette(palette)
786 self.fee_e.setPalette(palette)
788 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
789 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
791 run_hook('create_send_tab', grid)
795 def set_pay_from(self, l):
797 self.from_list.clear()
798 self.from_label.setHidden(len(self.pay_from) == 0)
799 self.from_list.setHidden(len(self.pay_from) == 0)
800 for addr in self.pay_from:
801 c, u = self.wallet.get_addr_balance(addr)
802 balance = self.format_amount(c + u)
803 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
806 def update_completions(self):
808 for addr,label in self.wallet.labels.items():
809 if addr in self.wallet.addressbook:
810 l.append( label + ' <' + addr + '>')
812 run_hook('update_completions', l)
813 self.completions.setStringList(l)
817 return lambda s, *args: s.do_protect(func, args)
822 label = unicode( self.message_e.text() )
823 r = unicode( self.payto_e.text() )
826 # label or alias, with address in brackets
827 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
828 to_address = m.group(2) if m else r
830 if not is_valid(to_address):
831 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
835 amount = self.read_amount(unicode( self.amount_e.text()))
837 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
840 fee = self.read_amount(unicode( self.fee_e.text()))
842 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
845 confirm_amount = self.config.get('confirm_amount', 100000000)
846 if amount >= confirm_amount:
847 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
850 confirm_fee = self.config.get('confirm_fee', 100000)
851 if fee >= confirm_fee:
852 if not self.question(_("The fee for this transaction seems unusually high.\nAre you really sure you want to pay %(fee)s in fees?")%{ 'fee' : self.format_amount(fee) + ' '+ self.base_unit()}):
855 self.send_tx(to_address, amount, fee, label)
859 def send_tx(self, to_address, amount, fee, label, password):
861 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
862 domain=self.get_payment_sources())
863 except Exception as e:
864 traceback.print_exc(file=sys.stdout)
865 self.show_message(str(e))
868 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
869 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
873 self.wallet.set_label(tx.hash(), label)
876 h = self.wallet.send_tx(tx)
877 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
878 status, msg = self.wallet.receive_tx( h, tx )
880 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
882 self.update_contacts_tab()
884 QMessageBox.warning(self, _('Error'), msg, _('OK'))
887 self.show_transaction(tx)
889 # add recipient to addressbook
890 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
891 self.wallet.addressbook.append(to_address)
896 def set_url(self, url):
897 address, amount, label, message, signature, identity, url = util.parse_url(url)
900 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
901 elif amount: amount = str(Decimal(amount))
904 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
907 self.mini.set_payment_fields(address, amount)
909 if label and self.wallet.labels.get(address) != label:
910 if self.question('Give label "%s" to address %s ?'%(label,address)):
911 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
912 self.wallet.addressbook.append(address)
913 self.wallet.set_label(address, label)
915 run_hook('set_url', url, self.show_message, self.question)
917 self.tabs.setCurrentIndex(1)
918 label = self.wallet.labels.get(address)
919 m_addr = label + ' <'+ address +'>' if label else address
920 self.payto_e.setText(m_addr)
922 self.message_e.setText(message)
924 self.amount_e.setText(amount)
927 self.set_frozen(self.payto_e,True)
928 self.set_frozen(self.amount_e,True)
929 self.set_frozen(self.message_e,True)
930 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
932 self.payto_sig.setVisible(False)
935 self.payto_sig.setVisible(False)
936 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
938 self.set_frozen(e,False)
940 self.set_pay_from([])
943 def set_frozen(self,entry,frozen):
945 entry.setReadOnly(True)
946 entry.setFrame(False)
948 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
949 entry.setPalette(palette)
951 entry.setReadOnly(False)
954 palette.setColor(entry.backgroundRole(), QColor('white'))
955 entry.setPalette(palette)
958 def set_addrs_frozen(self,addrs,freeze):
960 if not addr: continue
961 if addr in self.wallet.frozen_addresses and not freeze:
962 self.wallet.unfreeze(addr)
963 elif addr not in self.wallet.frozen_addresses and freeze:
964 self.wallet.freeze(addr)
965 self.update_receive_tab()
969 def create_list_tab(self, headers):
970 "generic tab creation method"
971 l = MyTreeWidget(self)
972 l.setColumnCount( len(headers) )
973 l.setHeaderLabels( headers )
983 vbox.addWidget(buttons)
988 buttons.setLayout(hbox)
993 def create_receive_tab(self):
994 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
995 l.setContextMenuPolicy(Qt.CustomContextMenu)
996 l.customContextMenuRequested.connect(self.create_receive_menu)
997 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
998 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
999 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1000 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1001 self.receive_list = l
1002 self.receive_buttons_hbox = hbox
1009 def save_column_widths(self):
1010 self.column_widths["receive"] = []
1011 for i in range(self.receive_list.columnCount() -1):
1012 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1014 self.column_widths["history"] = []
1015 for i in range(self.history_list.columnCount() - 1):
1016 self.column_widths["history"].append(self.history_list.columnWidth(i))
1018 self.column_widths["contacts"] = []
1019 for i in range(self.contacts_list.columnCount() - 1):
1020 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1022 self.config.set_key("column_widths_2", self.column_widths, True)
1025 def create_contacts_tab(self):
1026 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1027 l.setContextMenuPolicy(Qt.CustomContextMenu)
1028 l.customContextMenuRequested.connect(self.create_contact_menu)
1029 for i,width in enumerate(self.column_widths['contacts']):
1030 l.setColumnWidth(i, width)
1032 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1033 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1034 self.contacts_list = l
1035 self.contacts_buttons_hbox = hbox
1040 def delete_imported_key(self, addr):
1041 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1042 self.wallet.delete_imported_key(addr)
1043 self.update_receive_tab()
1044 self.update_history_tab()
1046 def edit_account_label(self, k):
1047 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1049 label = unicode(text)
1050 self.wallet.set_label(k,label)
1051 self.update_receive_tab()
1053 def account_set_expanded(self, item, k, b):
1055 self.accounts_expanded[k] = b
1057 def create_account_menu(self, position, k, item):
1059 if item.isExpanded():
1060 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1062 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1063 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1064 if self.wallet.seed_version > 4:
1065 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1066 if self.wallet.account_is_pending(k):
1067 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1068 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1070 def delete_pending_account(self, k):
1071 self.wallet.delete_pending_account(k)
1072 self.update_receive_tab()
1074 def create_receive_menu(self, position):
1075 # fixme: this function apparently has a side effect.
1076 # if it is not called the menu pops up several times
1077 #self.receive_list.selectedIndexes()
1079 selected = self.receive_list.selectedItems()
1080 multi_select = len(selected) > 1
1081 addrs = [unicode(item.text(0)) for item in selected]
1082 if not multi_select:
1083 item = self.receive_list.itemAt(position)
1087 if not is_valid(addr):
1088 k = str(item.data(0,32).toString())
1090 self.create_account_menu(position, k, item)
1092 item.setExpanded(not item.isExpanded())
1096 if not multi_select:
1097 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1098 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1099 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1100 if self.wallet.seed:
1101 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1102 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1103 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1104 if addr in self.wallet.imported_keys:
1105 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1107 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1108 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1109 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1110 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1112 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1113 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1115 run_hook('receive_menu', menu, addrs)
1116 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1119 def get_sendable_balance(self):
1120 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1123 def get_payment_sources(self):
1125 return self.pay_from
1127 return self.wallet.get_account_addresses(self.current_account)
1130 def send_from_addresses(self, addrs):
1131 self.set_pay_from( addrs )
1132 self.tabs.setCurrentIndex(1)
1135 def payto(self, addr):
1137 label = self.wallet.labels.get(addr)
1138 m_addr = label + ' <' + addr + '>' if label else addr
1139 self.tabs.setCurrentIndex(1)
1140 self.payto_e.setText(m_addr)
1141 self.amount_e.setFocus()
1144 def delete_contact(self, x):
1145 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1146 self.wallet.delete_contact(x)
1147 self.wallet.set_label(x, None)
1148 self.update_history_tab()
1149 self.update_contacts_tab()
1150 self.update_completions()
1153 def create_contact_menu(self, position):
1154 item = self.contacts_list.itemAt(position)
1157 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1159 addr = unicode(item.text(0))
1160 label = unicode(item.text(1))
1161 is_editable = item.data(0,32).toBool()
1162 payto_addr = item.data(0,33).toString()
1163 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1164 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1165 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1167 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1168 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1170 run_hook('create_contact_menu', menu, item)
1171 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1174 def update_receive_item(self, item):
1175 item.setFont(0, QFont(MONOSPACE_FONT))
1176 address = str(item.data(0,0).toString())
1177 label = self.wallet.labels.get(address,'')
1178 item.setData(1,0,label)
1179 item.setData(0,32, True) # is editable
1181 run_hook('update_receive_item', address, item)
1183 if not self.wallet.is_mine(address): return
1185 c, u = self.wallet.get_addr_balance(address)
1186 balance = self.format_amount(c + u)
1187 item.setData(2,0,balance)
1189 if address in self.wallet.frozen_addresses:
1190 item.setBackgroundColor(0, QColor('lightblue'))
1193 def update_receive_tab(self):
1194 l = self.receive_list
1197 l.setColumnHidden(2, False)
1198 l.setColumnHidden(3, False)
1199 for i,width in enumerate(self.column_widths['receive']):
1200 l.setColumnWidth(i, width)
1202 if self.current_account is None:
1203 account_items = self.wallet.accounts.items()
1204 elif self.current_account != -1:
1205 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1209 for k, account in account_items:
1210 name = self.wallet.get_account_name(k)
1211 c,u = self.wallet.get_account_balance(k)
1212 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1213 l.addTopLevelItem(account_item)
1214 account_item.setExpanded(self.accounts_expanded.get(k, True))
1215 account_item.setData(0, 32, k)
1217 if not self.wallet.is_seeded(k):
1218 icon = QIcon(":icons/key.png")
1219 account_item.setIcon(0, icon)
1221 for is_change in ([0,1]):
1222 name = _("Receiving") if not is_change else _("Change")
1223 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1224 account_item.addChild(seq_item)
1225 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1227 if not is_change: seq_item.setExpanded(True)
1232 for address in account.get_addresses(is_change):
1233 h = self.wallet.history.get(address,[])
1237 if gap > self.wallet.gap_limit:
1242 c, u = self.wallet.get_addr_balance(address)
1243 num_tx = '*' if h == ['*'] else "%d"%len(h)
1244 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1245 self.update_receive_item(item)
1247 item.setBackgroundColor(1, QColor('red'))
1248 if len(h) > 0 and c == -u:
1250 seq_item.insertChild(0,used_item)
1252 used_item.addChild(item)
1254 seq_item.addChild(item)
1257 for k, addr in self.wallet.get_pending_accounts():
1258 name = self.wallet.labels.get(k,'')
1259 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1260 self.update_receive_item(item)
1261 l.addTopLevelItem(account_item)
1262 account_item.setExpanded(True)
1263 account_item.setData(0, 32, k)
1264 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1265 account_item.addChild(item)
1266 self.update_receive_item(item)
1269 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1270 c,u = self.wallet.get_imported_balance()
1271 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1272 l.addTopLevelItem(account_item)
1273 account_item.setExpanded(True)
1274 for address in self.wallet.imported_keys.keys():
1275 item = QTreeWidgetItem( [ address, '', '', ''] )
1276 self.update_receive_item(item)
1277 account_item.addChild(item)
1280 # we use column 1 because column 0 may be hidden
1281 l.setCurrentItem(l.topLevelItem(0),1)
1284 def update_contacts_tab(self):
1285 l = self.contacts_list
1288 for address in self.wallet.addressbook:
1289 label = self.wallet.labels.get(address,'')
1290 n = self.wallet.get_num_tx(address)
1291 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1292 item.setFont(0, QFont(MONOSPACE_FONT))
1293 # 32 = label can be edited (bool)
1294 item.setData(0,32, True)
1296 item.setData(0,33, address)
1297 l.addTopLevelItem(item)
1299 run_hook('update_contacts_tab', l)
1300 l.setCurrentItem(l.topLevelItem(0))
1304 def create_console_tab(self):
1305 from console import Console
1306 self.console = console = Console()
1310 def update_console(self):
1311 console = self.console
1312 console.history = self.config.get("console-history",[])
1313 console.history_index = len(console.history)
1315 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1316 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1318 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1320 def mkfunc(f, method):
1321 return lambda *args: apply( f, (method, args, self.password_dialog ))
1323 if m[0]=='_' or m in ['network','wallet']: continue
1324 methods[m] = mkfunc(c._run, m)
1326 console.updateNamespace(methods)
1329 def change_account(self,s):
1330 if s == _("All accounts"):
1331 self.current_account = None
1333 accounts = self.wallet.get_account_names()
1334 for k, v in accounts.items():
1336 self.current_account = k
1337 self.update_history_tab()
1338 self.update_status()
1339 self.update_receive_tab()
1341 def create_status_bar(self):
1344 sb.setFixedHeight(35)
1345 qtVersion = qVersion()
1347 self.balance_label = QLabel("")
1348 sb.addWidget(self.balance_label)
1350 from version_getter import UpdateLabel
1351 self.updatelabel = UpdateLabel(self.config, sb)
1353 self.account_selector = QComboBox()
1354 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1355 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1356 sb.addPermanentWidget(self.account_selector)
1358 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1359 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1361 self.lock_icon = QIcon()
1362 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1363 sb.addPermanentWidget( self.password_button )
1365 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1366 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1367 sb.addPermanentWidget( self.seed_button )
1368 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1369 sb.addPermanentWidget( self.status_button )
1371 run_hook('create_status_bar', (sb,))
1373 self.setStatusBar(sb)
1376 def update_lock_icon(self):
1377 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1378 self.password_button.setIcon( icon )
1381 def update_buttons_on_seed(self):
1382 if not self.wallet.is_watching_only():
1383 self.seed_button.show()
1384 self.password_button.show()
1385 self.send_button.setText(_("Send"))
1387 self.password_button.hide()
1388 self.seed_button.hide()
1389 self.send_button.setText(_("Create unsigned transaction"))
1392 def change_password_dialog(self):
1393 from password_dialog import PasswordDialog
1394 d = PasswordDialog(self.wallet, self)
1396 self.update_lock_icon()
1399 def new_contact_dialog(self):
1402 d.setWindowTitle(_("New Contact"))
1403 vbox = QVBoxLayout(d)
1404 vbox.addWidget(QLabel(_('New Contact')+':'))
1406 grid = QGridLayout()
1409 grid.addWidget(QLabel(_("Address")), 1, 0)
1410 grid.addWidget(line1, 1, 1)
1411 grid.addWidget(QLabel(_("Name")), 2, 0)
1412 grid.addWidget(line2, 2, 1)
1414 vbox.addLayout(grid)
1415 vbox.addLayout(ok_cancel_buttons(d))
1420 address = str(line1.text())
1421 label = unicode(line2.text())
1423 if not is_valid(address):
1424 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1427 self.wallet.add_contact(address)
1429 self.wallet.set_label(address, label)
1431 self.update_contacts_tab()
1432 self.update_history_tab()
1433 self.update_completions()
1434 self.tabs.setCurrentIndex(3)
1437 def new_account_dialog(self):
1439 dialog = QDialog(self)
1441 dialog.setWindowTitle(_("New Account"))
1443 vbox = QVBoxLayout()
1444 vbox.addWidget(QLabel(_('Account name')+':'))
1447 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1448 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1453 vbox.addLayout(ok_cancel_buttons(dialog))
1454 dialog.setLayout(vbox)
1458 name = str(e.text())
1461 self.wallet.create_pending_account('1', name)
1462 self.update_receive_tab()
1463 self.tabs.setCurrentIndex(2)
1467 def show_master_public_key_old(self):
1468 dialog = QDialog(self)
1470 dialog.setWindowTitle(_("Master Public Key"))
1472 main_text = QTextEdit()
1473 main_text.setText(self.wallet.get_master_public_key())
1474 main_text.setReadOnly(True)
1475 main_text.setMaximumHeight(170)
1476 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1478 ok_button = QPushButton(_("OK"))
1479 ok_button.setDefault(True)
1480 ok_button.clicked.connect(dialog.accept)
1482 main_layout = QGridLayout()
1483 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1485 main_layout.addWidget(main_text, 1, 0)
1486 main_layout.addWidget(qrw, 1, 1 )
1488 vbox = QVBoxLayout()
1489 vbox.addLayout(main_layout)
1490 vbox.addLayout(close_button(dialog))
1491 dialog.setLayout(vbox)
1495 def show_master_public_key(self):
1497 if self.wallet.seed_version == 4:
1498 self.show_master_public_key_old()
1501 dialog = QDialog(self)
1503 dialog.setWindowTitle(_("Master Public Keys"))
1505 chain_text = QTextEdit()
1506 chain_text.setReadOnly(True)
1507 chain_text.setMaximumHeight(170)
1508 chain_qrw = QRCodeWidget()
1510 mpk_text = QTextEdit()
1511 mpk_text.setReadOnly(True)
1512 mpk_text.setMaximumHeight(170)
1513 mpk_qrw = QRCodeWidget()
1515 main_layout = QGridLayout()
1517 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1518 main_layout.addWidget(mpk_text, 1, 1)
1519 main_layout.addWidget(mpk_qrw, 1, 2)
1521 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1522 main_layout.addWidget(chain_text, 2, 1)
1523 main_layout.addWidget(chain_qrw, 2, 2)
1526 c, K, cK = self.wallet.master_public_keys[str(key)]
1527 chain_text.setText(c)
1528 chain_qrw.set_addr(c)
1529 chain_qrw.update_qr()
1534 key_selector = QComboBox()
1535 keys = sorted(self.wallet.master_public_keys.keys())
1536 key_selector.addItems(keys)
1538 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1539 main_layout.addWidget(key_selector, 0, 1)
1540 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1544 vbox = QVBoxLayout()
1545 vbox.addLayout(main_layout)
1546 vbox.addLayout(close_button(dialog))
1548 dialog.setLayout(vbox)
1553 def show_seed_dialog(self, password):
1554 if self.wallet.is_watching_only():
1555 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1558 if self.wallet.seed:
1560 mnemonic = self.wallet.get_mnemonic(password)
1562 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1564 from seed_dialog import SeedDialog
1565 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1569 for k in self.wallet.master_private_keys.keys():
1570 pk = self.wallet.get_master_private_key(k, password)
1572 from seed_dialog import PrivateKeysDialog
1573 d = PrivateKeysDialog(self,l)
1580 def show_qrcode(self, data, title = _("QR code")):
1584 d.setWindowTitle(title)
1585 d.setMinimumSize(270, 300)
1586 vbox = QVBoxLayout()
1587 qrw = QRCodeWidget(data)
1588 vbox.addWidget(qrw, 1)
1589 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1590 hbox = QHBoxLayout()
1593 filename = os.path.join(self.config.path, "qrcode.bmp")
1596 bmp.save_qrcode(qrw.qr, filename)
1597 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1599 def copy_to_clipboard():
1600 bmp.save_qrcode(qrw.qr, filename)
1601 self.app.clipboard().setImage(QImage(filename))
1602 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1604 b = QPushButton(_("Copy"))
1606 b.clicked.connect(copy_to_clipboard)
1608 b = QPushButton(_("Save"))
1610 b.clicked.connect(print_qr)
1612 b = QPushButton(_("Close"))
1614 b.clicked.connect(d.accept)
1617 vbox.addLayout(hbox)
1622 def do_protect(self, func, args):
1623 if self.wallet.use_encryption:
1624 password = self.password_dialog()
1630 if args != (False,):
1631 args = (self,) + args + (password,)
1633 args = (self,password)
1638 def show_private_key(self, address, password):
1639 if not address: return
1641 pk_list = self.wallet.get_private_key(address, password)
1642 except Exception as e:
1643 self.show_message(str(e))
1647 d.setMinimumSize(600, 200)
1649 vbox = QVBoxLayout()
1650 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1651 vbox.addWidget( QLabel(_("Private key") + ':'))
1653 keys.setReadOnly(True)
1654 keys.setText('\n'.join(pk_list))
1655 vbox.addWidget(keys)
1656 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1657 vbox.addLayout(close_button(d))
1663 def do_sign(self, address, message, signature, password):
1664 message = unicode(message.toPlainText())
1665 message = message.encode('utf-8')
1667 sig = self.wallet.sign_message(str(address.text()), message, password)
1668 signature.setText(sig)
1669 except Exception as e:
1670 self.show_message(str(e))
1672 def do_verify(self, address, message, signature):
1673 message = unicode(message.toPlainText())
1674 message = message.encode('utf-8')
1675 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1676 self.show_message(_("Signature verified"))
1678 self.show_message(_("Error: wrong signature"))
1681 def sign_verify_message(self, address=''):
1684 d.setWindowTitle(_('Sign/verify Message'))
1685 d.setMinimumSize(410, 290)
1687 layout = QGridLayout(d)
1689 message_e = QTextEdit()
1690 layout.addWidget(QLabel(_('Message')), 1, 0)
1691 layout.addWidget(message_e, 1, 1)
1692 layout.setRowStretch(2,3)
1694 address_e = QLineEdit()
1695 address_e.setText(address)
1696 layout.addWidget(QLabel(_('Address')), 2, 0)
1697 layout.addWidget(address_e, 2, 1)
1699 signature_e = QTextEdit()
1700 layout.addWidget(QLabel(_('Signature')), 3, 0)
1701 layout.addWidget(signature_e, 3, 1)
1702 layout.setRowStretch(3,1)
1704 hbox = QHBoxLayout()
1706 b = QPushButton(_("Sign"))
1707 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1710 b = QPushButton(_("Verify"))
1711 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1714 b = QPushButton(_("Close"))
1715 b.clicked.connect(d.accept)
1717 layout.addLayout(hbox, 4, 1)
1722 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1724 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1725 message_e.setText(decrypted)
1726 except Exception as e:
1727 self.show_message(str(e))
1730 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1731 message = unicode(message_e.toPlainText())
1732 message = message.encode('utf-8')
1734 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1735 encrypted_e.setText(encrypted)
1736 except Exception as e:
1737 self.show_message(str(e))
1741 def encrypt_message(self, address = ''):
1744 d.setWindowTitle(_('Encrypt/decrypt Message'))
1745 d.setMinimumSize(610, 490)
1747 layout = QGridLayout(d)
1749 message_e = QTextEdit()
1750 layout.addWidget(QLabel(_('Message')), 1, 0)
1751 layout.addWidget(message_e, 1, 1)
1752 layout.setRowStretch(2,3)
1754 pubkey_e = QLineEdit()
1756 pubkey = self.wallet.getpubkeys(address)[0]
1757 pubkey_e.setText(pubkey)
1758 layout.addWidget(QLabel(_('Public key')), 2, 0)
1759 layout.addWidget(pubkey_e, 2, 1)
1761 encrypted_e = QTextEdit()
1762 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1763 layout.addWidget(encrypted_e, 3, 1)
1764 layout.setRowStretch(3,1)
1766 hbox = QHBoxLayout()
1767 b = QPushButton(_("Encrypt"))
1768 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1771 b = QPushButton(_("Decrypt"))
1772 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1775 b = QPushButton(_("Close"))
1776 b.clicked.connect(d.accept)
1779 layout.addLayout(hbox, 4, 1)
1783 def question(self, msg):
1784 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1786 def show_message(self, msg):
1787 QMessageBox.information(self, _('Message'), msg, _('OK'))
1789 def password_dialog(self ):
1792 d.setWindowTitle(_("Enter Password"))
1797 vbox = QVBoxLayout()
1798 msg = _('Please enter your password')
1799 vbox.addWidget(QLabel(msg))
1801 grid = QGridLayout()
1803 grid.addWidget(QLabel(_('Password')), 1, 0)
1804 grid.addWidget(pw, 1, 1)
1805 vbox.addLayout(grid)
1807 vbox.addLayout(ok_cancel_buttons(d))
1810 run_hook('password_dialog', pw, grid, 1)
1811 if not d.exec_(): return
1812 return unicode(pw.text())
1821 def tx_from_text(self, txt):
1822 "json or raw hexadecimal"
1825 tx = Transaction(txt)
1831 tx_dict = json.loads(str(txt))
1832 assert "hex" in tx_dict.keys()
1833 assert "complete" in tx_dict.keys()
1834 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1835 if not tx_dict["complete"]:
1836 assert "input_info" in tx_dict.keys()
1837 input_info = json.loads(tx_dict['input_info'])
1838 tx.add_input_info(input_info)
1843 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1847 def read_tx_from_file(self):
1848 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1852 with open(fileName, "r") as f:
1853 file_content = f.read()
1854 except (ValueError, IOError, os.error), reason:
1855 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1857 return self.tx_from_text(file_content)
1861 def sign_raw_transaction(self, tx, input_info, password):
1862 self.wallet.signrawtransaction(tx, input_info, [], password)
1864 def do_process_from_text(self):
1865 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1868 tx = self.tx_from_text(text)
1870 self.show_transaction(tx)
1872 def do_process_from_file(self):
1873 tx = self.read_tx_from_file()
1875 self.show_transaction(tx)
1877 def do_process_from_txid(self):
1878 from electrum import transaction
1879 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1881 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1883 tx = transaction.Transaction(r)
1885 self.show_transaction(tx)
1887 self.show_message("unknown transaction")
1889 def do_process_from_csvReader(self, csvReader):
1894 for position, row in enumerate(csvReader):
1896 if not is_valid(address):
1897 errors.append((position, address))
1899 amount = Decimal(row[1])
1900 amount = int(100000000*amount)
1901 outputs.append((address, amount))
1902 except (ValueError, IOError, os.error), reason:
1903 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1907 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1908 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1912 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1913 except Exception as e:
1914 self.show_message(str(e))
1917 self.show_transaction(tx)
1919 def do_process_from_csv_file(self):
1920 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1924 with open(fileName, "r") as f:
1925 csvReader = csv.reader(f)
1926 self.do_process_from_csvReader(csvReader)
1927 except (ValueError, IOError, os.error), reason:
1928 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1931 def do_process_from_csv_text(self):
1932 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1933 + _("Format: address, amount. One output per line"), _("Load CSV"))
1936 f = StringIO.StringIO(text)
1937 csvReader = csv.reader(f)
1938 self.do_process_from_csvReader(csvReader)
1943 def do_export_privkeys(self, password):
1944 if not self.wallet.seed:
1945 self.show_message(_("This wallet has no seed"))
1948 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.")))
1951 select_export = _('Select file to export your private keys to')
1952 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1954 with open(fileName, "w+") as csvfile:
1955 transaction = csv.writer(csvfile)
1956 transaction.writerow(["address", "private_key"])
1958 addresses = self.wallet.addresses(True)
1960 for addr in addresses:
1961 pk = "".join(self.wallet.get_private_key(addr, password))
1962 transaction.writerow(["%34s"%addr,pk])
1964 self.show_message(_("Private keys exported."))
1966 except (IOError, os.error), reason:
1967 export_error_label = _("Electrum was unable to produce a private key-export.")
1968 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1970 except Exception as e:
1971 self.show_message(str(e))
1975 def do_import_labels(self):
1976 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1977 if not labelsFile: return
1979 f = open(labelsFile, 'r')
1982 for key, value in json.loads(data).items():
1983 self.wallet.set_label(key, value)
1984 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1985 except (IOError, os.error), reason:
1986 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1989 def do_export_labels(self):
1990 labels = self.wallet.labels
1992 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1994 with open(fileName, 'w+') as f:
1995 json.dump(labels, f)
1996 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1997 except (IOError, os.error), reason:
1998 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2001 def do_export_history(self):
2002 from lite_window import csv_transaction
2003 csv_transaction(self.wallet)
2007 def do_import_privkey(self, password):
2008 if not self.wallet.imported_keys:
2009 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2010 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2011 + _('Are you sure you understand what you are doing?'), 3, 4)
2014 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2017 text = str(text).split()
2022 addr = self.wallet.import_key(key, password)
2023 except Exception as e:
2029 addrlist.append(addr)
2031 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2033 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2034 self.update_receive_tab()
2035 self.update_history_tab()
2038 def settings_dialog(self):
2040 d.setWindowTitle(_('Electrum Settings'))
2042 vbox = QVBoxLayout()
2043 grid = QGridLayout()
2044 grid.setColumnStretch(0,1)
2046 nz_label = QLabel(_('Display zeros') + ':')
2047 grid.addWidget(nz_label, 0, 0)
2048 nz_e = AmountEdit(None,True)
2049 nz_e.setText("%d"% self.num_zeros)
2050 grid.addWidget(nz_e, 0, 1)
2051 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2052 grid.addWidget(HelpButton(msg), 0, 2)
2053 if not self.config.is_modifiable('num_zeros'):
2054 for w in [nz_e, nz_label]: w.setEnabled(False)
2056 lang_label=QLabel(_('Language') + ':')
2057 grid.addWidget(lang_label, 1, 0)
2058 lang_combo = QComboBox()
2059 from electrum.i18n import languages
2060 lang_combo.addItems(languages.values())
2062 index = languages.keys().index(self.config.get("language",''))
2065 lang_combo.setCurrentIndex(index)
2066 grid.addWidget(lang_combo, 1, 1)
2067 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2068 if not self.config.is_modifiable('language'):
2069 for w in [lang_combo, lang_label]: w.setEnabled(False)
2072 fee_label = QLabel(_('Transaction fee') + ':')
2073 grid.addWidget(fee_label, 2, 0)
2074 fee_e = AmountEdit(self.base_unit)
2075 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2076 grid.addWidget(fee_e, 2, 1)
2077 msg = _('Fee per kilobyte of transaction.') + ' ' \
2078 + _('Recommended value') + ': ' + self.format_amount(20000)
2079 grid.addWidget(HelpButton(msg), 2, 2)
2080 if not self.config.is_modifiable('fee_per_kb'):
2081 for w in [fee_e, fee_label]: w.setEnabled(False)
2083 units = ['BTC', 'mBTC']
2084 unit_label = QLabel(_('Base unit') + ':')
2085 grid.addWidget(unit_label, 3, 0)
2086 unit_combo = QComboBox()
2087 unit_combo.addItems(units)
2088 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2089 grid.addWidget(unit_combo, 3, 1)
2090 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2091 + '\n1BTC=1000mBTC.\n' \
2092 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2094 usechange_cb = QCheckBox(_('Use change addresses'))
2095 usechange_cb.setChecked(self.wallet.use_change)
2096 grid.addWidget(usechange_cb, 4, 0)
2097 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2098 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2100 grid.setRowStretch(5,1)
2102 vbox.addLayout(grid)
2103 vbox.addLayout(ok_cancel_buttons(d))
2107 if not d.exec_(): return
2109 fee = unicode(fee_e.text())
2111 fee = self.read_amount(fee)
2113 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2116 self.wallet.set_fee(fee)
2118 nz = unicode(nz_e.text())
2123 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2126 if self.num_zeros != nz:
2128 self.config.set_key('num_zeros', nz, True)
2129 self.update_history_tab()
2130 self.update_receive_tab()
2132 usechange_result = usechange_cb.isChecked()
2133 if self.wallet.use_change != usechange_result:
2134 self.wallet.use_change = usechange_result
2135 self.wallet.storage.put('use_change', self.wallet.use_change)
2137 unit_result = units[unit_combo.currentIndex()]
2138 if self.base_unit() != unit_result:
2139 self.decimal_point = 8 if unit_result == 'BTC' else 5
2140 self.config.set_key('decimal_point', self.decimal_point, True)
2141 self.update_history_tab()
2142 self.update_status()
2144 need_restart = False
2146 lang_request = languages.keys()[lang_combo.currentIndex()]
2147 if lang_request != self.config.get('language'):
2148 self.config.set_key("language", lang_request, True)
2151 run_hook('close_settings_dialog')
2154 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2157 def run_network_dialog(self):
2158 if not self.network:
2160 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2162 def closeEvent(self, event):
2165 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2166 self.save_column_widths()
2167 self.config.set_key("console-history", self.console.history[-50:], True)
2168 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2172 def plugins_dialog(self):
2173 from electrum.plugins import plugins
2176 d.setWindowTitle(_('Electrum Plugins'))
2179 vbox = QVBoxLayout(d)
2182 scroll = QScrollArea()
2183 scroll.setEnabled(True)
2184 scroll.setWidgetResizable(True)
2185 scroll.setMinimumSize(400,250)
2186 vbox.addWidget(scroll)
2190 w.setMinimumHeight(len(plugins)*35)
2192 grid = QGridLayout()
2193 grid.setColumnStretch(0,1)
2196 def do_toggle(cb, p, w):
2199 if w: w.setEnabled(r)
2201 def mk_toggle(cb, p, w):
2202 return lambda: do_toggle(cb,p,w)
2204 for i, p in enumerate(plugins):
2206 cb = QCheckBox(p.fullname())
2207 cb.setDisabled(not p.is_available())
2208 cb.setChecked(p.is_enabled())
2209 grid.addWidget(cb, i, 0)
2210 if p.requires_settings():
2211 w = p.settings_widget(self)
2212 w.setEnabled( p.is_enabled() )
2213 grid.addWidget(w, i, 1)
2216 cb.clicked.connect(mk_toggle(cb,p,w))
2217 grid.addWidget(HelpButton(p.description()), i, 2)
2219 print_msg(_("Error: cannot display plugin"), p)
2220 traceback.print_exc(file=sys.stdout)
2221 grid.setRowStretch(i+1,1)
2223 vbox.addLayout(close_button(d))
2228 def show_account_details(self, k):
2230 d.setWindowTitle(_('Account Details'))
2233 vbox = QVBoxLayout(d)
2234 roots = self.wallet.get_roots(k)
2236 name = self.wallet.get_account_name(k)
2237 label = QLabel('Name: ' + name)
2238 vbox.addWidget(label)
2240 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2241 vbox.addWidget(QLabel('Type: ' + acctype))
2243 label = QLabel('Derivation: ' + k)
2244 vbox.addWidget(label)
2247 # mpk = self.wallet.master_public_keys[root]
2248 # text = QTextEdit()
2249 # text.setReadOnly(True)
2250 # text.setMaximumHeight(120)
2251 # text.setText(repr(mpk))
2252 # vbox.addWidget(text)
2254 vbox.addLayout(close_button(d))