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 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, self.get_payment_sources())
761 fee = self.wallet.estimated_fee(inputs)
763 self.amount_e.setText( self.format_amount(amount) )
764 self.fee_e.setText( self.format_amount( fee ) )
767 amount = self.read_amount(str(self.amount_e.text()))
768 fee = self.read_amount(str(self.fee_e.text()))
770 if not is_fee: fee = None
773 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, self.get_payment_sources())
775 self.fee_e.setText( self.format_amount( fee ) )
778 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
782 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
783 self.funds_error = True
784 text = _( "Not enough funds" )
785 c, u = self.wallet.get_frozen_balance()
786 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
788 self.statusBar().showMessage(text)
789 self.amount_e.setPalette(palette)
790 self.fee_e.setPalette(palette)
792 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
793 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
795 run_hook('create_send_tab', grid)
799 def set_pay_from(self, l):
801 self.from_list.clear()
802 self.from_label.setHidden(len(self.pay_from) == 0)
803 self.from_list.setHidden(len(self.pay_from) == 0)
804 for addr in self.pay_from:
805 c, u = self.wallet.get_addr_balance(addr)
806 balance = self.format_amount(c + u)
807 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
810 def update_completions(self):
812 for addr,label in self.wallet.labels.items():
813 if addr in self.wallet.addressbook:
814 l.append( label + ' <' + addr + '>')
816 run_hook('update_completions', l)
817 self.completions.setStringList(l)
821 return lambda s, *args: s.do_protect(func, args)
826 label = unicode( self.message_e.text() )
827 r = unicode( self.payto_e.text() )
830 # label or alias, with address in brackets
831 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
832 to_address = m.group(2) if m else r
834 if not is_valid(to_address):
835 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
839 amount = self.read_amount(unicode( self.amount_e.text()))
841 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
844 fee = self.read_amount(unicode( self.fee_e.text()))
846 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
849 confirm_amount = self.config.get('confirm_amount', 100000000)
850 if amount >= confirm_amount:
851 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
854 confirm_fee = self.config.get('confirm_fee', 100000)
855 if fee >= confirm_fee:
856 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()}):
859 self.send_tx(to_address, amount, fee, label)
863 def send_tx(self, to_address, amount, fee, label, password):
865 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
866 domain=self.get_payment_sources())
867 except Exception as e:
868 traceback.print_exc(file=sys.stdout)
869 self.show_message(str(e))
872 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
873 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
877 self.wallet.set_label(tx.hash(), label)
880 h = self.wallet.send_tx(tx)
881 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
882 status, msg = self.wallet.receive_tx( h, tx )
884 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
886 self.update_contacts_tab()
888 QMessageBox.warning(self, _('Error'), msg, _('OK'))
891 self.show_transaction(tx)
893 # add recipient to addressbook
894 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
895 self.wallet.addressbook.append(to_address)
900 def set_url(self, url):
901 address, amount, label, message, signature, identity, url = util.parse_url(url)
904 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
905 elif amount: amount = str(Decimal(amount))
908 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
911 self.mini.set_payment_fields(address, amount)
913 if label and self.wallet.labels.get(address) != label:
914 if self.question('Give label "%s" to address %s ?'%(label,address)):
915 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
916 self.wallet.addressbook.append(address)
917 self.wallet.set_label(address, label)
919 run_hook('set_url', url, self.show_message, self.question)
921 self.tabs.setCurrentIndex(1)
922 label = self.wallet.labels.get(address)
923 m_addr = label + ' <'+ address +'>' if label else address
924 self.payto_e.setText(m_addr)
926 self.message_e.setText(message)
928 self.amount_e.setText(amount)
931 self.set_frozen(self.payto_e,True)
932 self.set_frozen(self.amount_e,True)
933 self.set_frozen(self.message_e,True)
934 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
936 self.payto_sig.setVisible(False)
939 self.payto_sig.setVisible(False)
940 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
942 self.set_frozen(e,False)
944 self.set_pay_from([])
947 def set_frozen(self,entry,frozen):
949 entry.setReadOnly(True)
950 entry.setFrame(False)
952 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
953 entry.setPalette(palette)
955 entry.setReadOnly(False)
958 palette.setColor(entry.backgroundRole(), QColor('white'))
959 entry.setPalette(palette)
962 def set_addrs_frozen(self,addrs,freeze):
964 if not addr: continue
965 if addr in self.wallet.frozen_addresses and not freeze:
966 self.wallet.unfreeze(addr)
967 elif addr not in self.wallet.frozen_addresses and freeze:
968 self.wallet.freeze(addr)
969 self.update_receive_tab()
973 def create_list_tab(self, headers):
974 "generic tab creation method"
975 l = MyTreeWidget(self)
976 l.setColumnCount( len(headers) )
977 l.setHeaderLabels( headers )
987 vbox.addWidget(buttons)
992 buttons.setLayout(hbox)
997 def create_receive_tab(self):
998 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
999 l.setContextMenuPolicy(Qt.CustomContextMenu)
1000 l.customContextMenuRequested.connect(self.create_receive_menu)
1001 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1002 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1003 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1004 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1005 self.receive_list = l
1006 self.receive_buttons_hbox = hbox
1013 def save_column_widths(self):
1014 self.column_widths["receive"] = []
1015 for i in range(self.receive_list.columnCount() -1):
1016 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1018 self.column_widths["history"] = []
1019 for i in range(self.history_list.columnCount() - 1):
1020 self.column_widths["history"].append(self.history_list.columnWidth(i))
1022 self.column_widths["contacts"] = []
1023 for i in range(self.contacts_list.columnCount() - 1):
1024 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1026 self.config.set_key("column_widths_2", self.column_widths, True)
1029 def create_contacts_tab(self):
1030 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1031 l.setContextMenuPolicy(Qt.CustomContextMenu)
1032 l.customContextMenuRequested.connect(self.create_contact_menu)
1033 for i,width in enumerate(self.column_widths['contacts']):
1034 l.setColumnWidth(i, width)
1036 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1037 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1038 self.contacts_list = l
1039 self.contacts_buttons_hbox = hbox
1044 def delete_imported_key(self, addr):
1045 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1046 self.wallet.delete_imported_key(addr)
1047 self.update_receive_tab()
1048 self.update_history_tab()
1050 def edit_account_label(self, k):
1051 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1053 label = unicode(text)
1054 self.wallet.set_label(k,label)
1055 self.update_receive_tab()
1057 def account_set_expanded(self, item, k, b):
1059 self.accounts_expanded[k] = b
1061 def create_account_menu(self, position, k, item):
1063 if item.isExpanded():
1064 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1066 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1067 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1068 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1069 if self.wallet.account_is_pending(k):
1070 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1071 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1073 def delete_pending_account(self, k):
1074 self.wallet.delete_pending_account(k)
1075 self.update_receive_tab()
1077 def create_receive_menu(self, position):
1078 # fixme: this function apparently has a side effect.
1079 # if it is not called the menu pops up several times
1080 #self.receive_list.selectedIndexes()
1082 selected = self.receive_list.selectedItems()
1083 multi_select = len(selected) > 1
1084 addrs = [unicode(item.text(0)) for item in selected]
1085 if not multi_select:
1086 item = self.receive_list.itemAt(position)
1090 if not is_valid(addr):
1091 k = str(item.data(0,32).toString())
1093 self.create_account_menu(position, k, item)
1095 item.setExpanded(not item.isExpanded())
1099 if not multi_select:
1100 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1101 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1102 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1103 if self.wallet.seed:
1104 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1105 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1106 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1107 if addr in self.wallet.imported_keys:
1108 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1110 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1111 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1112 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1113 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1115 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1116 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1118 run_hook('receive_menu', menu, addrs)
1119 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1122 def get_sendable_balance(self):
1123 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1126 def get_payment_sources(self):
1128 return self.pay_from
1130 return self.wallet.get_account_addresses(self.current_account)
1133 def send_from_addresses(self, addrs):
1134 self.set_pay_from( addrs )
1135 self.tabs.setCurrentIndex(1)
1138 def payto(self, addr):
1140 label = self.wallet.labels.get(addr)
1141 m_addr = label + ' <' + addr + '>' if label else addr
1142 self.tabs.setCurrentIndex(1)
1143 self.payto_e.setText(m_addr)
1144 self.amount_e.setFocus()
1147 def delete_contact(self, x):
1148 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1149 self.wallet.delete_contact(x)
1150 self.wallet.set_label(x, None)
1151 self.update_history_tab()
1152 self.update_contacts_tab()
1153 self.update_completions()
1156 def create_contact_menu(self, position):
1157 item = self.contacts_list.itemAt(position)
1160 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1162 addr = unicode(item.text(0))
1163 label = unicode(item.text(1))
1164 is_editable = item.data(0,32).toBool()
1165 payto_addr = item.data(0,33).toString()
1166 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1167 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1168 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1170 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1171 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1173 run_hook('create_contact_menu', menu, item)
1174 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1177 def update_receive_item(self, item):
1178 item.setFont(0, QFont(MONOSPACE_FONT))
1179 address = str(item.data(0,0).toString())
1180 label = self.wallet.labels.get(address,'')
1181 item.setData(1,0,label)
1182 item.setData(0,32, True) # is editable
1184 run_hook('update_receive_item', address, item)
1186 if not self.wallet.is_mine(address): return
1188 c, u = self.wallet.get_addr_balance(address)
1189 balance = self.format_amount(c + u)
1190 item.setData(2,0,balance)
1192 if address in self.wallet.frozen_addresses:
1193 item.setBackgroundColor(0, QColor('lightblue'))
1196 def update_receive_tab(self):
1197 l = self.receive_list
1200 l.setColumnHidden(2, False)
1201 l.setColumnHidden(3, False)
1202 for i,width in enumerate(self.column_widths['receive']):
1203 l.setColumnWidth(i, width)
1205 if self.current_account is None:
1206 account_items = self.wallet.accounts.items()
1207 elif self.current_account != -1:
1208 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1212 for k, account in account_items:
1213 name = self.wallet.get_account_name(k)
1214 c,u = self.wallet.get_account_balance(k)
1215 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1216 l.addTopLevelItem(account_item)
1217 account_item.setExpanded(self.accounts_expanded.get(k, True))
1218 account_item.setData(0, 32, k)
1220 if not self.wallet.is_seeded(k):
1221 icon = QIcon(":icons/key.png")
1222 account_item.setIcon(0, icon)
1224 for is_change in ([0,1]):
1225 name = _("Receiving") if not is_change else _("Change")
1226 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1227 account_item.addChild(seq_item)
1228 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1230 if not is_change: seq_item.setExpanded(True)
1235 for address in account.get_addresses(is_change):
1236 h = self.wallet.history.get(address,[])
1240 if gap > self.wallet.gap_limit:
1245 c, u = self.wallet.get_addr_balance(address)
1246 num_tx = '*' if h == ['*'] else "%d"%len(h)
1247 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1248 self.update_receive_item(item)
1250 item.setBackgroundColor(1, QColor('red'))
1251 if len(h) > 0 and c == -u:
1253 seq_item.insertChild(0,used_item)
1255 used_item.addChild(item)
1257 seq_item.addChild(item)
1260 for k, addr in self.wallet.get_pending_accounts():
1261 name = self.wallet.labels.get(k,'')
1262 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1263 self.update_receive_item(item)
1264 l.addTopLevelItem(account_item)
1265 account_item.setExpanded(True)
1266 account_item.setData(0, 32, k)
1267 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1268 account_item.addChild(item)
1269 self.update_receive_item(item)
1272 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1273 c,u = self.wallet.get_imported_balance()
1274 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1275 l.addTopLevelItem(account_item)
1276 account_item.setExpanded(True)
1277 for address in self.wallet.imported_keys.keys():
1278 item = QTreeWidgetItem( [ address, '', '', ''] )
1279 self.update_receive_item(item)
1280 account_item.addChild(item)
1283 # we use column 1 because column 0 may be hidden
1284 l.setCurrentItem(l.topLevelItem(0),1)
1287 def update_contacts_tab(self):
1288 l = self.contacts_list
1291 for address in self.wallet.addressbook:
1292 label = self.wallet.labels.get(address,'')
1293 n = self.wallet.get_num_tx(address)
1294 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1295 item.setFont(0, QFont(MONOSPACE_FONT))
1296 # 32 = label can be edited (bool)
1297 item.setData(0,32, True)
1299 item.setData(0,33, address)
1300 l.addTopLevelItem(item)
1302 run_hook('update_contacts_tab', l)
1303 l.setCurrentItem(l.topLevelItem(0))
1307 def create_console_tab(self):
1308 from console import Console
1309 self.console = console = Console()
1313 def update_console(self):
1314 console = self.console
1315 console.history = self.config.get("console-history",[])
1316 console.history_index = len(console.history)
1318 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1319 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1321 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1323 def mkfunc(f, method):
1324 return lambda *args: apply( f, (method, args, self.password_dialog ))
1326 if m[0]=='_' or m in ['network','wallet']: continue
1327 methods[m] = mkfunc(c._run, m)
1329 console.updateNamespace(methods)
1332 def change_account(self,s):
1333 if s == _("All accounts"):
1334 self.current_account = None
1336 accounts = self.wallet.get_account_names()
1337 for k, v in accounts.items():
1339 self.current_account = k
1340 self.update_history_tab()
1341 self.update_status()
1342 self.update_receive_tab()
1344 def create_status_bar(self):
1347 sb.setFixedHeight(35)
1348 qtVersion = qVersion()
1350 self.balance_label = QLabel("")
1351 sb.addWidget(self.balance_label)
1353 from version_getter import UpdateLabel
1354 self.updatelabel = UpdateLabel(self.config, sb)
1356 self.account_selector = QComboBox()
1357 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1358 sb.addPermanentWidget(self.account_selector)
1360 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1361 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1363 self.lock_icon = QIcon()
1364 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1365 sb.addPermanentWidget( self.password_button )
1367 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1368 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1369 sb.addPermanentWidget( self.seed_button )
1370 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1371 sb.addPermanentWidget( self.status_button )
1373 run_hook('create_status_bar', (sb,))
1375 self.setStatusBar(sb)
1378 def update_lock_icon(self):
1379 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1380 self.password_button.setIcon( icon )
1383 def update_buttons_on_seed(self):
1384 if not self.wallet.is_watching_only():
1385 self.seed_button.show()
1386 self.password_button.show()
1387 self.send_button.setText(_("Send"))
1389 self.password_button.hide()
1390 self.seed_button.hide()
1391 self.send_button.setText(_("Create unsigned transaction"))
1394 def change_password_dialog(self):
1395 from password_dialog import PasswordDialog
1396 d = PasswordDialog(self.wallet, self)
1398 self.update_lock_icon()
1401 def new_contact_dialog(self):
1404 vbox = QVBoxLayout(d)
1405 vbox.addWidget(QLabel(_('New Contact')+':'))
1407 grid = QGridLayout()
1410 grid.addWidget(QLabel(_("Address")), 1, 0)
1411 grid.addWidget(line1, 1, 1)
1412 grid.addWidget(QLabel(_("Name")), 2, 0)
1413 grid.addWidget(line2, 2, 1)
1415 vbox.addLayout(grid)
1416 vbox.addLayout(ok_cancel_buttons(d))
1421 address = str(line1.text())
1422 label = unicode(line2.text())
1424 if not is_valid(address):
1425 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1428 self.wallet.add_contact(address)
1430 self.wallet.set_label(address, label)
1432 self.update_contacts_tab()
1433 self.update_history_tab()
1434 self.update_completions()
1435 self.tabs.setCurrentIndex(3)
1438 def new_account_dialog(self):
1440 dialog = QDialog(self)
1442 dialog.setWindowTitle(_("New Account"))
1444 vbox = QVBoxLayout()
1445 vbox.addWidget(QLabel(_('Account name')+':'))
1448 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1449 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1454 vbox.addLayout(ok_cancel_buttons(dialog))
1455 dialog.setLayout(vbox)
1459 name = str(e.text())
1462 self.wallet.create_pending_account('1', name)
1463 self.update_receive_tab()
1464 self.tabs.setCurrentIndex(2)
1468 def show_master_public_key_old(self):
1469 dialog = QDialog(self)
1471 dialog.setWindowTitle(_("Master Public Key"))
1473 main_text = QTextEdit()
1474 main_text.setText(self.wallet.get_master_public_key())
1475 main_text.setReadOnly(True)
1476 main_text.setMaximumHeight(170)
1477 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1479 ok_button = QPushButton(_("OK"))
1480 ok_button.setDefault(True)
1481 ok_button.clicked.connect(dialog.accept)
1483 main_layout = QGridLayout()
1484 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1486 main_layout.addWidget(main_text, 1, 0)
1487 main_layout.addWidget(qrw, 1, 1 )
1489 vbox = QVBoxLayout()
1490 vbox.addLayout(main_layout)
1491 vbox.addLayout(close_button(dialog))
1492 dialog.setLayout(vbox)
1496 def show_master_public_key(self):
1498 if self.wallet.seed_version == 4:
1499 self.show_master_public_key_old()
1502 dialog = QDialog(self)
1504 dialog.setWindowTitle(_("Master Public Keys"))
1506 chain_text = QTextEdit()
1507 chain_text.setReadOnly(True)
1508 chain_text.setMaximumHeight(170)
1509 chain_qrw = QRCodeWidget()
1511 mpk_text = QTextEdit()
1512 mpk_text.setReadOnly(True)
1513 mpk_text.setMaximumHeight(170)
1514 mpk_qrw = QRCodeWidget()
1516 main_layout = QGridLayout()
1518 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1519 main_layout.addWidget(mpk_text, 1, 1)
1520 main_layout.addWidget(mpk_qrw, 1, 2)
1522 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1523 main_layout.addWidget(chain_text, 2, 1)
1524 main_layout.addWidget(chain_qrw, 2, 2)
1527 c, K, cK = self.wallet.master_public_keys[str(key)]
1528 chain_text.setText(c)
1529 chain_qrw.set_addr(c)
1530 chain_qrw.update_qr()
1535 key_selector = QComboBox()
1536 keys = sorted(self.wallet.master_public_keys.keys())
1537 key_selector.addItems(keys)
1539 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1540 main_layout.addWidget(key_selector, 0, 1)
1541 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1545 vbox = QVBoxLayout()
1546 vbox.addLayout(main_layout)
1547 vbox.addLayout(close_button(dialog))
1549 dialog.setLayout(vbox)
1554 def show_seed_dialog(self, password):
1555 if self.wallet.is_watching_only():
1556 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1559 if self.wallet.seed:
1561 mnemonic = self.wallet.get_mnemonic(password)
1563 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1565 from seed_dialog import SeedDialog
1566 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1570 for k in self.wallet.master_private_keys.keys():
1571 pk = self.wallet.get_master_private_key(k, password)
1573 from seed_dialog import PrivateKeysDialog
1574 d = PrivateKeysDialog(self,l)
1581 def show_qrcode(self, data, title = _("QR code")):
1585 d.setWindowTitle(title)
1586 d.setMinimumSize(270, 300)
1587 vbox = QVBoxLayout()
1588 qrw = QRCodeWidget(data)
1589 vbox.addWidget(qrw, 1)
1590 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1591 hbox = QHBoxLayout()
1594 filename = os.path.join(self.config.path, "qrcode.bmp")
1597 bmp.save_qrcode(qrw.qr, filename)
1598 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1600 def copy_to_clipboard():
1601 bmp.save_qrcode(qrw.qr, filename)
1602 self.app.clipboard().setImage(QImage(filename))
1603 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1605 b = QPushButton(_("Copy"))
1607 b.clicked.connect(copy_to_clipboard)
1609 b = QPushButton(_("Save"))
1611 b.clicked.connect(print_qr)
1613 b = QPushButton(_("Close"))
1615 b.clicked.connect(d.accept)
1618 vbox.addLayout(hbox)
1623 def do_protect(self, func, args):
1624 if self.wallet.use_encryption:
1625 password = self.password_dialog()
1631 if args != (False,):
1632 args = (self,) + args + (password,)
1634 args = (self,password)
1639 def show_private_key(self, address, password):
1640 if not address: return
1642 pk_list = self.wallet.get_private_key(address, password)
1643 except Exception as e:
1644 self.show_message(str(e))
1648 d.setMinimumSize(600, 200)
1650 vbox = QVBoxLayout()
1651 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1652 vbox.addWidget( QLabel(_("Private key") + ':'))
1654 keys.setReadOnly(True)
1655 keys.setText('\n'.join(pk_list))
1656 vbox.addWidget(keys)
1657 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1658 vbox.addLayout(close_button(d))
1664 def do_sign(self, address, message, signature, password):
1665 message = unicode(message.toPlainText())
1666 message = message.encode('utf-8')
1668 sig = self.wallet.sign_message(str(address.text()), message, password)
1669 signature.setText(sig)
1670 except Exception as e:
1671 self.show_message(str(e))
1673 def do_verify(self, address, message, signature):
1674 message = unicode(message.toPlainText())
1675 message = message.encode('utf-8')
1676 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1677 self.show_message(_("Signature verified"))
1679 self.show_message(_("Error: wrong signature"))
1682 def sign_verify_message(self, address=''):
1685 d.setWindowTitle(_('Sign/verify Message'))
1686 d.setMinimumSize(410, 290)
1688 layout = QGridLayout(d)
1690 message_e = QTextEdit()
1691 layout.addWidget(QLabel(_('Message')), 1, 0)
1692 layout.addWidget(message_e, 1, 1)
1693 layout.setRowStretch(2,3)
1695 address_e = QLineEdit()
1696 address_e.setText(address)
1697 layout.addWidget(QLabel(_('Address')), 2, 0)
1698 layout.addWidget(address_e, 2, 1)
1700 signature_e = QTextEdit()
1701 layout.addWidget(QLabel(_('Signature')), 3, 0)
1702 layout.addWidget(signature_e, 3, 1)
1703 layout.setRowStretch(3,1)
1705 hbox = QHBoxLayout()
1707 b = QPushButton(_("Sign"))
1708 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1711 b = QPushButton(_("Verify"))
1712 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1715 b = QPushButton(_("Close"))
1716 b.clicked.connect(d.accept)
1718 layout.addLayout(hbox, 4, 1)
1723 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1725 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1726 message_e.setText(decrypted)
1727 except Exception as e:
1728 self.show_message(str(e))
1731 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1732 message = unicode(message_e.toPlainText())
1733 message = message.encode('utf-8')
1735 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1736 encrypted_e.setText(encrypted)
1737 except Exception as e:
1738 self.show_message(str(e))
1742 def encrypt_message(self, address = ''):
1745 d.setWindowTitle(_('Encrypt/decrypt Message'))
1746 d.setMinimumSize(610, 490)
1748 layout = QGridLayout(d)
1750 message_e = QTextEdit()
1751 layout.addWidget(QLabel(_('Message')), 1, 0)
1752 layout.addWidget(message_e, 1, 1)
1753 layout.setRowStretch(2,3)
1755 pubkey_e = QLineEdit()
1757 pubkey = self.wallet.getpubkeys(address)[0]
1758 pubkey_e.setText(pubkey)
1759 layout.addWidget(QLabel(_('Public key')), 2, 0)
1760 layout.addWidget(pubkey_e, 2, 1)
1762 encrypted_e = QTextEdit()
1763 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1764 layout.addWidget(encrypted_e, 3, 1)
1765 layout.setRowStretch(3,1)
1767 hbox = QHBoxLayout()
1768 b = QPushButton(_("Encrypt"))
1769 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1772 b = QPushButton(_("Decrypt"))
1773 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1776 b = QPushButton(_("Close"))
1777 b.clicked.connect(d.accept)
1780 layout.addLayout(hbox, 4, 1)
1784 def question(self, msg):
1785 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1787 def show_message(self, msg):
1788 QMessageBox.information(self, _('Message'), msg, _('OK'))
1790 def password_dialog(self ):
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))