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+Q"), self, self.close)
185 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
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 for i in range(tabs.count()):
190 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
192 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
193 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
194 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
196 self.history_list.setFocus(True)
200 self.network.register_callback('updated', lambda: self.need_update.set())
201 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
202 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
203 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
204 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
206 # set initial message
207 self.console.showMessage(self.network.banner)
214 self.config.set_key('lite_mode', False, True)
219 self.config.set_key('lite_mode', True, True)
226 if not self.check_qt_version():
227 if self.config.get('lite_mode') is True:
228 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
229 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
230 self.config.set_key('lite_mode', False, True)
236 actuator = lite_window.MiniActuator(self)
238 # Should probably not modify the current path but instead
239 # change the behaviour of rsrc(...)
240 old_path = QDir.currentPath()
241 actuator.load_theme()
243 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
245 driver = lite_window.MiniDriver(self, self.mini)
247 # Reset path back to original value now that loading the GUI
249 QDir.setCurrent(old_path)
251 if self.config.get('lite_mode') is True:
257 def check_qt_version(self):
258 qtVersion = qVersion()
259 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
262 def update_account_selector(self):
264 accounts = self.wallet.get_account_names()
265 self.account_selector.clear()
266 if len(accounts) > 1:
267 self.account_selector.addItems([_("All accounts")] + accounts.values())
268 self.account_selector.setCurrentIndex(0)
269 self.account_selector.show()
271 self.account_selector.hide()
274 def load_wallet(self, wallet):
277 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
278 self.current_account = self.wallet.storage.get("current_account", None)
280 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
281 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
282 self.setWindowTitle( title )
284 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
285 self.notify_transactions()
286 self.update_account_selector()
287 self.new_account.setEnabled(self.wallet.seed_version>4)
288 self.update_lock_icon()
289 self.update_buttons_on_seed()
290 self.update_console()
292 run_hook('load_wallet', wallet)
295 def open_wallet(self):
296 wallet_folder = self.wallet.storage.path
297 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
301 storage = WalletStorage({'wallet_path': filename})
302 if not storage.file_exists:
303 self.show_message("file not found "+ filename)
306 self.wallet.stop_threads()
309 wallet = Wallet(storage)
310 wallet.start_threads(self.network)
312 self.load_wallet(wallet)
316 def backup_wallet(self):
318 path = self.wallet.storage.path
319 wallet_folder = os.path.dirname(path)
320 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
324 new_path = os.path.join(wallet_folder, filename)
327 shutil.copy2(path, new_path)
328 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
329 except (IOError, os.error), reason:
330 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
333 def new_wallet(self):
336 wallet_folder = os.path.dirname(self.wallet.storage.path)
337 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
340 filename = os.path.join(wallet_folder, filename)
342 storage = WalletStorage({'wallet_path': filename})
343 if storage.file_exists:
344 QMessageBox.critical(None, "Error", _("File exists"))
347 wizard = installwizard.InstallWizard(self.config, self.network, storage)
348 wallet = wizard.run()
350 self.load_wallet(wallet)
354 def init_menubar(self):
357 file_menu = menubar.addMenu(_("&File"))
358 open_wallet_action = file_menu.addAction(_("&Open"))
359 open_wallet_action.setShortcut(QKeySequence.Open)
360 open_wallet_action.triggered.connect(self.open_wallet)
362 new_wallet_action = file_menu.addAction(_("&New/Restore"))
363 new_wallet_action.setShortcut(QKeySequence.New)
364 new_wallet_action.triggered.connect(self.new_wallet)
366 wallet_backup = file_menu.addAction(_("&Save Copy"))
367 wallet_backup.setShortcut(QKeySequence.SaveAs)
368 wallet_backup.triggered.connect(self.backup_wallet)
370 quit_item = file_menu.addAction(_("&Quit"))
371 #quit_item.setShortcut(QKeySequence.Quit)
372 quit_item.triggered.connect(self.close)
374 wallet_menu = menubar.addMenu(_("&Wallet"))
376 new_contact = wallet_menu.addAction(_("&New contact"))
377 new_contact.triggered.connect(self.new_contact_dialog)
379 self.new_account = wallet_menu.addAction(_("&New account"))
380 self.new_account.triggered.connect(self.new_account_dialog)
382 wallet_menu.addSeparator()
384 pw = wallet_menu.addAction(_("&Password"))
385 pw.triggered.connect(self.change_password_dialog)
387 show_seed = wallet_menu.addAction(_("&Seed"))
388 show_seed.triggered.connect(self.show_seed_dialog)
390 show_mpk = wallet_menu.addAction(_("&Master Public Key"))
391 show_mpk.triggered.connect(self.show_master_public_key)
393 wallet_menu.addSeparator()
395 labels_menu = wallet_menu.addMenu(_("&Labels"))
396 import_labels = labels_menu.addAction(_("&Import"))
397 import_labels.triggered.connect(self.do_import_labels)
398 export_labels = labels_menu.addAction(_("&Export"))
399 export_labels.triggered.connect(self.do_export_labels)
401 keys_menu = wallet_menu.addMenu(_("&Private keys"))
402 import_keys = keys_menu.addAction(_("&Import"))
403 import_keys.triggered.connect(self.do_import_privkey)
404 export_keys = keys_menu.addAction(_("&Export"))
405 export_keys.triggered.connect(self.do_export_privkeys)
407 ex_history = wallet_menu.addAction(_("&Export History"))
408 ex_history.triggered.connect(self.do_export_history)
412 tools_menu = menubar.addMenu(_("&Tools"))
414 # Settings / Preferences are all reserved keywords in OSX using this as work around
415 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
416 preferences_menu = tools_menu.addAction(preferences_name)
417 #preferences_menu.setShortcut(QKeySequence.Preferences)
418 preferences_menu.triggered.connect(self.settings_dialog)
420 network = tools_menu.addAction(_("&Network"))
421 network.triggered.connect(self.run_network_dialog)
423 plugins_labels = tools_menu.addAction(_("&Plugins"))
424 plugins_labels.triggered.connect(self.plugins_dialog)
426 tools_menu.addSeparator()
428 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
430 csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
431 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
433 csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
434 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
436 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
438 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
439 raw_transaction_file.triggered.connect(self.do_process_from_file)
441 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
442 raw_transaction_text.triggered.connect(self.do_process_from_text)
445 help_menu = menubar.addMenu(_("&Help"))
446 show_about = help_menu.addAction(_("&About"))
447 show_about.triggered.connect(self.show_about)
448 web_open = help_menu.addAction(_("&Official website"))
449 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
451 help_menu.addSeparator()
452 doc_open = help_menu.addAction(_("&Documentation"))
453 doc_open.setShortcut(QKeySequence.HelpContents)
454 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
455 report_bug = help_menu.addAction(_("&Report Bug"))
456 report_bug.triggered.connect(self.show_report_bug)
458 self.setMenuBar(menubar)
460 def show_about(self):
461 QMessageBox.about(self, "Electrum",
462 _("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."))
464 def show_report_bug(self):
465 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
466 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
469 def notify_transactions(self):
470 if not self.network or not self.network.is_connected():
473 print_error("Notifying GUI")
474 if len(self.network.interface.pending_transactions_for_notifications) > 0:
475 # Combine the transactions if there are more then three
476 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
479 for tx in self.network.interface.pending_transactions_for_notifications:
480 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
484 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
485 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
487 self.network.interface.pending_transactions_for_notifications = []
489 for tx in self.network.interface.pending_transactions_for_notifications:
491 self.network.interface.pending_transactions_for_notifications.remove(tx)
492 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
494 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
496 def notify(self, message):
497 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
501 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
502 def getOpenFileName(self, title, filter = ""):
503 directory = self.config.get('io_dir', os.path.expanduser('~'))
504 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
505 if fileName and directory != os.path.dirname(fileName):
506 self.config.set_key('io_dir', os.path.dirname(fileName), True)
509 def getSaveFileName(self, title, filename, filter = ""):
510 directory = self.config.get('io_dir', os.path.expanduser('~'))
511 path = os.path.join( directory, filename )
512 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
513 if fileName and directory != os.path.dirname(fileName):
514 self.config.set_key('io_dir', os.path.dirname(fileName), True)
518 QMainWindow.close(self)
519 run_hook('close_main_window')
521 def connect_slots(self, sender):
522 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
523 self.previous_payto_e=''
525 def timer_actions(self):
526 if self.need_update.is_set():
528 self.need_update.clear()
529 run_hook('timer_actions')
531 def format_amount(self, x, is_diff=False, whitespaces=False):
532 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
534 def read_amount(self, x):
535 if x in['.', '']: return None
536 p = pow(10, self.decimal_point)
537 return int( p * Decimal(x) )
540 assert self.decimal_point in [5,8]
541 return "BTC" if self.decimal_point == 8 else "mBTC"
544 def update_status(self):
545 if self.network is None or not self.network.is_running():
547 icon = QIcon(":icons/status_disconnected.png")
549 elif self.network.is_connected():
550 if not self.wallet.up_to_date:
551 text = _("Synchronizing...")
552 icon = QIcon(":icons/status_waiting.png")
553 elif self.network.server_lag > 1:
554 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
555 icon = QIcon(":icons/status_lagging.png")
557 c, u = self.wallet.get_account_balance(self.current_account)
558 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
559 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
562 run_hook('set_quote_text', c+u, r)
565 text += " (%s)"%quote
567 self.tray.setToolTip(text)
568 icon = QIcon(":icons/status_connected.png")
570 text = _("Not connected")
571 icon = QIcon(":icons/status_disconnected.png")
573 self.balance_label.setText(text)
574 self.status_button.setIcon( icon )
577 def update_wallet(self):
579 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
580 self.update_history_tab()
581 self.update_receive_tab()
582 self.update_contacts_tab()
583 self.update_completions()
586 def create_history_tab(self):
587 self.history_list = l = MyTreeWidget(self)
589 for i,width in enumerate(self.column_widths['history']):
590 l.setColumnWidth(i, width)
591 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
592 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
593 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
595 l.customContextMenuRequested.connect(self.create_history_menu)
599 def create_history_menu(self, position):
600 self.history_list.selectedIndexes()
601 item = self.history_list.currentItem()
603 tx_hash = str(item.data(0, Qt.UserRole).toString())
604 if not tx_hash: return
606 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
607 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
608 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
609 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
612 def show_transaction(self, tx):
613 import transaction_dialog
614 d = transaction_dialog.TxDialog(tx, self)
617 def tx_label_clicked(self, item, column):
618 if column==2 and item.isSelected():
620 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
621 self.history_list.editItem( item, column )
622 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
625 def tx_label_changed(self, item, column):
629 tx_hash = str(item.data(0, Qt.UserRole).toString())
630 tx = self.wallet.transactions.get(tx_hash)
631 text = unicode( item.text(2) )
632 self.wallet.set_label(tx_hash, text)
634 item.setForeground(2, QBrush(QColor('black')))
636 text = self.wallet.get_default_label(tx_hash)
637 item.setText(2, text)
638 item.setForeground(2, QBrush(QColor('gray')))
642 def edit_label(self, is_recv):
643 l = self.receive_list if is_recv else self.contacts_list
644 item = l.currentItem()
645 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
646 l.editItem( item, 1 )
647 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
651 def address_label_clicked(self, item, column, l, column_addr, column_label):
652 if column == column_label and item.isSelected():
653 is_editable = item.data(0, 32).toBool()
656 addr = unicode( item.text(column_addr) )
657 label = unicode( item.text(column_label) )
658 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
659 l.editItem( item, column )
660 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
663 def address_label_changed(self, item, column, l, column_addr, column_label):
664 if column == column_label:
665 addr = unicode( item.text(column_addr) )
666 text = unicode( item.text(column_label) )
667 is_editable = item.data(0, 32).toBool()
671 changed = self.wallet.set_label(addr, text)
673 self.update_history_tab()
674 self.update_completions()
676 self.current_item_changed(item)
678 run_hook('item_changed', item, column)
681 def current_item_changed(self, a):
682 run_hook('current_item_changed', a)
686 def update_history_tab(self):
688 self.history_list.clear()
689 for item in self.wallet.get_tx_history(self.current_account):
690 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
691 time_str = _("unknown")
694 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
696 time_str = _("error")
699 time_str = 'unverified'
700 icon = QIcon(":icons/unconfirmed.png")
703 icon = QIcon(":icons/unconfirmed.png")
705 icon = QIcon(":icons/clock%d.png"%conf)
707 icon = QIcon(":icons/confirmed.png")
709 if value is not None:
710 v_str = self.format_amount(value, True, whitespaces=True)
714 balance_str = self.format_amount(balance, whitespaces=True)
717 label, is_default_label = self.wallet.get_label(tx_hash)
719 label = _('Pruned transaction outputs')
720 is_default_label = False
722 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
723 item.setFont(2, QFont(MONOSPACE_FONT))
724 item.setFont(3, QFont(MONOSPACE_FONT))
725 item.setFont(4, QFont(MONOSPACE_FONT))
727 item.setForeground(3, QBrush(QColor("#BC1E1E")))
729 item.setData(0, Qt.UserRole, tx_hash)
730 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
732 item.setForeground(2, QBrush(QColor('grey')))
734 item.setIcon(0, icon)
735 self.history_list.insertTopLevelItem(0,item)
738 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
741 def create_send_tab(self):
746 grid.setColumnMinimumWidth(3,300)
747 grid.setColumnStretch(5,1)
750 self.payto_e = QLineEdit()
751 grid.addWidget(QLabel(_('Pay to')), 1, 0)
752 grid.addWidget(self.payto_e, 1, 1, 1, 3)
754 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)
756 completer = QCompleter()
757 completer.setCaseSensitivity(False)
758 self.payto_e.setCompleter(completer)
759 completer.setModel(self.completions)
761 self.message_e = QLineEdit()
762 grid.addWidget(QLabel(_('Description')), 2, 0)
763 grid.addWidget(self.message_e, 2, 1, 1, 3)
764 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)
766 self.from_label = QLabel(_('From'))
767 grid.addWidget(self.from_label, 3, 0)
768 self.from_list = QTreeWidget(self)
769 self.from_list.setColumnCount(2)
770 self.from_list.setColumnWidth(0, 350)
771 self.from_list.setColumnWidth(1, 50)
772 self.from_list.setHeaderHidden (True)
773 self.from_list.setMaximumHeight(80)
774 grid.addWidget(self.from_list, 3, 1, 1, 3)
775 self.set_pay_from([])
777 self.amount_e = AmountEdit(self.base_unit)
778 grid.addWidget(QLabel(_('Amount')), 4, 0)
779 grid.addWidget(self.amount_e, 4, 1, 1, 2)
780 grid.addWidget(HelpButton(
781 _('Amount to be sent.') + '\n\n' \
782 + _('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.') \
783 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
785 self.fee_e = AmountEdit(self.base_unit)
786 grid.addWidget(QLabel(_('Fee')), 5, 0)
787 grid.addWidget(self.fee_e, 5, 1, 1, 2)
788 grid.addWidget(HelpButton(
789 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
790 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
791 + _('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)
794 self.send_button = EnterButton(_("Send"), self.do_send)
795 grid.addWidget(self.send_button, 6, 1)
797 b = EnterButton(_("Clear"),self.do_clear)
798 grid.addWidget(b, 6, 2)
800 self.payto_sig = QLabel('')
801 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
803 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
804 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
813 def entry_changed( is_fee ):
814 self.funds_error = False
816 if self.amount_e.is_shortcut:
817 self.amount_e.is_shortcut = False
818 sendable = self.get_sendable_balance()
819 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, self.get_payment_sources())
820 fee = self.wallet.estimated_fee(inputs)
822 self.amount_e.setText( self.format_amount(amount) )
823 self.fee_e.setText( self.format_amount( fee ) )
826 amount = self.read_amount(str(self.amount_e.text()))
827 fee = self.read_amount(str(self.fee_e.text()))
829 if not is_fee: fee = None
832 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, self.get_payment_sources())
834 self.fee_e.setText( self.format_amount( fee ) )
837 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
841 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
842 self.funds_error = True
843 text = _( "Not enough funds" )
844 c, u = self.wallet.get_frozen_balance()
845 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
847 self.statusBar().showMessage(text)
848 self.amount_e.setPalette(palette)
849 self.fee_e.setPalette(palette)
851 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
852 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
854 run_hook('create_send_tab', grid)
858 def set_pay_from(self, l):
860 self.from_list.clear()
861 self.from_label.setHidden(len(self.pay_from) == 0)
862 self.from_list.setHidden(len(self.pay_from) == 0)
863 for addr in self.pay_from:
864 c, u = self.wallet.get_addr_balance(addr)
865 balance = self.format_amount(c + u)
866 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
869 def update_completions(self):
871 for addr,label in self.wallet.labels.items():
872 if addr in self.wallet.addressbook:
873 l.append( label + ' <' + addr + '>')
875 run_hook('update_completions', l)
876 self.completions.setStringList(l)
880 return lambda s, *args: s.do_protect(func, args)
885 label = unicode( self.message_e.text() )
886 r = unicode( self.payto_e.text() )
889 # label or alias, with address in brackets
890 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
891 to_address = m.group(2) if m else r
893 if not is_valid(to_address):
894 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
898 amount = self.read_amount(unicode( self.amount_e.text()))
900 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
903 fee = self.read_amount(unicode( self.fee_e.text()))
905 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
908 confirm_amount = self.config.get('confirm_amount', 100000000)
909 if amount >= confirm_amount:
910 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
913 confirm_fee = self.config.get('confirm_fee', 100000)
914 if fee >= confirm_fee:
915 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()}):
918 self.send_tx(to_address, amount, fee, label)
922 def send_tx(self, to_address, amount, fee, label, password):
924 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
925 domain=self.get_payment_sources())
926 except Exception as e:
927 traceback.print_exc(file=sys.stdout)
928 self.show_message(str(e))
931 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
932 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
936 self.wallet.set_label(tx.hash(), label)
939 h = self.wallet.send_tx(tx)
940 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
941 status, msg = self.wallet.receive_tx( h, tx )
943 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
945 self.update_contacts_tab()
947 QMessageBox.warning(self, _('Error'), msg, _('OK'))
950 self.show_transaction(tx)
952 # add recipient to addressbook
953 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
954 self.wallet.addressbook.append(to_address)
959 def set_url(self, url):
960 address, amount, label, message, signature, identity, url = util.parse_url(url)
963 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
964 elif amount: amount = str(Decimal(amount))
967 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
970 self.mini.set_payment_fields(address, amount)
972 if label and self.wallet.labels.get(address) != label:
973 if self.question('Give label "%s" to address %s ?'%(label,address)):
974 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
975 self.wallet.addressbook.append(address)
976 self.wallet.set_label(address, label)
978 run_hook('set_url', url, self.show_message, self.question)
980 self.tabs.setCurrentIndex(1)
981 label = self.wallet.labels.get(address)
982 m_addr = label + ' <'+ address +'>' if label else address
983 self.payto_e.setText(m_addr)
985 self.message_e.setText(message)
987 self.amount_e.setText(amount)
990 self.set_frozen(self.payto_e,True)
991 self.set_frozen(self.amount_e,True)
992 self.set_frozen(self.message_e,True)
993 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
995 self.payto_sig.setVisible(False)
998 self.payto_sig.setVisible(False)
999 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1001 self.set_frozen(e,False)
1003 self.set_pay_from([])
1004 self.update_status()
1006 def set_frozen(self,entry,frozen):
1008 entry.setReadOnly(True)
1009 entry.setFrame(False)
1010 palette = QPalette()
1011 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1012 entry.setPalette(palette)
1014 entry.setReadOnly(False)
1015 entry.setFrame(True)
1016 palette = QPalette()
1017 palette.setColor(entry.backgroundRole(), QColor('white'))
1018 entry.setPalette(palette)
1021 def set_addrs_frozen(self,addrs,freeze):
1023 if not addr: continue
1024 if addr in self.wallet.frozen_addresses and not freeze:
1025 self.wallet.unfreeze(addr)
1026 elif addr not in self.wallet.frozen_addresses and freeze:
1027 self.wallet.freeze(addr)
1028 self.update_receive_tab()
1032 def create_list_tab(self, headers):
1033 "generic tab creation method"
1034 l = MyTreeWidget(self)
1035 l.setColumnCount( len(headers) )
1036 l.setHeaderLabels( headers )
1039 vbox = QVBoxLayout()
1046 vbox.addWidget(buttons)
1048 hbox = QHBoxLayout()
1051 buttons.setLayout(hbox)
1056 def create_receive_tab(self):
1057 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1058 l.setContextMenuPolicy(Qt.CustomContextMenu)
1059 l.customContextMenuRequested.connect(self.create_receive_menu)
1060 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1061 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1062 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1063 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1064 self.receive_list = l
1065 self.receive_buttons_hbox = hbox
1072 def save_column_widths(self):
1073 self.column_widths["receive"] = []
1074 for i in range(self.receive_list.columnCount() -1):
1075 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1077 self.column_widths["history"] = []
1078 for i in range(self.history_list.columnCount() - 1):
1079 self.column_widths["history"].append(self.history_list.columnWidth(i))
1081 self.column_widths["contacts"] = []
1082 for i in range(self.contacts_list.columnCount() - 1):
1083 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1085 self.config.set_key("column_widths_2", self.column_widths, True)
1088 def create_contacts_tab(self):
1089 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1090 l.setContextMenuPolicy(Qt.CustomContextMenu)
1091 l.customContextMenuRequested.connect(self.create_contact_menu)
1092 for i,width in enumerate(self.column_widths['contacts']):
1093 l.setColumnWidth(i, width)
1095 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1096 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1097 self.contacts_list = l
1098 self.contacts_buttons_hbox = hbox
1103 def delete_imported_key(self, addr):
1104 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1105 self.wallet.delete_imported_key(addr)
1106 self.update_receive_tab()
1107 self.update_history_tab()
1109 def edit_account_label(self, k):
1110 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1112 label = unicode(text)
1113 self.wallet.set_label(k,label)
1114 self.update_receive_tab()
1116 def account_set_expanded(self, item, k, b):
1118 self.accounts_expanded[k] = b
1120 def create_account_menu(self, position, k, item):
1122 if item.isExpanded():
1123 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1125 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1126 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1127 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1128 if self.wallet.account_is_pending(k):
1129 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1130 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1132 def delete_pending_account(self, k):
1133 self.wallet.delete_pending_account(k)
1134 self.update_receive_tab()
1136 def create_receive_menu(self, position):
1137 # fixme: this function apparently has a side effect.
1138 # if it is not called the menu pops up several times
1139 #self.receive_list.selectedIndexes()
1141 selected = self.receive_list.selectedItems()
1142 multi_select = len(selected) > 1
1143 addrs = [unicode(item.text(0)) for item in selected]
1144 if not multi_select:
1145 item = self.receive_list.itemAt(position)
1149 if not is_valid(addr):
1150 k = str(item.data(0,32).toString())
1152 self.create_account_menu(position, k, item)
1154 item.setExpanded(not item.isExpanded())
1158 if not multi_select:
1159 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1160 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1161 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1162 if self.wallet.seed:
1163 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1164 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1165 if addr in self.wallet.imported_keys:
1166 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1168 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1169 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1170 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1171 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1173 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1175 run_hook('receive_menu', menu, addrs)
1176 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1179 def get_sendable_balance(self):
1180 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1183 def get_payment_sources(self):
1185 return self.pay_from
1187 return self.wallet.get_account_addresses(self.current_account)
1190 def send_from_addresses(self, addrs):
1191 self.set_pay_from( addrs )
1192 self.tabs.setCurrentIndex(1)
1195 def payto(self, addr):
1197 label = self.wallet.labels.get(addr)
1198 m_addr = label + ' <' + addr + '>' if label else addr
1199 self.tabs.setCurrentIndex(1)
1200 self.payto_e.setText(m_addr)
1201 self.amount_e.setFocus()
1204 def delete_contact(self, x):
1205 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1206 self.wallet.delete_contact(x)
1207 self.wallet.set_label(x, None)
1208 self.update_history_tab()
1209 self.update_contacts_tab()
1210 self.update_completions()
1213 def create_contact_menu(self, position):
1214 item = self.contacts_list.itemAt(position)
1217 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1219 addr = unicode(item.text(0))
1220 label = unicode(item.text(1))
1221 is_editable = item.data(0,32).toBool()
1222 payto_addr = item.data(0,33).toString()
1223 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1224 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1225 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1227 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1228 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1230 run_hook('create_contact_menu', menu, item)
1231 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1234 def update_receive_item(self, item):
1235 item.setFont(0, QFont(MONOSPACE_FONT))
1236 address = str(item.data(0,0).toString())
1237 label = self.wallet.labels.get(address,'')
1238 item.setData(1,0,label)
1239 item.setData(0,32, True) # is editable
1241 run_hook('update_receive_item', address, item)
1243 if not self.wallet.is_mine(address): return
1245 c, u = self.wallet.get_addr_balance(address)
1246 balance = self.format_amount(c + u)
1247 item.setData(2,0,balance)
1249 if address in self.wallet.frozen_addresses:
1250 item.setBackgroundColor(0, QColor('lightblue'))
1253 def update_receive_tab(self):
1254 l = self.receive_list
1257 l.setColumnHidden(2, False)
1258 l.setColumnHidden(3, False)
1259 for i,width in enumerate(self.column_widths['receive']):
1260 l.setColumnWidth(i, width)
1262 if self.current_account is None:
1263 account_items = self.wallet.accounts.items()
1264 elif self.current_account != -1:
1265 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1269 for k, account in account_items:
1270 name = self.wallet.get_account_name(k)
1271 c,u = self.wallet.get_account_balance(k)
1272 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1273 l.addTopLevelItem(account_item)
1274 account_item.setExpanded(self.accounts_expanded.get(k, True))
1275 account_item.setData(0, 32, k)
1277 if not self.wallet.is_seeded(k):
1278 icon = QIcon(":icons/key.png")
1279 account_item.setIcon(0, icon)
1281 for is_change in ([0,1]):
1282 name = _("Receiving") if not is_change else _("Change")
1283 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1284 account_item.addChild(seq_item)
1285 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1287 if not is_change: seq_item.setExpanded(True)
1292 for address in account.get_addresses(is_change):
1293 h = self.wallet.history.get(address,[])
1297 if gap > self.wallet.gap_limit:
1302 c, u = self.wallet.get_addr_balance(address)
1303 num_tx = '*' if h == ['*'] else "%d"%len(h)
1304 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1305 self.update_receive_item(item)
1307 item.setBackgroundColor(1, QColor('red'))
1308 if len(h) > 0 and c == -u:
1310 seq_item.addChild(used_item)
1312 used_item.addChild(item)
1314 seq_item.addChild(item)
1317 for k, addr in self.wallet.get_pending_accounts():
1318 name = self.wallet.labels.get(k,'')
1319 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1320 self.update_receive_item(item)
1321 l.addTopLevelItem(account_item)
1322 account_item.setExpanded(True)
1323 account_item.setData(0, 32, k)
1324 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1325 account_item.addChild(item)
1326 self.update_receive_item(item)
1329 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1330 c,u = self.wallet.get_imported_balance()
1331 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1332 l.addTopLevelItem(account_item)
1333 account_item.setExpanded(True)
1334 for address in self.wallet.imported_keys.keys():
1335 item = QTreeWidgetItem( [ address, '', '', ''] )
1336 self.update_receive_item(item)
1337 account_item.addChild(item)
1340 # we use column 1 because column 0 may be hidden
1341 l.setCurrentItem(l.topLevelItem(0),1)
1344 def update_contacts_tab(self):
1345 l = self.contacts_list
1348 for address in self.wallet.addressbook:
1349 label = self.wallet.labels.get(address,'')
1350 n = self.wallet.get_num_tx(address)
1351 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1352 item.setFont(0, QFont(MONOSPACE_FONT))
1353 # 32 = label can be edited (bool)
1354 item.setData(0,32, True)
1356 item.setData(0,33, address)
1357 l.addTopLevelItem(item)
1359 run_hook('update_contacts_tab', l)
1360 l.setCurrentItem(l.topLevelItem(0))
1364 def create_console_tab(self):
1365 from console import Console
1366 self.console = console = Console()
1370 def update_console(self):
1371 console = self.console
1372 console.history = self.config.get("console-history",[])
1373 console.history_index = len(console.history)
1375 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1376 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1378 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1380 def mkfunc(f, method):
1381 return lambda *args: apply( f, (method, args, self.password_dialog ))
1383 if m[0]=='_' or m in ['network','wallet']: continue
1384 methods[m] = mkfunc(c._run, m)
1386 console.updateNamespace(methods)
1389 def change_account(self,s):
1390 if s == _("All accounts"):
1391 self.current_account = None
1393 accounts = self.wallet.get_account_names()
1394 for k, v in accounts.items():
1396 self.current_account = k
1397 self.update_history_tab()
1398 self.update_status()
1399 self.update_receive_tab()
1401 def create_status_bar(self):
1404 sb.setFixedHeight(35)
1405 qtVersion = qVersion()
1407 self.balance_label = QLabel("")
1408 sb.addWidget(self.balance_label)
1410 from version_getter import UpdateLabel
1411 self.updatelabel = UpdateLabel(self.config, sb)
1413 self.account_selector = QComboBox()
1414 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1415 sb.addPermanentWidget(self.account_selector)
1417 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1418 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1420 self.lock_icon = QIcon()
1421 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1422 sb.addPermanentWidget( self.password_button )
1424 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1425 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1426 sb.addPermanentWidget( self.seed_button )
1427 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1428 sb.addPermanentWidget( self.status_button )
1430 run_hook('create_status_bar', (sb,))
1432 self.setStatusBar(sb)
1435 def update_lock_icon(self):
1436 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1437 self.password_button.setIcon( icon )
1440 def update_buttons_on_seed(self):
1441 if not self.wallet.is_watching_only():
1442 self.seed_button.show()
1443 self.password_button.show()
1444 self.send_button.setText(_("Send"))
1446 self.password_button.hide()
1447 self.seed_button.hide()
1448 self.send_button.setText(_("Create unsigned transaction"))
1451 def change_password_dialog(self):
1452 from password_dialog import PasswordDialog
1453 d = PasswordDialog(self.wallet, self)
1455 self.update_lock_icon()
1458 def new_contact_dialog(self):
1461 vbox = QVBoxLayout(d)
1462 vbox.addWidget(QLabel(_('New Contact')+':'))
1464 grid = QGridLayout()
1467 grid.addWidget(QLabel(_("Address")), 1, 0)
1468 grid.addWidget(line1, 1, 1)
1469 grid.addWidget(QLabel(_("Name")), 2, 0)
1470 grid.addWidget(line2, 2, 1)
1472 vbox.addLayout(grid)
1473 vbox.addLayout(ok_cancel_buttons(d))
1478 address = str(line1.text())
1479 label = unicode(line2.text())
1481 if not is_valid(address):
1482 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1485 self.wallet.add_contact(address)
1487 self.wallet.set_label(address, label)
1489 self.update_contacts_tab()
1490 self.update_history_tab()
1491 self.update_completions()
1492 self.tabs.setCurrentIndex(3)
1495 def new_account_dialog(self):
1497 dialog = QDialog(self)
1499 dialog.setWindowTitle(_("New Account"))
1501 vbox = QVBoxLayout()
1502 vbox.addWidget(QLabel(_('Account name')+':'))
1505 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1506 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1511 vbox.addLayout(ok_cancel_buttons(dialog))
1512 dialog.setLayout(vbox)
1516 name = str(e.text())
1519 self.wallet.create_pending_account('1', name)
1520 self.update_receive_tab()
1521 self.tabs.setCurrentIndex(2)
1525 def show_master_public_key_old(self):
1526 dialog = QDialog(self)
1528 dialog.setWindowTitle(_("Master Public Key"))
1530 main_text = QTextEdit()
1531 main_text.setText(self.wallet.get_master_public_key())
1532 main_text.setReadOnly(True)
1533 main_text.setMaximumHeight(170)
1534 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1536 ok_button = QPushButton(_("OK"))
1537 ok_button.setDefault(True)
1538 ok_button.clicked.connect(dialog.accept)
1540 main_layout = QGridLayout()
1541 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1543 main_layout.addWidget(main_text, 1, 0)
1544 main_layout.addWidget(qrw, 1, 1 )
1546 vbox = QVBoxLayout()
1547 vbox.addLayout(main_layout)
1548 vbox.addLayout(close_button(dialog))
1549 dialog.setLayout(vbox)
1553 def show_master_public_key(self):
1555 if self.wallet.seed_version == 4:
1556 self.show_master_public_key_old()
1559 dialog = QDialog(self)
1561 dialog.setWindowTitle(_("Master Public Keys"))
1563 chain_text = QTextEdit()
1564 chain_text.setReadOnly(True)
1565 chain_text.setMaximumHeight(170)
1566 chain_qrw = QRCodeWidget()
1568 mpk_text = QTextEdit()
1569 mpk_text.setReadOnly(True)
1570 mpk_text.setMaximumHeight(170)
1571 mpk_qrw = QRCodeWidget()
1573 main_layout = QGridLayout()
1575 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1576 main_layout.addWidget(mpk_text, 1, 1)
1577 main_layout.addWidget(mpk_qrw, 1, 2)
1579 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1580 main_layout.addWidget(chain_text, 2, 1)
1581 main_layout.addWidget(chain_qrw, 2, 2)
1584 c, K, cK = self.wallet.master_public_keys[str(key)]
1585 chain_text.setText(c)
1586 chain_qrw.set_addr(c)
1587 chain_qrw.update_qr()
1592 key_selector = QComboBox()
1593 keys = sorted(self.wallet.master_public_keys.keys())
1594 key_selector.addItems(keys)
1596 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1597 main_layout.addWidget(key_selector, 0, 1)
1598 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1602 vbox = QVBoxLayout()
1603 vbox.addLayout(main_layout)
1604 vbox.addLayout(close_button(dialog))
1606 dialog.setLayout(vbox)
1611 def show_seed_dialog(self, password):
1612 if self.wallet.is_watching_only():
1613 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1616 if self.wallet.seed:
1618 mnemonic = self.wallet.get_mnemonic(password)
1620 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1622 from seed_dialog import SeedDialog
1623 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1627 for k in self.wallet.master_private_keys.keys():
1628 pk = self.wallet.get_master_private_key(k, password)
1630 from seed_dialog import PrivateKeysDialog
1631 d = PrivateKeysDialog(self,l)
1638 def show_qrcode(self, data, title = _("QR code")):
1642 d.setWindowTitle(title)
1643 d.setMinimumSize(270, 300)
1644 vbox = QVBoxLayout()
1645 qrw = QRCodeWidget(data)
1646 vbox.addWidget(qrw, 1)
1647 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1648 hbox = QHBoxLayout()
1651 filename = os.path.join(self.config.path, "qrcode.bmp")
1654 bmp.save_qrcode(qrw.qr, filename)
1655 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1657 def copy_to_clipboard():
1658 bmp.save_qrcode(qrw.qr, filename)
1659 self.app.clipboard().setImage(QImage(filename))
1660 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1662 b = QPushButton(_("Copy"))
1664 b.clicked.connect(copy_to_clipboard)
1666 b = QPushButton(_("Save"))
1668 b.clicked.connect(print_qr)
1670 b = QPushButton(_("Close"))
1672 b.clicked.connect(d.accept)
1675 vbox.addLayout(hbox)
1680 def do_protect(self, func, args):
1681 if self.wallet.use_encryption:
1682 password = self.password_dialog()
1688 if args != (False,):
1689 args = (self,) + args + (password,)
1691 args = (self,password)
1696 def show_private_key(self, address, password):
1697 if not address: return
1699 pk_list = self.wallet.get_private_key(address, password)
1700 except Exception as e:
1701 self.show_message(str(e))
1705 d.setMinimumSize(600, 200)
1707 vbox = QVBoxLayout()
1708 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1709 vbox.addWidget( QLabel(_("Private key") + ':'))
1711 keys.setReadOnly(True)
1712 keys.setText('\n'.join(pk_list))
1713 vbox.addWidget(keys)
1714 vbox.addLayout(close_button(d))
1720 def do_sign(self, address, message, signature, password):
1721 message = unicode(message.toPlainText())
1722 message = message.encode('utf-8')
1724 sig = self.wallet.sign_message(str(address.text()), message, password)
1725 signature.setText(sig)
1726 except Exception as e:
1727 self.show_message(str(e))
1729 def sign_message(self, address):
1730 if not address: return
1733 d.setWindowTitle(_('Sign Message'))
1734 d.setMinimumSize(410, 290)
1736 tab_widget = QTabWidget()
1738 layout = QGridLayout(tab)
1740 sign_address = QLineEdit()
1742 sign_address.setText(address)
1743 layout.addWidget(QLabel(_('Address')), 1, 0)
1744 layout.addWidget(sign_address, 1, 1)
1746 sign_message = QTextEdit()
1747 layout.addWidget(QLabel(_('Message')), 2, 0)
1748 layout.addWidget(sign_message, 2, 1)
1749 layout.setRowStretch(2,3)
1751 sign_signature = QTextEdit()
1752 layout.addWidget(QLabel(_('Signature')), 3, 0)
1753 layout.addWidget(sign_signature, 3, 1)
1754 layout.setRowStretch(3,1)
1757 hbox = QHBoxLayout()
1758 b = QPushButton(_("Sign"))
1760 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1761 b = QPushButton(_("Close"))
1762 b.clicked.connect(d.accept)
1764 layout.addLayout(hbox, 4, 1)
1765 tab_widget.addTab(tab, _("Sign"))
1769 layout = QGridLayout(tab)
1771 verify_address = QLineEdit()
1772 layout.addWidget(QLabel(_('Address')), 1, 0)
1773 layout.addWidget(verify_address, 1, 1)
1775 verify_message = QTextEdit()
1776 layout.addWidget(QLabel(_('Message')), 2, 0)
1777 layout.addWidget(verify_message, 2, 1)
1778 layout.setRowStretch(2,3)
1780 verify_signature = QTextEdit()
1781 layout.addWidget(QLabel(_('Signature')), 3, 0)
1782 layout.addWidget(verify_signature, 3, 1)
1783 layout.setRowStretch(3,1)
1786 message = unicode(verify_message.toPlainText())
1787 message = message.encode('utf-8')
1788 if bitcoin.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1789 self.show_message(_("Signature verified"))
1791 self.show_message(_("Error: wrong signature"))
1793 hbox = QHBoxLayout()
1794 b = QPushButton(_("Verify"))
1795 b.clicked.connect(do_verify)
1797 b = QPushButton(_("Close"))
1798 b.clicked.connect(d.accept)
1800 layout.addLayout(hbox, 4, 1)
1801 tab_widget.addTab(tab, _("Verify"))
1803 vbox = QVBoxLayout()
1804 vbox.addWidget(tab_widget)
1811 def question(self, msg):
1812 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1814 def show_message(self, msg):
1815 QMessageBox.information(self, _('Message'), msg, _('OK'))
1817 def password_dialog(self ):
1824 vbox = QVBoxLayout()
1825 msg = _('Please enter your password')
1826 vbox.addWidget(QLabel(msg))
1828 grid = QGridLayout()
1830 grid.addWidget(QLabel(_('Password')), 1, 0)
1831 grid.addWidget(pw, 1, 1)
1832 vbox.addLayout(grid)
1834 vbox.addLayout(ok_cancel_buttons(d))
1837 run_hook('password_dialog', pw, grid, 1)
1838 if not d.exec_(): return
1839 return unicode(pw.text())
1848 def tx_from_text(self, txt):
1849 "json or raw hexadecimal"
1852 tx = Transaction(txt)
1858 tx_dict = json.loads(str(txt))
1859 assert "hex" in tx_dict.keys()
1860 assert "complete" in tx_dict.keys()
1861 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1862 if not tx_dict["complete"]:
1863 assert "input_info" in tx_dict.keys()
1864 input_info = json.loads(tx_dict['input_info'])
1865 tx.add_input_info(input_info)
1870 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1874 def read_tx_from_file(self):
1875 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1879 with open(fileName, "r") as f:
1880 file_content = f.read()
1881 except (ValueError, IOError, os.error), reason:
1882 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1884 return self.tx_from_text(file_content)
1888 def sign_raw_transaction(self, tx, input_info, password):
1889 self.wallet.signrawtransaction(tx, input_info, [], password)
1891 def do_process_from_text(self):
1892 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1895 tx = self.tx_from_text(text)
1897 self.show_transaction(tx)
1899 def do_process_from_file(self):
1900 tx = self.read_tx_from_file()
1902 self.show_transaction(tx)
1904 def do_process_from_csvReader(self, csvReader):
1907 for row in csvReader:
1909 amount = Decimal(row[1])
1910 amount = int(100000000*amount)
1911 outputs.append((address, amount))
1912 except (ValueError, IOError, os.error), reason:
1913 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1917 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1918 except Exception as e:
1919 self.show_message(str(e))
1922 self.show_transaction(tx)
1924 def do_process_from_csv_file(self):
1925 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1929 with open(fileName, "r") as f:
1930 csvReader = csv.reader(f)
1931 self.do_process_from_csvReader(csvReader)
1932 except (ValueError, IOError, os.error), reason:
1933 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1936 def do_process_from_csv_text(self):
1937 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1938 + _("Format: address, amount. One output per line"), _("Load CSV"))
1941 f = StringIO.StringIO(text)
1942 csvReader = csv.reader(f)
1943 self.do_process_from_csvReader(csvReader)
1948 def do_export_privkeys(self, password):
1949 if not self.wallet.seed:
1950 self.show_message(_("This wallet has no seed"))
1953 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.")))
1956 select_export = _('Select file to export your private keys to')
1957 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1959 with open(fileName, "w+") as csvfile:
1960 transaction = csv.writer(csvfile)
1961 transaction.writerow(["address", "private_key"])
1963 addresses = self.wallet.addresses(True)
1965 for addr in addresses:
1966 pk = "".join(self.wallet.get_private_key(addr, password))
1967 transaction.writerow(["%34s"%addr,pk])
1969 self.show_message(_("Private keys exported."))
1971 except (IOError, os.error), reason:
1972 export_error_label = _("Electrum was unable to produce a private key-export.")
1973 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1975 except Exception as e:
1976 self.show_message(str(e))
1980 def do_import_labels(self):
1981 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1982 if not labelsFile: return
1984 f = open(labelsFile, 'r')
1987 for key, value in json.loads(data).items():
1988 self.wallet.set_label(key, value)
1989 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1990 except (IOError, os.error), reason:
1991 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1994 def do_export_labels(self):
1995 labels = self.wallet.labels
1997 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1999 with open(fileName, 'w+') as f:
2000 json.dump(labels, f)
2001 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2002 except (IOError, os.error), reason:
2003 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2006 def do_export_history(self):
2007 from lite_window import csv_transaction
2008 csv_transaction(self.wallet)
2012 def do_import_privkey(self, password):
2013 if not self.wallet.imported_keys:
2014 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2015 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2016 + _('Are you sure you understand what you are doing?'), 3, 4)
2019 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2022 text = str(text).split()
2027 addr = self.wallet.import_key(key, password)
2028 except Exception as e:
2034 addrlist.append(addr)
2036 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2038 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2039 self.update_receive_tab()
2040 self.update_history_tab()
2043 def settings_dialog(self):
2045 d.setWindowTitle(_('Electrum Settings'))
2047 vbox = QVBoxLayout()
2048 grid = QGridLayout()
2049 grid.setColumnStretch(0,1)
2051 nz_label = QLabel(_('Display zeros') + ':')
2052 grid.addWidget(nz_label, 0, 0)
2053 nz_e = AmountEdit(None,True)
2054 nz_e.setText("%d"% self.num_zeros)
2055 grid.addWidget(nz_e, 0, 1)
2056 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2057 grid.addWidget(HelpButton(msg), 0, 2)
2058 if not self.config.is_modifiable('num_zeros'):
2059 for w in [nz_e, nz_label]: w.setEnabled(False)
2061 lang_label=QLabel(_('Language') + ':')
2062 grid.addWidget(lang_label, 1, 0)
2063 lang_combo = QComboBox()
2064 from electrum.i18n import languages
2065 lang_combo.addItems(languages.values())
2067 index = languages.keys().index(self.config.get("language",''))
2070 lang_combo.setCurrentIndex(index)
2071 grid.addWidget(lang_combo, 1, 1)
2072 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2073 if not self.config.is_modifiable('language'):
2074 for w in [lang_combo, lang_label]: w.setEnabled(False)
2077 fee_label = QLabel(_('Transaction fee') + ':')
2078 grid.addWidget(fee_label, 2, 0)
2079 fee_e = AmountEdit(self.base_unit)
2080 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2081 grid.addWidget(fee_e, 2, 1)
2082 msg = _('Fee per kilobyte of transaction.') + ' ' \
2083 + _('Recommended value') + ': ' + self.format_amount(20000)
2084 grid.addWidget(HelpButton(msg), 2, 2)
2085 if not self.config.is_modifiable('fee_per_kb'):
2086 for w in [fee_e, fee_label]: w.setEnabled(False)
2088 units = ['BTC', 'mBTC']
2089 unit_label = QLabel(_('Base unit') + ':')
2090 grid.addWidget(unit_label, 3, 0)
2091 unit_combo = QComboBox()
2092 unit_combo.addItems(units)
2093 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2094 grid.addWidget(unit_combo, 3, 1)
2095 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2096 + '\n1BTC=1000mBTC.\n' \
2097 + _(' This settings affects the fields in the Send tab')+' '), 3, 2)
2099 usechange_cb = QCheckBox(_('Use change addresses'))
2100 usechange_cb.setChecked(self.wallet.use_change)
2101 grid.addWidget(usechange_cb, 4, 0)
2102 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2103 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2105 grid.setRowStretch(5,1)
2107 vbox.addLayout(grid)
2108 vbox.addLayout(ok_cancel_buttons(d))
2112 if not d.exec_(): return
2114 fee = unicode(fee_e.text())
2116 fee = self.read_amount(fee)
2118 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2121 self.wallet.set_fee(fee)
2123 nz = unicode(nz_e.text())
2128 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2131 if self.num_zeros != nz:
2133 self.config.set_key('num_zeros', nz, True)
2134 self.update_history_tab()
2135 self.update_receive_tab()
2137 usechange_result = usechange_cb.isChecked()
2138 if self.wallet.use_change != usechange_result:
2139 self.wallet.use_change = usechange_result
2140 self.wallet.storage.put('use_change', self.wallet.use_change)
2142 unit_result = units[unit_combo.currentIndex()]
2143 if self.base_unit() != unit_result:
2144 self.decimal_point = 8 if unit_result == 'BTC' else 5
2145 self.config.set_key('decimal_point', self.decimal_point, True)
2146 self.update_history_tab()
2147 self.update_status()
2149 need_restart = False
2151 lang_request = languages.keys()[lang_combo.currentIndex()]
2152 if lang_request != self.config.get('language'):
2153 self.config.set_key("language", lang_request, True)
2156 run_hook('close_settings_dialog')
2159 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2162 def run_network_dialog(self):
2163 if not self.network:
2165 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2167 def closeEvent(self, event):
2170 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2171 self.save_column_widths()
2172 self.config.set_key("console-history", self.console.history[-50:], True)
2173 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2178 def plugins_dialog(self):
2179 from electrum.plugins import plugins
2182 d.setWindowTitle(_('Electrum Plugins'))
2185 vbox = QVBoxLayout(d)
2188 scroll = QScrollArea()
2189 scroll.setEnabled(True)
2190 scroll.setWidgetResizable(True)
2191 scroll.setMinimumSize(400,250)
2192 vbox.addWidget(scroll)
2196 w.setMinimumHeight(len(plugins)*35)
2198 grid = QGridLayout()
2199 grid.setColumnStretch(0,1)
2202 def do_toggle(cb, p, w):
2205 if w: w.setEnabled(r)
2207 def mk_toggle(cb, p, w):
2208 return lambda: do_toggle(cb,p,w)
2210 for i, p in enumerate(plugins):
2212 cb = QCheckBox(p.fullname())
2213 cb.setDisabled(not p.is_available())
2214 cb.setChecked(p.is_enabled())
2215 grid.addWidget(cb, i, 0)
2216 if p.requires_settings():
2217 w = p.settings_widget(self)
2218 w.setEnabled( p.is_enabled() )
2219 grid.addWidget(w, i, 1)
2222 cb.clicked.connect(mk_toggle(cb,p,w))
2223 grid.addWidget(HelpButton(p.description()), i, 2)
2225 print_msg(_("Error: cannot display plugin"), p)
2226 traceback.print_exc(file=sys.stdout)
2227 grid.setRowStretch(i+1,1)
2229 vbox.addLayout(close_button(d))
2234 def show_account_details(self, k):
2236 d.setWindowTitle(_('Account Details'))
2239 vbox = QVBoxLayout(d)
2240 roots = self.wallet.get_roots(k)
2242 name = self.wallet.get_account_name(k)
2243 label = QLabel('Name: ' + name)
2244 vbox.addWidget(label)
2246 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2247 vbox.addWidget(QLabel('Type: ' + acctype))
2249 label = QLabel('Derivation: ' + k)
2250 vbox.addWidget(label)
2253 # mpk = self.wallet.master_public_keys[root]
2254 # text = QTextEdit()
2255 # text.setReadOnly(True)
2256 # text.setMaximumHeight(120)
2257 # text.setText(repr(mpk))
2258 # vbox.addWidget(text)
2260 vbox.addLayout(close_button(d))