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
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
32 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
33 from electrum.plugins import run_hook
37 from electrum.wallet import format_satoshis
38 from electrum import Transaction
39 from electrum import mnemonic
40 from electrum import util, bitcoin, commands, Interface, Wallet
41 from electrum import SimpleConfig, Wallet, WalletStorage
44 from electrum import bmp, pyqrnative
46 from amountedit import AmountEdit
47 from network_dialog import NetworkDialog
48 from qrcodewidget import QRCodeWidget
50 from decimal import Decimal
58 if platform.system() == 'Windows':
59 MONOSPACE_FONT = 'Lucida Console'
60 elif platform.system() == 'Darwin':
61 MONOSPACE_FONT = 'Monaco'
63 MONOSPACE_FONT = 'monospace'
65 from electrum import ELECTRUM_VERSION
75 class StatusBarButton(QPushButton):
76 def __init__(self, icon, tooltip, func):
77 QPushButton.__init__(self, icon, '')
78 self.setToolTip(tooltip)
80 self.setMaximumWidth(25)
81 self.clicked.connect(func)
83 self.setIconSize(QSize(25,25))
85 def keyPressEvent(self, e):
86 if e.key() == QtCore.Qt.Key_Return:
98 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
100 class ElectrumWindow(QMainWindow):
101 def changeEvent(self, event):
102 flags = self.windowFlags();
103 if event and event.type() == QtCore.QEvent.WindowStateChange:
104 if self.windowState() & QtCore.Qt.WindowMinimized:
105 self.build_menu(True)
106 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
107 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
108 # Electrum from closing.
109 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
110 # self.setWindowFlags(flags & ~Qt.ToolTip)
111 elif event.oldState() & QtCore.Qt.WindowMinimized:
112 self.build_menu(False)
113 #self.setWindowFlags(flags | Qt.ToolTip)
115 def build_menu(self, is_hidden = False):
117 if self.isMinimized():
118 m.addAction(_("Show"), self.showNormal)
120 m.addAction(_("Hide"), self.showMinimized)
123 m.addAction(_("Exit Electrum"), self.close)
124 self.tray.setContextMenu(m)
126 def tray_activated(self, reason):
127 if reason == QSystemTrayIcon.DoubleClick:
130 def showNormal(self):
131 self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
133 def __init__(self, config, network):
134 QMainWindow.__init__(self)
137 self.network = network
139 self._close_electrum = False
142 if sys.platform == 'darwin':
143 self.icon = QIcon(":icons/electrum_dark_icon.png")
144 #self.icon = QIcon(":icons/lock.png")
146 self.icon = QIcon(':icons/electrum_light_icon.png')
148 self.tray = QSystemTrayIcon(self.icon, self)
149 self.tray.setToolTip('Electrum')
150 self.tray.activated.connect(self.tray_activated)
154 self.create_status_bar()
156 self.need_update = threading.Event()
158 self.decimal_point = config.get('decimal_point', 8)
159 self.num_zeros = int(config.get('num_zeros',0))
161 set_language(config.get('language'))
163 self.funds_error = False
164 self.completions = QStringListModel()
166 self.tabs = tabs = QTabWidget(self)
167 self.column_widths = self.config.get("column_widths_2", default_column_widths )
168 tabs.addTab(self.create_history_tab(), _('History') )
169 tabs.addTab(self.create_send_tab(), _('Send') )
170 tabs.addTab(self.create_receive_tab(), _('Receive') )
171 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
172 tabs.addTab(self.create_console_tab(), _('Console') )
173 tabs.setMinimumSize(600, 400)
174 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
175 self.setCentralWidget(tabs)
177 g = self.config.get("winpos-qt",[100, 100, 840, 400])
178 self.setGeometry(g[0], g[1], g[2], g[3])
180 self.setWindowIcon(QIcon(":icons/electrum.png"))
183 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
184 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
185 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
186 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
187 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
189 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
190 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
191 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
193 self.history_list.setFocus(True)
197 self.network.register_callback('updated', lambda: self.need_update.set())
198 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
199 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
200 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
201 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
203 # set initial message
204 self.console.showMessage(self.network.banner)
211 self.config.set_key('lite_mode', False, True)
216 self.config.set_key('lite_mode', True, True)
223 if not self.check_qt_version():
224 if self.config.get('lite_mode') is True:
225 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
226 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
227 self.config.set_key('lite_mode', False, True)
233 actuator = lite_window.MiniActuator(self)
235 # Should probably not modify the current path but instead
236 # change the behaviour of rsrc(...)
237 old_path = QDir.currentPath()
238 actuator.load_theme()
240 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
242 driver = lite_window.MiniDriver(self, self.mini)
244 # Reset path back to original value now that loading the GUI
246 QDir.setCurrent(old_path)
248 if self.config.get('lite_mode') is True:
254 def check_qt_version(self):
255 qtVersion = qVersion()
256 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
259 def update_account_selector(self):
261 accounts = self.wallet.get_account_names()
262 self.account_selector.clear()
263 if len(accounts) > 1:
264 self.account_selector.addItems([_("All accounts")] + accounts.values())
265 self.account_selector.setCurrentIndex(0)
266 self.account_selector.show()
268 self.account_selector.hide()
271 def load_wallet(self, wallet):
274 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
275 self.current_account = self.wallet.storage.get("current_account", None)
277 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
278 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
279 self.setWindowTitle( title )
281 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
282 self.notify_transactions()
283 self.update_account_selector()
284 self.new_account.setEnabled(self.wallet.seed_version>4)
285 self.update_lock_icon()
286 self.update_buttons_on_seed()
287 self.update_console()
289 run_hook('load_wallet', wallet)
292 def open_wallet(self):
293 wallet_folder = self.wallet.storage.path
294 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
298 storage = WalletStorage({'wallet_path': filename})
299 if not storage.file_exists:
300 self.show_message("file not found "+ filename)
303 self.wallet.stop_threads()
306 wallet = Wallet(storage)
307 wallet.start_threads(self.network)
309 self.load_wallet(wallet)
313 def backup_wallet(self):
315 path = self.wallet.storage.path
316 wallet_folder = os.path.dirname(path)
317 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
321 new_path = os.path.join(wallet_folder, filename)
324 shutil.copy2(path, new_path)
325 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
326 except (IOError, os.error), reason:
327 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
330 def new_wallet(self):
333 wallet_folder = os.path.dirname(self.wallet.storage.path)
334 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
337 filename = os.path.join(wallet_folder, filename)
339 storage = WalletStorage({'wallet_path': filename})
340 if storage.file_exists:
341 QMessageBox.critical(None, "Error", _("File exists"))
344 wizard = installwizard.InstallWizard(self.config, self.network, storage)
345 wallet = wizard.run()
347 self.load_wallet(wallet)
351 def init_menubar(self):
354 file_menu = menubar.addMenu(_("&File"))
355 open_wallet_action = file_menu.addAction(_("&Open"))
356 open_wallet_action.triggered.connect(self.open_wallet)
358 new_wallet_action = file_menu.addAction(_("&Create/Restore"))
359 new_wallet_action.triggered.connect(self.new_wallet)
361 wallet_backup = file_menu.addAction(_("&Copy"))
362 wallet_backup.triggered.connect(self.backup_wallet)
364 quit_item = file_menu.addAction(_("&Close"))
365 quit_item.triggered.connect(self.close)
367 wallet_menu = menubar.addMenu(_("&Wallet"))
369 new_contact = wallet_menu.addAction(_("&New contact"))
370 new_contact.triggered.connect(self.new_contact_dialog)
372 self.new_account = wallet_menu.addAction(_("&New account"))
373 self.new_account.triggered.connect(self.new_account_dialog)
375 wallet_menu.addSeparator()
377 pw = wallet_menu.addAction(_("&Password"))
378 pw.triggered.connect(self.change_password_dialog)
380 show_seed = wallet_menu.addAction(_("&Seed"))
381 show_seed.triggered.connect(self.show_seed_dialog)
383 show_mpk = wallet_menu.addAction(_("&Master Public Key"))
384 show_mpk.triggered.connect(self.show_master_public_key)
386 wallet_menu.addSeparator()
388 labels_menu = wallet_menu.addMenu(_("&Labels"))
389 import_labels = labels_menu.addAction(_("&Import"))
390 import_labels.triggered.connect(self.do_import_labels)
391 export_labels = labels_menu.addAction(_("&Export"))
392 export_labels.triggered.connect(self.do_export_labels)
394 keys_menu = wallet_menu.addMenu(_("&Private keys"))
395 import_keys = keys_menu.addAction(_("&Import"))
396 import_keys.triggered.connect(self.do_import_privkey)
397 export_keys = keys_menu.addAction(_("&Export"))
398 export_keys.triggered.connect(self.do_export_privkeys)
400 ex_history = wallet_menu.addAction(_("&Export History"))
401 ex_history.triggered.connect(self.do_export_history)
405 tools_menu = menubar.addMenu(_("&Tools"))
407 # Settings / Preferences are all reserved keywords in OSX using this as work around
408 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
409 preferences_menu = tools_menu.addAction(preferences_name)
410 preferences_menu.triggered.connect(self.settings_dialog)
412 network = tools_menu.addAction(_("&Network"))
413 network.triggered.connect(self.run_network_dialog)
415 plugins_labels = tools_menu.addAction(_("&Plugins"))
416 plugins_labels.triggered.connect(self.plugins_dialog)
418 tools_menu.addSeparator()
420 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
422 csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
423 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
425 csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
426 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
428 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
430 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
431 raw_transaction_file.triggered.connect(self.do_process_from_file)
433 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
434 raw_transaction_text.triggered.connect(self.do_process_from_text)
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.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
446 report_bug = help_menu.addAction(_("&Report Bug"))
447 report_bug.triggered.connect(self.show_report_bug)
449 self.setMenuBar(menubar)
451 def show_about(self):
452 QMessageBox.about(self, "Electrum",
453 _("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."))
455 def show_report_bug(self):
456 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
457 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
460 def notify_transactions(self):
461 if not self.network or not self.network.is_connected():
464 print_error("Notifying GUI")
465 if len(self.network.interface.pending_transactions_for_notifications) > 0:
466 # Combine the transactions if there are more then three
467 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
470 for tx in self.network.interface.pending_transactions_for_notifications:
471 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
475 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
476 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
478 self.network.interface.pending_transactions_for_notifications = []
480 for tx in self.network.interface.pending_transactions_for_notifications:
482 self.network.interface.pending_transactions_for_notifications.remove(tx)
483 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
485 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
487 def notify(self, message):
488 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
492 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
493 def getOpenFileName(self, title, filter = ""):
494 directory = self.config.get('io_dir', os.path.expanduser('~'))
495 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
496 if fileName and directory != os.path.dirname(fileName):
497 self.config.set_key('io_dir', os.path.dirname(fileName), True)
500 def getSaveFileName(self, title, filename, filter = ""):
501 directory = self.config.get('io_dir', os.path.expanduser('~'))
502 path = os.path.join( directory, filename )
503 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
504 if fileName and directory != os.path.dirname(fileName):
505 self.config.set_key('io_dir', os.path.dirname(fileName), True)
509 QMainWindow.close(self)
510 run_hook('close_main_window')
512 def connect_slots(self, sender):
513 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
514 self.previous_payto_e=''
516 def timer_actions(self):
517 if self.need_update.is_set():
519 self.need_update.clear()
520 run_hook('timer_actions')
522 def format_amount(self, x, is_diff=False, whitespaces=False):
523 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
525 def read_amount(self, x):
526 if x in['.', '']: return None
527 p = pow(10, self.decimal_point)
528 return int( p * Decimal(x) )
531 assert self.decimal_point in [5,8]
532 return "BTC" if self.decimal_point == 8 else "mBTC"
535 def update_status(self):
536 if self.network is None or not self.network.is_running():
538 icon = QIcon(":icons/status_disconnected.png")
540 elif self.network.is_connected():
541 if not self.wallet.up_to_date:
542 text = _("Synchronizing...")
543 icon = QIcon(":icons/status_waiting.png")
544 elif self.network.server_lag > 1:
545 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
546 icon = QIcon(":icons/status_lagging.png")
548 c, u = self.wallet.get_account_balance(self.current_account)
549 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
550 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
553 run_hook('set_quote_text', c+u, r)
556 text += " (%s)"%quote
558 self.tray.setToolTip(text)
559 icon = QIcon(":icons/status_connected.png")
561 text = _("Not connected")
562 icon = QIcon(":icons/status_disconnected.png")
564 self.balance_label.setText(text)
565 self.status_button.setIcon( icon )
568 def update_wallet(self):
570 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
571 self.update_history_tab()
572 self.update_receive_tab()
573 self.update_contacts_tab()
574 self.update_completions()
577 def create_history_tab(self):
578 self.history_list = l = MyTreeWidget(self)
580 for i,width in enumerate(self.column_widths['history']):
581 l.setColumnWidth(i, width)
582 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
583 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
584 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
586 l.customContextMenuRequested.connect(self.create_history_menu)
590 def create_history_menu(self, position):
591 self.history_list.selectedIndexes()
592 item = self.history_list.currentItem()
594 tx_hash = str(item.data(0, Qt.UserRole).toString())
595 if not tx_hash: return
597 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
598 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
599 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
600 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
603 def show_transaction(self, tx):
604 import transaction_dialog
605 d = transaction_dialog.TxDialog(tx, self)
608 def tx_label_clicked(self, item, column):
609 if column==2 and item.isSelected():
611 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
612 self.history_list.editItem( item, column )
613 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
616 def tx_label_changed(self, item, column):
620 tx_hash = str(item.data(0, Qt.UserRole).toString())
621 tx = self.wallet.transactions.get(tx_hash)
622 text = unicode( item.text(2) )
623 self.wallet.set_label(tx_hash, text)
625 item.setForeground(2, QBrush(QColor('black')))
627 text = self.wallet.get_default_label(tx_hash)
628 item.setText(2, text)
629 item.setForeground(2, QBrush(QColor('gray')))
633 def edit_label(self, is_recv):
634 l = self.receive_list if is_recv else self.contacts_list
635 item = l.currentItem()
636 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
637 l.editItem( item, 1 )
638 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
642 def address_label_clicked(self, item, column, l, column_addr, column_label):
643 if column == column_label and item.isSelected():
644 is_editable = item.data(0, 32).toBool()
647 addr = unicode( item.text(column_addr) )
648 label = unicode( item.text(column_label) )
649 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
650 l.editItem( item, column )
651 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
654 def address_label_changed(self, item, column, l, column_addr, column_label):
655 if column == column_label:
656 addr = unicode( item.text(column_addr) )
657 text = unicode( item.text(column_label) )
658 is_editable = item.data(0, 32).toBool()
662 changed = self.wallet.set_label(addr, text)
664 self.update_history_tab()
665 self.update_completions()
667 self.current_item_changed(item)
669 run_hook('item_changed', item, column)
672 def current_item_changed(self, a):
673 run_hook('current_item_changed', a)
676 self.tabs.emit(SIGNAL('currentChanged(int)'), 1)
680 def update_history_tab(self):
682 self.history_list.clear()
683 for item in self.wallet.get_tx_history(self.current_account):
684 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
685 time_str = _("unknown")
688 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
690 time_str = _("error")
693 time_str = 'unverified'
694 icon = QIcon(":icons/unconfirmed.png")
697 icon = QIcon(":icons/unconfirmed.png")
699 icon = QIcon(":icons/clock%d.png"%conf)
701 icon = QIcon(":icons/confirmed.png")
703 if value is not None:
704 v_str = self.format_amount(value, True, whitespaces=True)
708 balance_str = self.format_amount(balance, whitespaces=True)
711 label, is_default_label = self.wallet.get_label(tx_hash)
713 label = _('Pruned transaction outputs')
714 is_default_label = False
716 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
717 item.setFont(2, QFont(MONOSPACE_FONT))
718 item.setFont(3, QFont(MONOSPACE_FONT))
719 item.setFont(4, QFont(MONOSPACE_FONT))
721 item.setForeground(3, QBrush(QColor("#BC1E1E")))
723 item.setData(0, Qt.UserRole, tx_hash)
724 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
726 item.setForeground(2, QBrush(QColor('grey')))
728 item.setIcon(0, icon)
729 self.history_list.insertTopLevelItem(0,item)
732 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
735 def create_send_tab(self):
740 grid.setColumnMinimumWidth(3,300)
741 grid.setColumnStretch(5,1)
744 self.payto_e = QLineEdit()
745 grid.addWidget(QLabel(_('Pay to')), 1, 0)
746 grid.addWidget(self.payto_e, 1, 1, 1, 3)
748 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)
750 completer = QCompleter()
751 completer.setCaseSensitivity(False)
752 self.payto_e.setCompleter(completer)
753 completer.setModel(self.completions)
755 self.message_e = QLineEdit()
756 grid.addWidget(QLabel(_('Description')), 2, 0)
757 grid.addWidget(self.message_e, 2, 1, 1, 3)
758 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)
761 self.from_label = QLabel(_('From'))
762 grid.addWidget(self.from_label, 3, 0)
763 self.from_list = QTreeWidget(self)
764 self.from_list.setColumnCount(2)
765 self.from_list.setColumnWidth(0, 350)
766 self.from_list.setColumnWidth(1, 50)
767 self.from_list.setHeaderHidden (True)
768 self.from_list.setMaximumHeight(80)
769 grid.addWidget(self.from_list, 3, 1, 1, 3)
770 self.connect(self.tabs, SIGNAL('currentChanged(int)'), lambda: self.update_pay_from_list(grid))
772 self.amount_e = AmountEdit(self.base_unit)
773 grid.addWidget(QLabel(_('Amount')), 4, 0)
774 grid.addWidget(self.amount_e, 4, 1, 1, 2)
775 grid.addWidget(HelpButton(
776 _('Amount to be sent.') + '\n\n' \
777 + _('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.') \
778 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
780 self.fee_e = AmountEdit(self.base_unit)
781 grid.addWidget(QLabel(_('Fee')), 5, 0)
782 grid.addWidget(self.fee_e, 5, 1, 1, 2)
783 grid.addWidget(HelpButton(
784 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
785 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
786 + _('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)
789 self.send_button = EnterButton(_("Send"), self.do_send)
790 grid.addWidget(self.send_button, 6, 1)
792 b = EnterButton(_("Clear"),self.do_clear)
793 grid.addWidget(b, 6, 2)
795 self.payto_sig = QLabel('')
796 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
798 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
799 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
808 def entry_changed( is_fee ):
809 self.funds_error = False
811 if self.amount_e.is_shortcut:
812 self.amount_e.is_shortcut = False
813 sendable = self.get_sendable_balance()
814 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, self.get_payment_sources())
815 fee = self.wallet.estimated_fee(inputs)
817 self.amount_e.setText( self.format_amount(amount) )
818 self.fee_e.setText( self.format_amount( fee ) )
821 amount = self.read_amount(str(self.amount_e.text()))
822 fee = self.read_amount(str(self.fee_e.text()))
824 if not is_fee: fee = None
827 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, self.get_payment_sources())
829 self.fee_e.setText( self.format_amount( fee ) )
832 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
836 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
837 self.funds_error = True
838 text = _( "Not enough funds" )
839 c, u = self.wallet.get_frozen_balance()
840 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
842 self.statusBar().showMessage(text)
843 self.amount_e.setPalette(palette)
844 self.fee_e.setPalette(palette)
846 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
847 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
849 run_hook('create_send_tab', grid)
853 def update_pay_from_list(self, grid):
854 self.from_list.clear()
855 self.from_label.setHidden(len(self.pay_from) == 0)
856 self.from_list.setHidden(len(self.pay_from) == 0)
857 for addr in self.pay_from:
858 c, u = self.wallet.get_addr_balance(addr)
859 balance = self.format_amount(c + u)
860 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
863 def update_completions(self):
865 for addr,label in self.wallet.labels.items():
866 if addr in self.wallet.addressbook:
867 l.append( label + ' <' + addr + '>')
869 run_hook('update_completions', l)
870 self.completions.setStringList(l)
874 return lambda s, *args: s.do_protect(func, args)
879 label = unicode( self.message_e.text() )
880 r = unicode( self.payto_e.text() )
883 # label or alias, with address in brackets
884 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
885 to_address = m.group(2) if m else r
887 if not is_valid(to_address):
888 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
892 amount = self.read_amount(unicode( self.amount_e.text()))
894 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
897 fee = self.read_amount(unicode( self.fee_e.text()))
899 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
902 confirm_amount = self.config.get('confirm_amount', 100000000)
903 if amount >= confirm_amount:
904 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
907 confirm_fee = self.config.get('confirm_fee', 100000)
908 if fee >= confirm_fee:
909 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()}):
912 self.send_tx(to_address, amount, fee, label)
916 def send_tx(self, to_address, amount, fee, label, password):
918 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
919 domain=self.get_payment_sources())
920 except Exception as e:
921 traceback.print_exc(file=sys.stdout)
922 self.show_message(str(e))
925 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
926 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
930 self.wallet.set_label(tx.hash(), label)
933 h = self.wallet.send_tx(tx)
934 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
935 status, msg = self.wallet.receive_tx( h )
937 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
939 self.update_contacts_tab()
941 QMessageBox.warning(self, _('Error'), msg, _('OK'))
944 self.show_transaction(tx)
946 # add recipient to addressbook
947 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
948 self.wallet.addressbook.append(to_address)
953 def set_url(self, url):
954 address, amount, label, message, signature, identity, url = util.parse_url(url)
957 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
958 elif amount: amount = str(Decimal(amount))
961 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
964 self.mini.set_payment_fields(address, amount)
966 if label and self.wallet.labels.get(address) != label:
967 if self.question('Give label "%s" to address %s ?'%(label,address)):
968 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
969 self.wallet.addressbook.append(address)
970 self.wallet.set_label(address, label)
972 run_hook('set_url', url, self.show_message, self.question)
974 self.tabs.setCurrentIndex(1)
975 label = self.wallet.labels.get(address)
976 m_addr = label + ' <'+ address +'>' if label else address
977 self.payto_e.setText(m_addr)
979 self.message_e.setText(message)
981 self.amount_e.setText(amount)
984 self.set_frozen(self.payto_e,True)
985 self.set_frozen(self.amount_e,True)
986 self.set_frozen(self.message_e,True)
987 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
989 self.payto_sig.setVisible(False)
992 self.payto_sig.setVisible(False)
993 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
995 self.set_frozen(e,False)
998 self.tabs.emit(SIGNAL('currentChanged(int)'), 1)
1000 self.update_status()
1002 def set_frozen(self,entry,frozen):
1004 entry.setReadOnly(True)
1005 entry.setFrame(False)
1006 palette = QPalette()
1007 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1008 entry.setPalette(palette)
1010 entry.setReadOnly(False)
1011 entry.setFrame(True)
1012 palette = QPalette()
1013 palette.setColor(entry.backgroundRole(), QColor('white'))
1014 entry.setPalette(palette)
1017 def set_addrs_frozen(self,addrs,freeze):
1019 if not addr: continue
1020 if addr in self.wallet.frozen_addresses and not freeze:
1021 self.wallet.unfreeze(addr)
1022 elif addr not in self.wallet.frozen_addresses and freeze:
1023 self.wallet.freeze(addr)
1024 self.update_receive_tab()
1028 def create_list_tab(self, headers):
1029 "generic tab creation method"
1030 l = MyTreeWidget(self)
1031 l.setColumnCount( len(headers) )
1032 l.setHeaderLabels( headers )
1035 vbox = QVBoxLayout()
1042 vbox.addWidget(buttons)
1044 hbox = QHBoxLayout()
1047 buttons.setLayout(hbox)
1052 def create_receive_tab(self):
1053 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1054 l.setContextMenuPolicy(Qt.CustomContextMenu)
1055 l.customContextMenuRequested.connect(self.create_receive_menu)
1056 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1057 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1058 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1059 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1060 self.receive_list = l
1061 self.receive_buttons_hbox = hbox
1068 def save_column_widths(self):
1069 self.column_widths["receive"] = []
1070 for i in range(self.receive_list.columnCount() -1):
1071 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1073 self.column_widths["history"] = []
1074 for i in range(self.history_list.columnCount() - 1):
1075 self.column_widths["history"].append(self.history_list.columnWidth(i))
1077 self.column_widths["contacts"] = []
1078 for i in range(self.contacts_list.columnCount() - 1):
1079 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1081 self.config.set_key("column_widths_2", self.column_widths, True)
1084 def create_contacts_tab(self):
1085 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1086 l.setContextMenuPolicy(Qt.CustomContextMenu)
1087 l.customContextMenuRequested.connect(self.create_contact_menu)
1088 for i,width in enumerate(self.column_widths['contacts']):
1089 l.setColumnWidth(i, width)
1091 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1092 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1093 self.contacts_list = l
1094 self.contacts_buttons_hbox = hbox
1099 def delete_imported_key(self, addr):
1100 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1101 self.wallet.delete_imported_key(addr)
1102 self.update_receive_tab()
1103 self.update_history_tab()
1105 def edit_account_label(self, k):
1106 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1108 label = unicode(text)
1109 self.wallet.set_label(k,label)
1110 self.update_receive_tab()
1112 def account_set_expanded(self, item, k, b):
1114 self.accounts_expanded[k] = b
1116 def create_account_menu(self, position, k, item):
1118 if item.isExpanded():
1119 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1121 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1122 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1123 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1124 if self.wallet.account_is_pending(k):
1125 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1126 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1128 def delete_pending_account(self, k):
1129 self.wallet.delete_pending_account(k)
1130 self.update_receive_tab()
1132 def create_receive_menu(self, position):
1133 # fixme: this function apparently has a side effect.
1134 # if it is not called the menu pops up several times
1135 #self.receive_list.selectedIndexes()
1137 selected = self.receive_list.selectedItems()
1138 multi_select = len(selected) > 1
1139 addrs = [unicode(item.text(0)) for item in selected]
1140 if not multi_select:
1141 item = self.receive_list.itemAt(position)
1145 if not is_valid(addr):
1146 k = str(item.data(0,32).toString())
1148 self.create_account_menu(position, k, item)
1150 item.setExpanded(not item.isExpanded())
1154 if not multi_select:
1155 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1156 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1157 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1158 if self.wallet.seed:
1159 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1160 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1161 if addr in self.wallet.imported_keys:
1162 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1164 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1165 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1166 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1167 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1169 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1171 run_hook('receive_menu', menu, addrs)
1172 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1175 def get_sendable_balance(self):
1176 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1179 def get_payment_sources(self):
1181 return self.pay_from
1183 return self.wallet.get_account_addresses(self.current_account)
1186 def send_from_addresses(self, addrs):
1187 self.pay_from = addrs[:]
1188 self.tabs.setCurrentIndex(1)
1191 def payto(self, addr):
1193 label = self.wallet.labels.get(addr)
1194 m_addr = label + ' <' + addr + '>' if label else addr
1195 self.tabs.setCurrentIndex(1)
1196 self.payto_e.setText(m_addr)
1197 self.amount_e.setFocus()
1200 def delete_contact(self, x):
1201 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1202 self.wallet.delete_contact(x)
1203 self.wallet.set_label(x, None)
1204 self.update_history_tab()
1205 self.update_contacts_tab()
1206 self.update_completions()
1209 def create_contact_menu(self, position):
1210 item = self.contacts_list.itemAt(position)
1212 addr = unicode(item.text(0))
1213 label = unicode(item.text(1))
1214 is_editable = item.data(0,32).toBool()
1215 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.addChild(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.addLayout(close_button(d))
1714 def do_sign(self, address, message, signature, password):
1715 message = unicode(message.toPlainText())
1716 message = message.encode('utf-8')
1718 sig = self.wallet.sign_message(str(address.text()), message, password)
1719 signature.setText(sig)
1720 except Exception as e:
1721 self.show_message(str(e))
1723 def sign_message(self, address):
1724 if not address: return
1727 d.setWindowTitle(_('Sign Message'))
1728 d.setMinimumSize(410, 290)
1730 tab_widget = QTabWidget()
1732 layout = QGridLayout(tab)
1734 sign_address = QLineEdit()
1736 sign_address.setText(address)
1737 layout.addWidget(QLabel(_('Address')), 1, 0)
1738 layout.addWidget(sign_address, 1, 1)
1740 sign_message = QTextEdit()
1741 layout.addWidget(QLabel(_('Message')), 2, 0)
1742 layout.addWidget(sign_message, 2, 1)
1743 layout.setRowStretch(2,3)
1745 sign_signature = QTextEdit()
1746 layout.addWidget(QLabel(_('Signature')), 3, 0)
1747 layout.addWidget(sign_signature, 3, 1)
1748 layout.setRowStretch(3,1)
1751 hbox = QHBoxLayout()
1752 b = QPushButton(_("Sign"))
1754 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1755 b = QPushButton(_("Close"))
1756 b.clicked.connect(d.accept)
1758 layout.addLayout(hbox, 4, 1)
1759 tab_widget.addTab(tab, _("Sign"))
1763 layout = QGridLayout(tab)
1765 verify_address = QLineEdit()
1766 layout.addWidget(QLabel(_('Address')), 1, 0)
1767 layout.addWidget(verify_address, 1, 1)
1769 verify_message = QTextEdit()
1770 layout.addWidget(QLabel(_('Message')), 2, 0)
1771 layout.addWidget(verify_message, 2, 1)
1772 layout.setRowStretch(2,3)
1774 verify_signature = QTextEdit()
1775 layout.addWidget(QLabel(_('Signature')), 3, 0)
1776 layout.addWidget(verify_signature, 3, 1)
1777 layout.setRowStretch(3,1)
1780 message = unicode(verify_message.toPlainText())
1781 message = message.encode('utf-8')
1782 if bitcoin.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1783 self.show_message(_("Signature verified"))
1785 self.show_message(_("Error: wrong signature"))
1787 hbox = QHBoxLayout()
1788 b = QPushButton(_("Verify"))
1789 b.clicked.connect(do_verify)
1791 b = QPushButton(_("Close"))
1792 b.clicked.connect(d.accept)
1794 layout.addLayout(hbox, 4, 1)
1795 tab_widget.addTab(tab, _("Verify"))
1797 vbox = QVBoxLayout()
1798 vbox.addWidget(tab_widget)
1805 def question(self, msg):
1806 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1808 def show_message(self, msg):
1809 QMessageBox.information(self, _('Message'), msg, _('OK'))
1811 def password_dialog(self ):
1818 vbox = QVBoxLayout()
1819 msg = _('Please enter your password')
1820 vbox.addWidget(QLabel(msg))
1822 grid = QGridLayout()
1824 grid.addWidget(QLabel(_('Password')), 1, 0)
1825 grid.addWidget(pw, 1, 1)
1826 vbox.addLayout(grid)
1828 vbox.addLayout(ok_cancel_buttons(d))
1831 run_hook('password_dialog', pw, grid, 1)
1832 if not d.exec_(): return
1833 return unicode(pw.text())
1842 def tx_from_text(self, txt):
1843 "json or raw hexadecimal"
1846 tx = Transaction(txt)
1852 tx_dict = json.loads(str(txt))
1853 assert "hex" in tx_dict.keys()
1854 assert "complete" in tx_dict.keys()
1855 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1856 if not tx_dict["complete"]:
1857 assert "input_info" in tx_dict.keys()
1858 input_info = json.loads(tx_dict['input_info'])
1859 tx.add_input_info(input_info)
1864 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1868 def read_tx_from_file(self):
1869 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1873 with open(fileName, "r") as f:
1874 file_content = f.read()
1875 except (ValueError, IOError, os.error), reason:
1876 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1878 return self.tx_from_text(file_content)
1882 def sign_raw_transaction(self, tx, input_info, password):
1883 self.wallet.signrawtransaction(tx, input_info, [], password)
1885 def do_process_from_text(self):
1886 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1889 tx = self.tx_from_text(text)
1891 self.show_transaction(tx)
1893 def do_process_from_file(self):
1894 tx = self.read_tx_from_file()
1896 self.show_transaction(tx)
1898 def do_process_from_csvReader(self, csvReader):
1901 for row in csvReader:
1903 amount = Decimal(row[1])
1904 amount = int(100000000*amount)
1905 outputs.append((address, amount))
1906 except (ValueError, IOError, os.error), reason:
1907 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1911 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1912 except Exception as e:
1913 self.show_message(str(e))
1916 self.show_transaction(tx)
1918 def do_process_from_csv_file(self):
1919 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1923 with open(fileName, "r") as f:
1924 csvReader = csv.reader(f)
1925 self.do_process_from_csvReader(csvReader)
1926 except (ValueError, IOError, os.error), reason:
1927 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1930 def do_process_from_csv_text(self):
1931 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1932 + _("Format: address, amount. One output per line"), _("Load CSV"))
1935 f = StringIO.StringIO(text)
1936 csvReader = csv.reader(f)
1937 self.do_process_from_csvReader(csvReader)
1942 def do_export_privkeys(self, password):
1943 if not self.wallet.seed:
1944 self.show_message(_("This wallet has no seed"))
1947 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.")))
1950 select_export = _('Select file to export your private keys to')
1951 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1953 with open(fileName, "w+") as csvfile:
1954 transaction = csv.writer(csvfile)
1955 transaction.writerow(["address", "private_key"])
1957 addresses = self.wallet.addresses(True)
1959 for addr in addresses:
1960 pk = "".join(self.wallet.get_private_key(addr, password))
1961 transaction.writerow(["%34s"%addr,pk])
1963 self.show_message(_("Private keys exported."))
1965 except (IOError, os.error), reason:
1966 export_error_label = _("Electrum was unable to produce a private key-export.")
1967 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1969 except Exception as e:
1970 self.show_message(str(e))
1974 def do_import_labels(self):
1975 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1976 if not labelsFile: return
1978 f = open(labelsFile, 'r')
1981 for key, value in json.loads(data).items():
1982 self.wallet.set_label(key, value)
1983 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1984 except (IOError, os.error), reason:
1985 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1988 def do_export_labels(self):
1989 labels = self.wallet.labels
1991 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1993 with open(fileName, 'w+') as f:
1994 json.dump(labels, f)
1995 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1996 except (IOError, os.error), reason:
1997 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2000 def do_export_history(self):
2001 from lite_window import csv_transaction
2002 csv_transaction(self.wallet)
2006 def do_import_privkey(self, password):
2007 if not self.wallet.imported_keys:
2008 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2009 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2010 + _('Are you sure you understand what you are doing?'), 3, 4)
2013 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2016 text = str(text).split()
2021 addr = self.wallet.import_key(key, password)
2022 except Exception as e:
2028 addrlist.append(addr)
2030 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2032 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2033 self.update_receive_tab()
2034 self.update_history_tab()
2037 def settings_dialog(self):
2039 d.setWindowTitle(_('Electrum Settings'))
2041 vbox = QVBoxLayout()
2042 grid = QGridLayout()
2043 grid.setColumnStretch(0,1)
2045 nz_label = QLabel(_('Display zeros') + ':')
2046 grid.addWidget(nz_label, 0, 0)
2047 nz_e = AmountEdit(None,True)
2048 nz_e.setText("%d"% self.num_zeros)
2049 grid.addWidget(nz_e, 0, 1)
2050 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2051 grid.addWidget(HelpButton(msg), 0, 2)
2052 if not self.config.is_modifiable('num_zeros'):
2053 for w in [nz_e, nz_label]: w.setEnabled(False)
2055 lang_label=QLabel(_('Language') + ':')
2056 grid.addWidget(lang_label, 1, 0)
2057 lang_combo = QComboBox()
2058 from electrum.i18n import languages
2059 lang_combo.addItems(languages.values())
2061 index = languages.keys().index(self.config.get("language",''))
2064 lang_combo.setCurrentIndex(index)
2065 grid.addWidget(lang_combo, 1, 1)
2066 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2067 if not self.config.is_modifiable('language'):
2068 for w in [lang_combo, lang_label]: w.setEnabled(False)
2071 fee_label = QLabel(_('Transaction fee') + ':')
2072 grid.addWidget(fee_label, 2, 0)
2073 fee_e = AmountEdit(self.base_unit)
2074 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2075 grid.addWidget(fee_e, 2, 1)
2076 msg = _('Fee per kilobyte of transaction.') + ' ' \
2077 + _('Recommended value') + ': ' + self.format_amount(20000)
2078 grid.addWidget(HelpButton(msg), 2, 2)
2079 if not self.config.is_modifiable('fee_per_kb'):
2080 for w in [fee_e, fee_label]: w.setEnabled(False)
2082 units = ['BTC', 'mBTC']
2083 unit_label = QLabel(_('Base unit') + ':')
2084 grid.addWidget(unit_label, 3, 0)
2085 unit_combo = QComboBox()
2086 unit_combo.addItems(units)
2087 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2088 grid.addWidget(unit_combo, 3, 1)
2089 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2090 + '\n1BTC=1000mBTC.\n' \
2091 + _(' This settings affects the fields in the Send tab')+' '), 3, 2)
2093 usechange_cb = QCheckBox(_('Use change addresses'))
2094 usechange_cb.setChecked(self.wallet.use_change)
2095 grid.addWidget(usechange_cb, 4, 0)
2096 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2097 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2099 grid.setRowStretch(5,1)
2101 vbox.addLayout(grid)
2102 vbox.addLayout(ok_cancel_buttons(d))
2106 if not d.exec_(): return
2108 fee = unicode(fee_e.text())
2110 fee = self.read_amount(fee)
2112 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2115 self.wallet.set_fee(fee)
2117 nz = unicode(nz_e.text())
2122 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2125 if self.num_zeros != nz:
2127 self.config.set_key('num_zeros', nz, True)
2128 self.update_history_tab()
2129 self.update_receive_tab()
2131 usechange_result = usechange_cb.isChecked()
2132 if self.wallet.use_change != usechange_result:
2133 self.wallet.use_change = usechange_result
2134 self.wallet.storage.put('use_change', self.wallet.use_change)
2136 unit_result = units[unit_combo.currentIndex()]
2137 if self.base_unit() != unit_result:
2138 self.decimal_point = 8 if unit_result == 'BTC' else 5
2139 self.config.set_key('decimal_point', self.decimal_point, True)
2140 self.update_history_tab()
2141 self.update_status()
2143 need_restart = False
2145 lang_request = languages.keys()[lang_combo.currentIndex()]
2146 if lang_request != self.config.get('language'):
2147 self.config.set_key("language", lang_request, True)
2150 run_hook('close_settings_dialog')
2153 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2156 def run_network_dialog(self):
2157 if not self.network:
2159 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2161 def closeEvent(self, event):
2164 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2165 self.save_column_widths()
2166 self.config.set_key("console-history", self.console.history[-50:], True)
2167 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))