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 open_wallet_action = file_menu.addAction(_("&Open"))
345 open_wallet_action.setShortcut(QKeySequence.Open)
346 open_wallet_action.triggered.connect(self.open_wallet)
348 new_wallet_action = file_menu.addAction(_("&New/Restore"))
349 new_wallet_action.setShortcut(QKeySequence.New)
350 new_wallet_action.triggered.connect(self.new_wallet)
352 wallet_backup = file_menu.addAction(_("&Save Copy"))
353 wallet_backup.setShortcut(QKeySequence.SaveAs)
354 wallet_backup.triggered.connect(self.backup_wallet)
356 quit_item = file_menu.addAction(_("&Quit"))
357 #quit_item.setShortcut(QKeySequence.Quit)
358 quit_item.triggered.connect(self.close)
360 wallet_menu = menubar.addMenu(_("&Wallet"))
362 new_contact = wallet_menu.addAction(_("&New contact"))
363 new_contact.triggered.connect(self.new_contact_dialog)
365 self.new_account = wallet_menu.addAction(_("&New account"))
366 self.new_account.triggered.connect(self.new_account_dialog)
368 wallet_menu.addSeparator()
370 pw = wallet_menu.addAction(_("&Password"))
371 pw.triggered.connect(self.change_password_dialog)
373 show_seed = wallet_menu.addAction(_("&Seed"))
374 show_seed.triggered.connect(self.show_seed_dialog)
376 show_mpk = wallet_menu.addAction(_("&Master Public Key"))
377 show_mpk.triggered.connect(self.show_master_public_key)
379 wallet_menu.addSeparator()
381 labels_menu = wallet_menu.addMenu(_("&Labels"))
382 import_labels = labels_menu.addAction(_("&Import"))
383 import_labels.triggered.connect(self.do_import_labels)
384 export_labels = labels_menu.addAction(_("&Export"))
385 export_labels.triggered.connect(self.do_export_labels)
387 keys_menu = wallet_menu.addMenu(_("&Private keys"))
388 import_keys = keys_menu.addAction(_("&Import"))
389 import_keys.triggered.connect(self.do_import_privkey)
390 export_keys = keys_menu.addAction(_("&Export"))
391 export_keys.triggered.connect(self.do_export_privkeys)
393 ex_history = wallet_menu.addAction(_("&Export History"))
394 ex_history.triggered.connect(self.do_export_history)
398 tools_menu = menubar.addMenu(_("&Tools"))
400 # Settings / Preferences are all reserved keywords in OSX using this as work around
401 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
402 preferences_menu = tools_menu.addAction(preferences_name)
403 #preferences_menu.setShortcut(QKeySequence.Preferences)
404 preferences_menu.triggered.connect(self.settings_dialog)
406 network = tools_menu.addAction(_("&Network"))
407 network.triggered.connect(self.run_network_dialog)
409 plugins_labels = tools_menu.addAction(_("&Plugins"))
410 plugins_labels.triggered.connect(self.plugins_dialog)
412 verifymessage = tools_menu.addAction(_("&Verify message"))
413 verifymessage.triggered.connect(self.verify_message)
415 tools_menu.addSeparator()
417 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
419 csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
420 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
422 csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
423 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
425 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
427 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
428 raw_transaction_file.triggered.connect(self.do_process_from_file)
430 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
431 raw_transaction_text.triggered.connect(self.do_process_from_text)
433 raw_transaction_text = raw_transaction_menu.addAction(_("&From the blockchain"))
434 raw_transaction_text.triggered.connect(self.do_process_from_txid)
437 help_menu = menubar.addMenu(_("&Help"))
438 show_about = help_menu.addAction(_("&About"))
439 show_about.triggered.connect(self.show_about)
440 web_open = help_menu.addAction(_("&Official website"))
441 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
443 help_menu.addSeparator()
444 doc_open = help_menu.addAction(_("&Documentation"))
445 doc_open.setShortcut(QKeySequence.HelpContents)
446 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
447 report_bug = help_menu.addAction(_("&Report Bug"))
448 report_bug.triggered.connect(self.show_report_bug)
450 self.setMenuBar(menubar)
452 def show_about(self):
453 QMessageBox.about(self, "Electrum",
454 _("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."))
456 def show_report_bug(self):
457 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
458 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
461 def notify_transactions(self):
462 if not self.network or not self.network.is_connected():
465 print_error("Notifying GUI")
466 if len(self.network.interface.pending_transactions_for_notifications) > 0:
467 # Combine the transactions if there are more then three
468 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
471 for tx in self.network.interface.pending_transactions_for_notifications:
472 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
476 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
477 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
479 self.network.interface.pending_transactions_for_notifications = []
481 for tx in self.network.interface.pending_transactions_for_notifications:
483 self.network.interface.pending_transactions_for_notifications.remove(tx)
484 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
486 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
488 def notify(self, message):
489 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
493 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
494 def getOpenFileName(self, title, filter = ""):
495 directory = self.config.get('io_dir', os.path.expanduser('~'))
496 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
497 if fileName and directory != os.path.dirname(fileName):
498 self.config.set_key('io_dir', os.path.dirname(fileName), True)
501 def getSaveFileName(self, title, filename, filter = ""):
502 directory = self.config.get('io_dir', os.path.expanduser('~'))
503 path = os.path.join( directory, filename )
504 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
505 if fileName and directory != os.path.dirname(fileName):
506 self.config.set_key('io_dir', os.path.dirname(fileName), True)
510 QMainWindow.close(self)
511 run_hook('close_main_window')
513 def connect_slots(self, sender):
514 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
515 self.previous_payto_e=''
517 def timer_actions(self):
518 if self.need_update.is_set():
520 self.need_update.clear()
521 run_hook('timer_actions')
523 def format_amount(self, x, is_diff=False, whitespaces=False):
524 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
526 def read_amount(self, x):
527 if x in['.', '']: return None
528 p = pow(10, self.decimal_point)
529 return int( p * Decimal(x) )
532 assert self.decimal_point in [5,8]
533 return "BTC" if self.decimal_point == 8 else "mBTC"
536 def update_status(self):
537 if self.network is None or not self.network.is_running():
539 icon = QIcon(":icons/status_disconnected.png")
541 elif self.network.is_connected():
542 if not self.wallet.up_to_date:
543 text = _("Synchronizing...")
544 icon = QIcon(":icons/status_waiting.png")
545 elif self.network.server_lag > 1:
546 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
547 icon = QIcon(":icons/status_lagging.png")
549 c, u = self.wallet.get_account_balance(self.current_account)
550 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
551 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
554 run_hook('set_quote_text', c+u, r)
557 text += " (%s)"%quote
559 self.tray.setToolTip(text)
560 icon = QIcon(":icons/status_connected.png")
562 text = _("Not connected")
563 icon = QIcon(":icons/status_disconnected.png")
565 self.balance_label.setText(text)
566 self.status_button.setIcon( icon )
569 def update_wallet(self):
571 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
572 self.update_history_tab()
573 self.update_receive_tab()
574 self.update_contacts_tab()
575 self.update_completions()
578 def create_history_tab(self):
579 self.history_list = l = MyTreeWidget(self)
581 for i,width in enumerate(self.column_widths['history']):
582 l.setColumnWidth(i, width)
583 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
584 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
585 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
587 l.customContextMenuRequested.connect(self.create_history_menu)
591 def create_history_menu(self, position):
592 self.history_list.selectedIndexes()
593 item = self.history_list.currentItem()
595 tx_hash = str(item.data(0, Qt.UserRole).toString())
596 if not tx_hash: return
598 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
599 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
600 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
601 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
602 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
605 def show_transaction(self, tx):
606 import transaction_dialog
607 d = transaction_dialog.TxDialog(tx, self)
610 def tx_label_clicked(self, item, column):
611 if column==2 and item.isSelected():
613 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
614 self.history_list.editItem( item, column )
615 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
618 def tx_label_changed(self, item, column):
622 tx_hash = str(item.data(0, Qt.UserRole).toString())
623 tx = self.wallet.transactions.get(tx_hash)
624 text = unicode( item.text(2) )
625 self.wallet.set_label(tx_hash, text)
627 item.setForeground(2, QBrush(QColor('black')))
629 text = self.wallet.get_default_label(tx_hash)
630 item.setText(2, text)
631 item.setForeground(2, QBrush(QColor('gray')))
635 def edit_label(self, is_recv):
636 l = self.receive_list if is_recv else self.contacts_list
637 item = l.currentItem()
638 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
639 l.editItem( item, 1 )
640 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
644 def address_label_clicked(self, item, column, l, column_addr, column_label):
645 if column == column_label and item.isSelected():
646 is_editable = item.data(0, 32).toBool()
649 addr = unicode( item.text(column_addr) )
650 label = unicode( item.text(column_label) )
651 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
652 l.editItem( item, column )
653 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
656 def address_label_changed(self, item, column, l, column_addr, column_label):
657 if column == column_label:
658 addr = unicode( item.text(column_addr) )
659 text = unicode( item.text(column_label) )
660 is_editable = item.data(0, 32).toBool()
664 changed = self.wallet.set_label(addr, text)
666 self.update_history_tab()
667 self.update_completions()
669 self.current_item_changed(item)
671 run_hook('item_changed', item, column)
674 def current_item_changed(self, a):
675 run_hook('current_item_changed', a)
679 def update_history_tab(self):
681 self.history_list.clear()
682 for item in self.wallet.get_tx_history(self.current_account):
683 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
684 time_str = _("unknown")
687 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
689 time_str = _("error")
692 time_str = 'unverified'
693 icon = QIcon(":icons/unconfirmed.png")
696 icon = QIcon(":icons/unconfirmed.png")
698 icon = QIcon(":icons/clock%d.png"%conf)
700 icon = QIcon(":icons/confirmed.png")
702 if value is not None:
703 v_str = self.format_amount(value, True, whitespaces=True)
707 balance_str = self.format_amount(balance, whitespaces=True)
710 label, is_default_label = self.wallet.get_label(tx_hash)
712 label = _('Pruned transaction outputs')
713 is_default_label = False
715 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
716 item.setFont(2, QFont(MONOSPACE_FONT))
717 item.setFont(3, QFont(MONOSPACE_FONT))
718 item.setFont(4, QFont(MONOSPACE_FONT))
720 item.setForeground(3, QBrush(QColor("#BC1E1E")))
722 item.setData(0, Qt.UserRole, tx_hash)
723 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
725 item.setForeground(2, QBrush(QColor('grey')))
727 item.setIcon(0, icon)
728 self.history_list.insertTopLevelItem(0,item)
731 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
734 def create_send_tab(self):
739 grid.setColumnMinimumWidth(3,300)
740 grid.setColumnStretch(5,1)
743 self.payto_e = QLineEdit()
744 grid.addWidget(QLabel(_('Pay to')), 1, 0)
745 grid.addWidget(self.payto_e, 1, 1, 1, 3)
747 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)
749 completer = QCompleter()
750 completer.setCaseSensitivity(False)
751 self.payto_e.setCompleter(completer)
752 completer.setModel(self.completions)
754 self.message_e = QLineEdit()
755 grid.addWidget(QLabel(_('Description')), 2, 0)
756 grid.addWidget(self.message_e, 2, 1, 1, 3)
757 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)
759 self.from_label = QLabel(_('From'))
760 grid.addWidget(self.from_label, 3, 0)
761 self.from_list = QTreeWidget(self)
762 self.from_list.setColumnCount(2)
763 self.from_list.setColumnWidth(0, 350)
764 self.from_list.setColumnWidth(1, 50)
765 self.from_list.setHeaderHidden (True)
766 self.from_list.setMaximumHeight(80)
767 grid.addWidget(self.from_list, 3, 1, 1, 3)
768 self.set_pay_from([])
770 self.amount_e = AmountEdit(self.base_unit)
771 grid.addWidget(QLabel(_('Amount')), 4, 0)
772 grid.addWidget(self.amount_e, 4, 1, 1, 2)
773 grid.addWidget(HelpButton(
774 _('Amount to be sent.') + '\n\n' \
775 + _('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.') \
776 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
778 self.fee_e = AmountEdit(self.base_unit)
779 grid.addWidget(QLabel(_('Fee')), 5, 0)
780 grid.addWidget(self.fee_e, 5, 1, 1, 2)
781 grid.addWidget(HelpButton(
782 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
783 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
784 + _('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)
787 self.send_button = EnterButton(_("Send"), self.do_send)
788 grid.addWidget(self.send_button, 6, 1)
790 b = EnterButton(_("Clear"),self.do_clear)
791 grid.addWidget(b, 6, 2)
793 self.payto_sig = QLabel('')
794 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
796 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
797 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
806 def entry_changed( is_fee ):
807 self.funds_error = False
809 if self.amount_e.is_shortcut:
810 self.amount_e.is_shortcut = False
811 sendable = self.get_sendable_balance()
812 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, self.get_payment_sources())
813 fee = self.wallet.estimated_fee(inputs)
815 self.amount_e.setText( self.format_amount(amount) )
816 self.fee_e.setText( self.format_amount( fee ) )
819 amount = self.read_amount(str(self.amount_e.text()))
820 fee = self.read_amount(str(self.fee_e.text()))
822 if not is_fee: fee = None
825 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, self.get_payment_sources())
827 self.fee_e.setText( self.format_amount( fee ) )
830 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
834 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
835 self.funds_error = True
836 text = _( "Not enough funds" )
837 c, u = self.wallet.get_frozen_balance()
838 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
840 self.statusBar().showMessage(text)
841 self.amount_e.setPalette(palette)
842 self.fee_e.setPalette(palette)
844 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
845 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
847 run_hook('create_send_tab', grid)
851 def set_pay_from(self, l):
853 self.from_list.clear()
854 self.from_label.setHidden(len(self.pay_from) == 0)
855 self.from_list.setHidden(len(self.pay_from) == 0)
856 for addr in self.pay_from:
857 c, u = self.wallet.get_addr_balance(addr)
858 balance = self.format_amount(c + u)
859 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
862 def update_completions(self):
864 for addr,label in self.wallet.labels.items():
865 if addr in self.wallet.addressbook:
866 l.append( label + ' <' + addr + '>')
868 run_hook('update_completions', l)
869 self.completions.setStringList(l)
873 return lambda s, *args: s.do_protect(func, args)
878 label = unicode( self.message_e.text() )
879 r = unicode( self.payto_e.text() )
882 # label or alias, with address in brackets
883 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
884 to_address = m.group(2) if m else r
886 if not is_valid(to_address):
887 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
891 amount = self.read_amount(unicode( self.amount_e.text()))
893 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
896 fee = self.read_amount(unicode( self.fee_e.text()))
898 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
901 confirm_amount = self.config.get('confirm_amount', 100000000)
902 if amount >= confirm_amount:
903 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
906 confirm_fee = self.config.get('confirm_fee', 100000)
907 if fee >= confirm_fee:
908 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()}):
911 self.send_tx(to_address, amount, fee, label)
915 def send_tx(self, to_address, amount, fee, label, password):
917 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
918 domain=self.get_payment_sources())
919 except Exception as e:
920 traceback.print_exc(file=sys.stdout)
921 self.show_message(str(e))
924 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
925 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
929 self.wallet.set_label(tx.hash(), label)
932 h = self.wallet.send_tx(tx)
933 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
934 status, msg = self.wallet.receive_tx( h, tx )
936 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
938 self.update_contacts_tab()
940 QMessageBox.warning(self, _('Error'), msg, _('OK'))
943 self.show_transaction(tx)
945 # add recipient to addressbook
946 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
947 self.wallet.addressbook.append(to_address)
952 def set_url(self, url):
953 address, amount, label, message, signature, identity, url = util.parse_url(url)
956 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
957 elif amount: amount = str(Decimal(amount))
960 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
963 self.mini.set_payment_fields(address, amount)
965 if label and self.wallet.labels.get(address) != label:
966 if self.question('Give label "%s" to address %s ?'%(label,address)):
967 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
968 self.wallet.addressbook.append(address)
969 self.wallet.set_label(address, label)
971 run_hook('set_url', url, self.show_message, self.question)
973 self.tabs.setCurrentIndex(1)
974 label = self.wallet.labels.get(address)
975 m_addr = label + ' <'+ address +'>' if label else address
976 self.payto_e.setText(m_addr)
978 self.message_e.setText(message)
980 self.amount_e.setText(amount)
983 self.set_frozen(self.payto_e,True)
984 self.set_frozen(self.amount_e,True)
985 self.set_frozen(self.message_e,True)
986 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
988 self.payto_sig.setVisible(False)
991 self.payto_sig.setVisible(False)
992 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
994 self.set_frozen(e,False)
996 self.set_pay_from([])
999 def set_frozen(self,entry,frozen):
1001 entry.setReadOnly(True)
1002 entry.setFrame(False)
1003 palette = QPalette()
1004 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1005 entry.setPalette(palette)
1007 entry.setReadOnly(False)
1008 entry.setFrame(True)
1009 palette = QPalette()
1010 palette.setColor(entry.backgroundRole(), QColor('white'))
1011 entry.setPalette(palette)
1014 def set_addrs_frozen(self,addrs,freeze):
1016 if not addr: continue
1017 if addr in self.wallet.frozen_addresses and not freeze:
1018 self.wallet.unfreeze(addr)
1019 elif addr not in self.wallet.frozen_addresses and freeze:
1020 self.wallet.freeze(addr)
1021 self.update_receive_tab()
1025 def create_list_tab(self, headers):
1026 "generic tab creation method"
1027 l = MyTreeWidget(self)
1028 l.setColumnCount( len(headers) )
1029 l.setHeaderLabels( headers )
1032 vbox = QVBoxLayout()
1039 vbox.addWidget(buttons)
1041 hbox = QHBoxLayout()
1044 buttons.setLayout(hbox)
1049 def create_receive_tab(self):
1050 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1051 l.setContextMenuPolicy(Qt.CustomContextMenu)
1052 l.customContextMenuRequested.connect(self.create_receive_menu)
1053 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1054 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1055 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1056 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1057 self.receive_list = l
1058 self.receive_buttons_hbox = hbox
1065 def save_column_widths(self):
1066 self.column_widths["receive"] = []
1067 for i in range(self.receive_list.columnCount() -1):
1068 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1070 self.column_widths["history"] = []
1071 for i in range(self.history_list.columnCount() - 1):
1072 self.column_widths["history"].append(self.history_list.columnWidth(i))
1074 self.column_widths["contacts"] = []
1075 for i in range(self.contacts_list.columnCount() - 1):
1076 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1078 self.config.set_key("column_widths_2", self.column_widths, True)
1081 def create_contacts_tab(self):
1082 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1083 l.setContextMenuPolicy(Qt.CustomContextMenu)
1084 l.customContextMenuRequested.connect(self.create_contact_menu)
1085 for i,width in enumerate(self.column_widths['contacts']):
1086 l.setColumnWidth(i, width)
1088 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1089 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1090 self.contacts_list = l
1091 self.contacts_buttons_hbox = hbox
1096 def delete_imported_key(self, addr):
1097 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1098 self.wallet.delete_imported_key(addr)
1099 self.update_receive_tab()
1100 self.update_history_tab()
1102 def edit_account_label(self, k):
1103 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1105 label = unicode(text)
1106 self.wallet.set_label(k,label)
1107 self.update_receive_tab()
1109 def account_set_expanded(self, item, k, b):
1111 self.accounts_expanded[k] = b
1113 def create_account_menu(self, position, k, item):
1115 if item.isExpanded():
1116 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1118 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1119 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1120 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1121 if self.wallet.account_is_pending(k):
1122 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1123 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1125 def delete_pending_account(self, k):
1126 self.wallet.delete_pending_account(k)
1127 self.update_receive_tab()
1129 def create_receive_menu(self, position):
1130 # fixme: this function apparently has a side effect.
1131 # if it is not called the menu pops up several times
1132 #self.receive_list.selectedIndexes()
1134 selected = self.receive_list.selectedItems()
1135 multi_select = len(selected) > 1
1136 addrs = [unicode(item.text(0)) for item in selected]
1137 if not multi_select:
1138 item = self.receive_list.itemAt(position)
1142 if not is_valid(addr):
1143 k = str(item.data(0,32).toString())
1145 self.create_account_menu(position, k, item)
1147 item.setExpanded(not item.isExpanded())
1151 if not multi_select:
1152 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1153 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1154 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1155 if self.wallet.seed:
1156 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1157 menu.addAction(_("Sign message"), lambda: self.sign_message(True,addr))
1158 if addr in self.wallet.imported_keys:
1159 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1161 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1162 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1163 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1164 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1166 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1167 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1169 run_hook('receive_menu', menu, addrs)
1170 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1173 def get_sendable_balance(self):
1174 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1177 def get_payment_sources(self):
1179 return self.pay_from
1181 return self.wallet.get_account_addresses(self.current_account)
1184 def send_from_addresses(self, addrs):
1185 self.set_pay_from( addrs )
1186 self.tabs.setCurrentIndex(1)
1189 def payto(self, addr):
1191 label = self.wallet.labels.get(addr)
1192 m_addr = label + ' <' + addr + '>' if label else addr
1193 self.tabs.setCurrentIndex(1)
1194 self.payto_e.setText(m_addr)
1195 self.amount_e.setFocus()
1198 def delete_contact(self, x):
1199 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1200 self.wallet.delete_contact(x)
1201 self.wallet.set_label(x, None)
1202 self.update_history_tab()
1203 self.update_contacts_tab()
1204 self.update_completions()
1207 def create_contact_menu(self, position):
1208 item = self.contacts_list.itemAt(position)
1211 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1213 addr = unicode(item.text(0))
1214 label = unicode(item.text(1))
1215 is_editable = item.data(0,32).toBool()
1216 payto_addr = item.data(0,33).toString()
1217 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1218 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1219 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1221 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1222 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1224 run_hook('create_contact_menu', menu, item)
1225 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1228 def update_receive_item(self, item):
1229 item.setFont(0, QFont(MONOSPACE_FONT))
1230 address = str(item.data(0,0).toString())
1231 label = self.wallet.labels.get(address,'')
1232 item.setData(1,0,label)
1233 item.setData(0,32, True) # is editable
1235 run_hook('update_receive_item', address, item)
1237 if not self.wallet.is_mine(address): return
1239 c, u = self.wallet.get_addr_balance(address)
1240 balance = self.format_amount(c + u)
1241 item.setData(2,0,balance)
1243 if address in self.wallet.frozen_addresses:
1244 item.setBackgroundColor(0, QColor('lightblue'))
1247 def update_receive_tab(self):
1248 l = self.receive_list
1251 l.setColumnHidden(2, False)
1252 l.setColumnHidden(3, False)
1253 for i,width in enumerate(self.column_widths['receive']):
1254 l.setColumnWidth(i, width)
1256 if self.current_account is None:
1257 account_items = self.wallet.accounts.items()
1258 elif self.current_account != -1:
1259 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1263 for k, account in account_items:
1264 name = self.wallet.get_account_name(k)
1265 c,u = self.wallet.get_account_balance(k)
1266 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1267 l.addTopLevelItem(account_item)
1268 account_item.setExpanded(self.accounts_expanded.get(k, True))
1269 account_item.setData(0, 32, k)
1271 if not self.wallet.is_seeded(k):
1272 icon = QIcon(":icons/key.png")
1273 account_item.setIcon(0, icon)
1275 for is_change in ([0,1]):
1276 name = _("Receiving") if not is_change else _("Change")
1277 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1278 account_item.addChild(seq_item)
1279 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1281 if not is_change: seq_item.setExpanded(True)
1286 for address in account.get_addresses(is_change):
1287 h = self.wallet.history.get(address,[])
1291 if gap > self.wallet.gap_limit:
1296 c, u = self.wallet.get_addr_balance(address)
1297 num_tx = '*' if h == ['*'] else "%d"%len(h)
1298 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1299 self.update_receive_item(item)
1301 item.setBackgroundColor(1, QColor('red'))
1302 if len(h) > 0 and c == -u:
1304 seq_item.insertChild(0,used_item)
1306 used_item.addChild(item)
1308 seq_item.addChild(item)
1311 for k, addr in self.wallet.get_pending_accounts():
1312 name = self.wallet.labels.get(k,'')
1313 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1314 self.update_receive_item(item)
1315 l.addTopLevelItem(account_item)
1316 account_item.setExpanded(True)
1317 account_item.setData(0, 32, k)
1318 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1319 account_item.addChild(item)
1320 self.update_receive_item(item)
1323 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1324 c,u = self.wallet.get_imported_balance()
1325 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1326 l.addTopLevelItem(account_item)
1327 account_item.setExpanded(True)
1328 for address in self.wallet.imported_keys.keys():
1329 item = QTreeWidgetItem( [ address, '', '', ''] )
1330 self.update_receive_item(item)
1331 account_item.addChild(item)
1334 # we use column 1 because column 0 may be hidden
1335 l.setCurrentItem(l.topLevelItem(0),1)
1338 def update_contacts_tab(self):
1339 l = self.contacts_list
1342 for address in self.wallet.addressbook:
1343 label = self.wallet.labels.get(address,'')
1344 n = self.wallet.get_num_tx(address)
1345 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1346 item.setFont(0, QFont(MONOSPACE_FONT))
1347 # 32 = label can be edited (bool)
1348 item.setData(0,32, True)
1350 item.setData(0,33, address)
1351 l.addTopLevelItem(item)
1353 run_hook('update_contacts_tab', l)
1354 l.setCurrentItem(l.topLevelItem(0))
1358 def create_console_tab(self):
1359 from console import Console
1360 self.console = console = Console()
1364 def update_console(self):
1365 console = self.console
1366 console.history = self.config.get("console-history",[])
1367 console.history_index = len(console.history)
1369 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1370 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1372 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1374 def mkfunc(f, method):
1375 return lambda *args: apply( f, (method, args, self.password_dialog ))
1377 if m[0]=='_' or m in ['network','wallet']: continue
1378 methods[m] = mkfunc(c._run, m)
1380 console.updateNamespace(methods)
1383 def change_account(self,s):
1384 if s == _("All accounts"):
1385 self.current_account = None
1387 accounts = self.wallet.get_account_names()
1388 for k, v in accounts.items():
1390 self.current_account = k
1391 self.update_history_tab()
1392 self.update_status()
1393 self.update_receive_tab()
1395 def create_status_bar(self):
1398 sb.setFixedHeight(35)
1399 qtVersion = qVersion()
1401 self.balance_label = QLabel("")
1402 sb.addWidget(self.balance_label)
1404 from version_getter import UpdateLabel
1405 self.updatelabel = UpdateLabel(self.config, sb)
1407 self.account_selector = QComboBox()
1408 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1409 sb.addPermanentWidget(self.account_selector)
1411 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1412 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1414 self.lock_icon = QIcon()
1415 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1416 sb.addPermanentWidget( self.password_button )
1418 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1419 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1420 sb.addPermanentWidget( self.seed_button )
1421 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1422 sb.addPermanentWidget( self.status_button )
1424 run_hook('create_status_bar', (sb,))
1426 self.setStatusBar(sb)
1429 def update_lock_icon(self):
1430 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1431 self.password_button.setIcon( icon )
1434 def update_buttons_on_seed(self):
1435 if not self.wallet.is_watching_only():
1436 self.seed_button.show()
1437 self.password_button.show()
1438 self.send_button.setText(_("Send"))
1440 self.password_button.hide()
1441 self.seed_button.hide()
1442 self.send_button.setText(_("Create unsigned transaction"))
1445 def change_password_dialog(self):
1446 from password_dialog import PasswordDialog
1447 d = PasswordDialog(self.wallet, self)
1449 self.update_lock_icon()
1452 def new_contact_dialog(self):
1455 vbox = QVBoxLayout(d)
1456 vbox.addWidget(QLabel(_('New Contact')+':'))
1458 grid = QGridLayout()
1461 grid.addWidget(QLabel(_("Address")), 1, 0)
1462 grid.addWidget(line1, 1, 1)
1463 grid.addWidget(QLabel(_("Name")), 2, 0)
1464 grid.addWidget(line2, 2, 1)
1466 vbox.addLayout(grid)
1467 vbox.addLayout(ok_cancel_buttons(d))
1472 address = str(line1.text())
1473 label = unicode(line2.text())
1475 if not is_valid(address):
1476 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1479 self.wallet.add_contact(address)
1481 self.wallet.set_label(address, label)
1483 self.update_contacts_tab()
1484 self.update_history_tab()
1485 self.update_completions()
1486 self.tabs.setCurrentIndex(3)
1489 def new_account_dialog(self):
1491 dialog = QDialog(self)
1493 dialog.setWindowTitle(_("New Account"))
1495 vbox = QVBoxLayout()
1496 vbox.addWidget(QLabel(_('Account name')+':'))
1499 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1500 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1505 vbox.addLayout(ok_cancel_buttons(dialog))
1506 dialog.setLayout(vbox)
1510 name = str(e.text())
1513 self.wallet.create_pending_account('1', name)
1514 self.update_receive_tab()
1515 self.tabs.setCurrentIndex(2)
1519 def show_master_public_key_old(self):
1520 dialog = QDialog(self)
1522 dialog.setWindowTitle(_("Master Public Key"))
1524 main_text = QTextEdit()
1525 main_text.setText(self.wallet.get_master_public_key())
1526 main_text.setReadOnly(True)
1527 main_text.setMaximumHeight(170)
1528 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1530 ok_button = QPushButton(_("OK"))
1531 ok_button.setDefault(True)
1532 ok_button.clicked.connect(dialog.accept)
1534 main_layout = QGridLayout()
1535 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1537 main_layout.addWidget(main_text, 1, 0)
1538 main_layout.addWidget(qrw, 1, 1 )
1540 vbox = QVBoxLayout()
1541 vbox.addLayout(main_layout)
1542 vbox.addLayout(close_button(dialog))
1543 dialog.setLayout(vbox)
1547 def show_master_public_key(self):
1549 if self.wallet.seed_version == 4:
1550 self.show_master_public_key_old()
1553 dialog = QDialog(self)
1555 dialog.setWindowTitle(_("Master Public Keys"))
1557 chain_text = QTextEdit()
1558 chain_text.setReadOnly(True)
1559 chain_text.setMaximumHeight(170)
1560 chain_qrw = QRCodeWidget()
1562 mpk_text = QTextEdit()
1563 mpk_text.setReadOnly(True)
1564 mpk_text.setMaximumHeight(170)
1565 mpk_qrw = QRCodeWidget()
1567 main_layout = QGridLayout()
1569 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1570 main_layout.addWidget(mpk_text, 1, 1)
1571 main_layout.addWidget(mpk_qrw, 1, 2)
1573 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1574 main_layout.addWidget(chain_text, 2, 1)
1575 main_layout.addWidget(chain_qrw, 2, 2)
1578 c, K, cK = self.wallet.master_public_keys[str(key)]
1579 chain_text.setText(c)
1580 chain_qrw.set_addr(c)
1581 chain_qrw.update_qr()
1586 key_selector = QComboBox()
1587 keys = sorted(self.wallet.master_public_keys.keys())
1588 key_selector.addItems(keys)
1590 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1591 main_layout.addWidget(key_selector, 0, 1)
1592 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1596 vbox = QVBoxLayout()
1597 vbox.addLayout(main_layout)
1598 vbox.addLayout(close_button(dialog))
1600 dialog.setLayout(vbox)
1605 def show_seed_dialog(self, password):
1606 if self.wallet.is_watching_only():
1607 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1610 if self.wallet.seed:
1612 mnemonic = self.wallet.get_mnemonic(password)
1614 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1616 from seed_dialog import SeedDialog
1617 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1621 for k in self.wallet.master_private_keys.keys():
1622 pk = self.wallet.get_master_private_key(k, password)
1624 from seed_dialog import PrivateKeysDialog
1625 d = PrivateKeysDialog(self,l)
1632 def show_qrcode(self, data, title = _("QR code")):
1636 d.setWindowTitle(title)
1637 d.setMinimumSize(270, 300)
1638 vbox = QVBoxLayout()
1639 qrw = QRCodeWidget(data)
1640 vbox.addWidget(qrw, 1)
1641 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1642 hbox = QHBoxLayout()
1645 filename = os.path.join(self.config.path, "qrcode.bmp")
1648 bmp.save_qrcode(qrw.qr, filename)
1649 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1651 def copy_to_clipboard():
1652 bmp.save_qrcode(qrw.qr, filename)
1653 self.app.clipboard().setImage(QImage(filename))
1654 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1656 b = QPushButton(_("Copy"))
1658 b.clicked.connect(copy_to_clipboard)
1660 b = QPushButton(_("Save"))
1662 b.clicked.connect(print_qr)
1664 b = QPushButton(_("Close"))
1666 b.clicked.connect(d.accept)
1669 vbox.addLayout(hbox)
1674 def do_protect(self, func, args):
1675 if self.wallet.use_encryption:
1676 password = self.password_dialog()
1682 if args != (False,):
1683 args = (self,) + args + (password,)
1685 args = (self,password)
1690 def show_private_key(self, address, password):
1691 if not address: return
1693 pk_list = self.wallet.get_private_key(address, password)
1694 except Exception as e:
1695 self.show_message(str(e))
1699 d.setMinimumSize(600, 200)
1701 vbox = QVBoxLayout()
1702 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1703 vbox.addWidget( QLabel(_("Private key") + ':'))
1705 keys.setReadOnly(True)
1706 keys.setText('\n'.join(pk_list))
1707 vbox.addWidget(keys)
1708 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1709 vbox.addLayout(close_button(d))
1715 def do_sign(self, address, message, signature, password):
1716 message = unicode(message.toPlainText())
1717 message = message.encode('utf-8')
1719 sig = self.wallet.sign_message(str(address.text()), message, password)
1720 signature.setText(sig)
1721 except Exception as e:
1722 self.show_message(str(e))
1724 def do_verify(self, address, message, signature):
1725 message = unicode(message.toPlainText())
1726 message = message.encode('utf-8')
1727 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1728 self.show_message(_("Signature verified"))
1730 self.show_message(_("Error: wrong signature"))
1733 def sign_message(self, sign, address):
1734 if sign and not address: return
1738 d.setWindowTitle(_('Sign Message'))
1740 d.setWindowTitle(_('Verify Message'))
1741 d.setMinimumSize(410, 290)
1743 layout = QGridLayout(d)
1745 sign_address = QLineEdit()
1747 sign_address.setText(address)
1748 layout.addWidget(QLabel(_('Address')), 1, 0)
1749 layout.addWidget(sign_address, 1, 1)
1751 sign_message = QTextEdit()
1752 layout.addWidget(QLabel(_('Message')), 2, 0)
1753 layout.addWidget(sign_message, 2, 1)
1754 layout.setRowStretch(2,3)
1756 sign_signature = QTextEdit()
1757 layout.addWidget(QLabel(_('Signature')), 3, 0)
1758 layout.addWidget(sign_signature, 3, 1)
1759 layout.setRowStretch(3,1)
1761 hbox = QHBoxLayout()
1763 b = QPushButton(_("Sign"))
1765 b = QPushButton(_("Verify"))
1768 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1770 b.clicked.connect(lambda: self.do_verify(sign_address, sign_message, sign_signature))
1771 b = QPushButton(_("Close"))
1772 b.clicked.connect(d.accept)
1774 layout.addLayout(hbox, 4, 1)
1778 def question(self, msg):
1779 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1781 def show_message(self, msg):
1782 QMessageBox.information(self, _('Message'), msg, _('OK'))
1784 def password_dialog(self ):
1791 vbox = QVBoxLayout()
1792 msg = _('Please enter your password')
1793 vbox.addWidget(QLabel(msg))
1795 grid = QGridLayout()
1797 grid.addWidget(QLabel(_('Password')), 1, 0)
1798 grid.addWidget(pw, 1, 1)
1799 vbox.addLayout(grid)
1801 vbox.addLayout(ok_cancel_buttons(d))
1804 run_hook('password_dialog', pw, grid, 1)
1805 if not d.exec_(): return
1806 return unicode(pw.text())
1815 def tx_from_text(self, txt):
1816 "json or raw hexadecimal"
1819 tx = Transaction(txt)
1825 tx_dict = json.loads(str(txt))
1826 assert "hex" in tx_dict.keys()
1827 assert "complete" in tx_dict.keys()
1828 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1829 if not tx_dict["complete"]:
1830 assert "input_info" in tx_dict.keys()
1831 input_info = json.loads(tx_dict['input_info'])
1832 tx.add_input_info(input_info)
1837 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1841 def read_tx_from_file(self):
1842 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1846 with open(fileName, "r") as f:
1847 file_content = f.read()
1848 except (ValueError, IOError, os.error), reason:
1849 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1851 return self.tx_from_text(file_content)
1855 def sign_raw_transaction(self, tx, input_info, password):
1856 self.wallet.signrawtransaction(tx, input_info, [], password)
1858 def do_process_from_text(self):
1859 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1862 tx = self.tx_from_text(text)
1864 self.show_transaction(tx)
1866 def do_process_from_file(self):
1867 tx = self.read_tx_from_file()
1869 self.show_transaction(tx)
1871 def do_process_from_txid(self):
1872 from electrum import transaction
1873 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1875 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1877 tx = transaction.Transaction(r)
1879 self.show_transaction(tx)
1881 self.show_message("unknown transaction")
1883 def do_process_from_csvReader(self, csvReader):
1888 for position, row in enumerate(csvReader):
1890 if not is_valid(address):
1891 errors.append((position, address))
1893 amount = Decimal(row[1])
1894 amount = int(100000000*amount)
1895 outputs.append((address, amount))
1896 except (ValueError, IOError, os.error), reason:
1897 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1901 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1902 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1906 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1907 except Exception as e:
1908 self.show_message(str(e))
1911 self.show_transaction(tx)
1913 def do_process_from_csv_file(self):
1914 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1918 with open(fileName, "r") as f:
1919 csvReader = csv.reader(f)
1920 self.do_process_from_csvReader(csvReader)
1921 except (ValueError, IOError, os.error), reason:
1922 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1925 def do_process_from_csv_text(self):
1926 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1927 + _("Format: address, amount. One output per line"), _("Load CSV"))
1930 f = StringIO.StringIO(text)
1931 csvReader = csv.reader(f)
1932 self.do_process_from_csvReader(csvReader)
1937 def do_export_privkeys(self, password):
1938 if not self.wallet.seed:
1939 self.show_message(_("This wallet has no seed"))
1942 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.")))
1945 select_export = _('Select file to export your private keys to')
1946 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1948 with open(fileName, "w+") as csvfile:
1949 transaction = csv.writer(csvfile)
1950 transaction.writerow(["address", "private_key"])
1952 addresses = self.wallet.addresses(True)
1954 for addr in addresses:
1955 pk = "".join(self.wallet.get_private_key(addr, password))
1956 transaction.writerow(["%34s"%addr,pk])
1958 self.show_message(_("Private keys exported."))
1960 except (IOError, os.error), reason:
1961 export_error_label = _("Electrum was unable to produce a private key-export.")
1962 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1964 except Exception as e:
1965 self.show_message(str(e))
1969 def do_import_labels(self):
1970 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1971 if not labelsFile: return
1973 f = open(labelsFile, 'r')
1976 for key, value in json.loads(data).items():
1977 self.wallet.set_label(key, value)
1978 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1979 except (IOError, os.error), reason:
1980 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1983 def do_export_labels(self):
1984 labels = self.wallet.labels
1986 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1988 with open(fileName, 'w+') as f:
1989 json.dump(labels, f)
1990 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1991 except (IOError, os.error), reason:
1992 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1995 def do_export_history(self):
1996 from lite_window import csv_transaction
1997 csv_transaction(self.wallet)
2001 def do_import_privkey(self, password):
2002 if not self.wallet.imported_keys:
2003 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2004 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2005 + _('Are you sure you understand what you are doing?'), 3, 4)
2008 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2011 text = str(text).split()
2016 addr = self.wallet.import_key(key, password)
2017 except Exception as e:
2023 addrlist.append(addr)
2025 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2027 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2028 self.update_receive_tab()
2029 self.update_history_tab()
2032 def settings_dialog(self):
2034 d.setWindowTitle(_('Electrum Settings'))
2036 vbox = QVBoxLayout()
2037 grid = QGridLayout()
2038 grid.setColumnStretch(0,1)
2040 nz_label = QLabel(_('Display zeros') + ':')
2041 grid.addWidget(nz_label, 0, 0)
2042 nz_e = AmountEdit(None,True)
2043 nz_e.setText("%d"% self.num_zeros)
2044 grid.addWidget(nz_e, 0, 1)
2045 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2046 grid.addWidget(HelpButton(msg), 0, 2)
2047 if not self.config.is_modifiable('num_zeros'):
2048 for w in [nz_e, nz_label]: w.setEnabled(False)
2050 lang_label=QLabel(_('Language') + ':')
2051 grid.addWidget(lang_label, 1, 0)
2052 lang_combo = QComboBox()
2053 from electrum.i18n import languages
2054 lang_combo.addItems(languages.values())
2056 index = languages.keys().index(self.config.get("language",''))
2059 lang_combo.setCurrentIndex(index)
2060 grid.addWidget(lang_combo, 1, 1)
2061 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2062 if not self.config.is_modifiable('language'):
2063 for w in [lang_combo, lang_label]: w.setEnabled(False)
2066 fee_label = QLabel(_('Transaction fee') + ':')
2067 grid.addWidget(fee_label, 2, 0)
2068 fee_e = AmountEdit(self.base_unit)
2069 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2070 grid.addWidget(fee_e, 2, 1)
2071 msg = _('Fee per kilobyte of transaction.') + ' ' \
2072 + _('Recommended value') + ': ' + self.format_amount(20000)
2073 grid.addWidget(HelpButton(msg), 2, 2)
2074 if not self.config.is_modifiable('fee_per_kb'):
2075 for w in [fee_e, fee_label]: w.setEnabled(False)
2077 units = ['BTC', 'mBTC']
2078 unit_label = QLabel(_('Base unit') + ':')
2079 grid.addWidget(unit_label, 3, 0)
2080 unit_combo = QComboBox()
2081 unit_combo.addItems(units)
2082 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2083 grid.addWidget(unit_combo, 3, 1)
2084 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2085 + '\n1BTC=1000mBTC.\n' \
2086 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2088 usechange_cb = QCheckBox(_('Use change addresses'))
2089 usechange_cb.setChecked(self.wallet.use_change)
2090 grid.addWidget(usechange_cb, 4, 0)
2091 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2092 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2094 grid.setRowStretch(5,1)
2096 vbox.addLayout(grid)
2097 vbox.addLayout(ok_cancel_buttons(d))
2101 if not d.exec_(): return
2103 fee = unicode(fee_e.text())
2105 fee = self.read_amount(fee)
2107 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2110 self.wallet.set_fee(fee)
2112 nz = unicode(nz_e.text())
2117 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2120 if self.num_zeros != nz:
2122 self.config.set_key('num_zeros', nz, True)
2123 self.update_history_tab()
2124 self.update_receive_tab()
2126 usechange_result = usechange_cb.isChecked()
2127 if self.wallet.use_change != usechange_result:
2128 self.wallet.use_change = usechange_result
2129 self.wallet.storage.put('use_change', self.wallet.use_change)
2131 unit_result = units[unit_combo.currentIndex()]
2132 if self.base_unit() != unit_result:
2133 self.decimal_point = 8 if unit_result == 'BTC' else 5
2134 self.config.set_key('decimal_point', self.decimal_point, True)
2135 self.update_history_tab()
2136 self.update_status()
2138 need_restart = False
2140 lang_request = languages.keys()[lang_combo.currentIndex()]
2141 if lang_request != self.config.get('language'):
2142 self.config.set_key("language", lang_request, True)
2145 run_hook('close_settings_dialog')
2148 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2151 def run_network_dialog(self):
2152 if not self.network:
2154 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2156 def closeEvent(self, event):
2159 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2160 self.save_column_widths()
2161 self.config.set_key("console-history", self.console.history[-50:], True)
2162 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2166 def verify_message(self):
2167 self.sign_message(False, "")
2169 def plugins_dialog(self):
2170 from electrum.plugins import plugins
2173 d.setWindowTitle(_('Electrum Plugins'))
2176 vbox = QVBoxLayout(d)
2179 scroll = QScrollArea()
2180 scroll.setEnabled(True)
2181 scroll.setWidgetResizable(True)
2182 scroll.setMinimumSize(400,250)
2183 vbox.addWidget(scroll)
2187 w.setMinimumHeight(len(plugins)*35)
2189 grid = QGridLayout()
2190 grid.setColumnStretch(0,1)
2193 def do_toggle(cb, p, w):
2196 if w: w.setEnabled(r)
2198 def mk_toggle(cb, p, w):
2199 return lambda: do_toggle(cb,p,w)
2201 for i, p in enumerate(plugins):
2203 cb = QCheckBox(p.fullname())
2204 cb.setDisabled(not p.is_available())
2205 cb.setChecked(p.is_enabled())
2206 grid.addWidget(cb, i, 0)
2207 if p.requires_settings():
2208 w = p.settings_widget(self)
2209 w.setEnabled( p.is_enabled() )
2210 grid.addWidget(w, i, 1)
2213 cb.clicked.connect(mk_toggle(cb,p,w))
2214 grid.addWidget(HelpButton(p.description()), i, 2)
2216 print_msg(_("Error: cannot display plugin"), p)
2217 traceback.print_exc(file=sys.stdout)
2218 grid.setRowStretch(i+1,1)
2220 vbox.addLayout(close_button(d))
2225 def show_account_details(self, k):
2227 d.setWindowTitle(_('Account Details'))
2230 vbox = QVBoxLayout(d)
2231 roots = self.wallet.get_roots(k)
2233 name = self.wallet.get_account_name(k)
2234 label = QLabel('Name: ' + name)
2235 vbox.addWidget(label)
2237 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2238 vbox.addWidget(QLabel('Type: ' + acctype))
2240 label = QLabel('Derivation: ' + k)
2241 vbox.addWidget(label)
2244 # mpk = self.wallet.master_public_keys[root]
2245 # text = QTextEdit()
2246 # text.setReadOnly(True)
2247 # text.setMaximumHeight(120)
2248 # text.setText(repr(mpk))
2249 # vbox.addWidget(text)
2251 vbox.addLayout(close_button(d))