3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
33 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
34 from electrum.plugins import run_hook
38 from electrum.wallet import format_satoshis
39 from electrum import Transaction
40 from electrum import mnemonic
41 from electrum import util, bitcoin, commands, Interface, Wallet
42 from electrum import SimpleConfig, Wallet, WalletStorage
45 from electrum import bmp, pyqrnative
47 from amountedit import AmountEdit
48 from network_dialog import NetworkDialog
49 from qrcodewidget import QRCodeWidget
51 from decimal import Decimal
59 if platform.system() == 'Windows':
60 MONOSPACE_FONT = 'Lucida Console'
61 elif platform.system() == 'Darwin':
62 MONOSPACE_FONT = 'Monaco'
64 MONOSPACE_FONT = 'monospace'
66 from electrum import ELECTRUM_VERSION
76 class StatusBarButton(QPushButton):
77 def __init__(self, icon, tooltip, func):
78 QPushButton.__init__(self, icon, '')
79 self.setToolTip(tooltip)
81 self.setMaximumWidth(25)
82 self.clicked.connect(func)
84 self.setIconSize(QSize(25,25))
86 def keyPressEvent(self, e):
87 if e.key() == QtCore.Qt.Key_Return:
99 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
101 class ElectrumWindow(QMainWindow):
102 def build_menu(self):
104 m.addAction(_("Show/Hide"), self.show_or_hide)
106 m.addAction(_("Exit Electrum"), self.close)
107 self.tray.setContextMenu(m)
109 def show_or_hide(self):
110 self.tray_activated(QSystemTrayIcon.DoubleClick)
112 def tray_activated(self, reason):
113 if reason == QSystemTrayIcon.DoubleClick:
114 if self.isMinimized() or self.isHidden():
119 def __init__(self, config, network):
120 QMainWindow.__init__(self)
123 self.network = network
125 self._close_electrum = False
128 if sys.platform == 'darwin':
129 self.icon = QIcon(":icons/electrum_dark_icon.png")
130 #self.icon = QIcon(":icons/lock.png")
132 self.icon = QIcon(':icons/electrum_light_icon.png')
134 self.tray = QSystemTrayIcon(self.icon, self)
135 self.tray.setToolTip('Electrum')
136 self.tray.activated.connect(self.tray_activated)
140 self.create_status_bar()
142 self.need_update = threading.Event()
144 self.decimal_point = config.get('decimal_point', 8)
145 self.num_zeros = int(config.get('num_zeros',0))
147 set_language(config.get('language'))
149 self.funds_error = False
150 self.completions = QStringListModel()
152 self.tabs = tabs = QTabWidget(self)
153 self.column_widths = self.config.get("column_widths_2", default_column_widths )
154 tabs.addTab(self.create_history_tab(), _('History') )
155 tabs.addTab(self.create_send_tab(), _('Send') )
156 tabs.addTab(self.create_receive_tab(), _('Receive') )
157 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
158 tabs.addTab(self.create_console_tab(), _('Console') )
159 tabs.setMinimumSize(600, 400)
160 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
161 self.setCentralWidget(tabs)
163 g = self.config.get("winpos-qt",[100, 100, 840, 400])
164 self.setGeometry(g[0], g[1], g[2], g[3])
166 self.setWindowIcon(QIcon(":icons/electrum.png"))
169 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
170 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
171 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
172 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
173 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
175 for i in range(tabs.count()):
176 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
178 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
179 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
180 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
182 self.history_list.setFocus(True)
186 self.network.register_callback('updated', lambda: self.need_update.set())
187 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
188 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
189 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
190 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
192 # set initial message
193 self.console.showMessage(self.network.banner)
200 self.config.set_key('lite_mode', False, True)
205 self.config.set_key('lite_mode', True, True)
212 if not self.check_qt_version():
213 if self.config.get('lite_mode') is True:
214 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
215 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
216 self.config.set_key('lite_mode', False, True)
222 actuator = lite_window.MiniActuator(self)
224 # Should probably not modify the current path but instead
225 # change the behaviour of rsrc(...)
226 old_path = QDir.currentPath()
227 actuator.load_theme()
229 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
231 driver = lite_window.MiniDriver(self, self.mini)
233 # Reset path back to original value now that loading the GUI
235 QDir.setCurrent(old_path)
237 if self.config.get('lite_mode') is True:
243 def check_qt_version(self):
244 qtVersion = qVersion()
245 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
248 def update_account_selector(self):
250 accounts = self.wallet.get_account_names()
251 self.account_selector.clear()
252 if len(accounts) > 1:
253 self.account_selector.addItems([_("All accounts")] + accounts.values())
254 self.account_selector.setCurrentIndex(0)
255 self.account_selector.show()
257 self.account_selector.hide()
260 def load_wallet(self, wallet):
263 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
264 self.current_account = self.wallet.storage.get("current_account", None)
266 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
267 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
268 self.setWindowTitle( title )
270 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
271 self.notify_transactions()
272 self.update_account_selector()
273 self.new_account.setEnabled(self.wallet.seed_version>4)
274 self.update_lock_icon()
275 self.update_buttons_on_seed()
276 self.update_console()
278 run_hook('load_wallet', wallet)
281 def open_wallet(self):
282 wallet_folder = self.wallet.storage.path
283 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
287 storage = WalletStorage({'wallet_path': filename})
288 if not storage.file_exists:
289 self.show_message("file not found "+ filename)
292 self.wallet.stop_threads()
295 wallet = Wallet(storage)
296 wallet.start_threads(self.network)
298 self.load_wallet(wallet)
302 def backup_wallet(self):
304 path = self.wallet.storage.path
305 wallet_folder = os.path.dirname(path)
306 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
310 new_path = os.path.join(wallet_folder, filename)
313 shutil.copy2(path, new_path)
314 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
315 except (IOError, os.error), reason:
316 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
319 def new_wallet(self):
322 wallet_folder = os.path.dirname(self.wallet.storage.path)
323 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
326 filename = os.path.join(wallet_folder, filename)
328 storage = WalletStorage({'wallet_path': filename})
329 if storage.file_exists:
330 QMessageBox.critical(None, "Error", _("File exists"))
333 wizard = installwizard.InstallWizard(self.config, self.network, storage)
334 wallet = wizard.run()
336 self.load_wallet(wallet)
340 def init_menubar(self):
343 file_menu = menubar.addMenu(_("&File"))
344 open_wallet_action = file_menu.addAction(_("&Open"))
345 open_wallet_action.setShortcut(QKeySequence.Open)
346 open_wallet_action.triggered.connect(self.open_wallet)
348 new_wallet_action = file_menu.addAction(_("&New/Restore"))
349 new_wallet_action.setShortcut(QKeySequence.New)
350 new_wallet_action.triggered.connect(self.new_wallet)
352 wallet_backup = file_menu.addAction(_("&Save Copy"))
353 wallet_backup.setShortcut(QKeySequence.SaveAs)
354 wallet_backup.triggered.connect(self.backup_wallet)
356 quit_item = file_menu.addAction(_("&Quit"))
357 #quit_item.setShortcut(QKeySequence.Quit)
358 quit_item.triggered.connect(self.close)
360 wallet_menu = menubar.addMenu(_("&Wallet"))
362 new_contact = wallet_menu.addAction(_("&New contact"))
363 new_contact.triggered.connect(self.new_contact_dialog)
365 self.new_account = wallet_menu.addAction(_("&New account"))
366 self.new_account.triggered.connect(self.new_account_dialog)
368 wallet_menu.addSeparator()
370 pw = wallet_menu.addAction(_("&Password"))
371 pw.triggered.connect(self.change_password_dialog)
373 show_seed = wallet_menu.addAction(_("&Seed"))
374 show_seed.triggered.connect(self.show_seed_dialog)
376 show_mpk = wallet_menu.addAction(_("&Master Public Key"))
377 show_mpk.triggered.connect(self.show_master_public_key)
379 wallet_menu.addSeparator()
381 labels_menu = wallet_menu.addMenu(_("&Labels"))
382 import_labels = labels_menu.addAction(_("&Import"))
383 import_labels.triggered.connect(self.do_import_labels)
384 export_labels = labels_menu.addAction(_("&Export"))
385 export_labels.triggered.connect(self.do_export_labels)
387 keys_menu = wallet_menu.addMenu(_("&Private keys"))
388 import_keys = keys_menu.addAction(_("&Import"))
389 import_keys.triggered.connect(self.do_import_privkey)
390 export_keys = keys_menu.addAction(_("&Export"))
391 export_keys.triggered.connect(self.do_export_privkeys)
393 ex_history = wallet_menu.addAction(_("&Export History"))
394 ex_history.triggered.connect(self.do_export_history)
398 tools_menu = menubar.addMenu(_("&Tools"))
400 # Settings / Preferences are all reserved keywords in OSX using this as work around
401 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
402 preferences_menu = tools_menu.addAction(preferences_name)
403 #preferences_menu.setShortcut(QKeySequence.Preferences)
404 preferences_menu.triggered.connect(self.settings_dialog)
406 network = tools_menu.addAction(_("&Network"))
407 network.triggered.connect(self.run_network_dialog)
409 plugins_labels = tools_menu.addAction(_("&Plugins"))
410 plugins_labels.triggered.connect(self.plugins_dialog)
412 tools_menu.addSeparator()
414 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
416 csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
417 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
419 csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
420 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
422 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
424 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
425 raw_transaction_file.triggered.connect(self.do_process_from_file)
427 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
428 raw_transaction_text.triggered.connect(self.do_process_from_text)
430 raw_transaction_text = raw_transaction_menu.addAction(_("&From the blockchain"))
431 raw_transaction_text.triggered.connect(self.do_process_from_txid)
434 help_menu = menubar.addMenu(_("&Help"))
435 show_about = help_menu.addAction(_("&About"))
436 show_about.triggered.connect(self.show_about)
437 web_open = help_menu.addAction(_("&Official website"))
438 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
440 help_menu.addSeparator()
441 doc_open = help_menu.addAction(_("&Documentation"))
442 doc_open.setShortcut(QKeySequence.HelpContents)
443 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
444 report_bug = help_menu.addAction(_("&Report Bug"))
445 report_bug.triggered.connect(self.show_report_bug)
447 self.setMenuBar(menubar)
449 def show_about(self):
450 QMessageBox.about(self, "Electrum",
451 _("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."))
453 def show_report_bug(self):
454 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
455 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
458 def notify_transactions(self):
459 if not self.network or not self.network.is_connected():
462 print_error("Notifying GUI")
463 if len(self.network.interface.pending_transactions_for_notifications) > 0:
464 # Combine the transactions if there are more then three
465 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
468 for tx in self.network.interface.pending_transactions_for_notifications:
469 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
473 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
474 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
476 self.network.interface.pending_transactions_for_notifications = []
478 for tx in self.network.interface.pending_transactions_for_notifications:
480 self.network.interface.pending_transactions_for_notifications.remove(tx)
481 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
483 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
485 def notify(self, message):
486 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
490 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
491 def getOpenFileName(self, title, filter = ""):
492 directory = self.config.get('io_dir', os.path.expanduser('~'))
493 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
494 if fileName and directory != os.path.dirname(fileName):
495 self.config.set_key('io_dir', os.path.dirname(fileName), True)
498 def getSaveFileName(self, title, filename, filter = ""):
499 directory = self.config.get('io_dir', os.path.expanduser('~'))
500 path = os.path.join( directory, filename )
501 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
502 if fileName and directory != os.path.dirname(fileName):
503 self.config.set_key('io_dir', os.path.dirname(fileName), True)
507 QMainWindow.close(self)
508 run_hook('close_main_window')
510 def connect_slots(self, sender):
511 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
512 self.previous_payto_e=''
514 def timer_actions(self):
515 if self.need_update.is_set():
517 self.need_update.clear()
518 run_hook('timer_actions')
520 def format_amount(self, x, is_diff=False, whitespaces=False):
521 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
523 def read_amount(self, x):
524 if x in['.', '']: return None
525 p = pow(10, self.decimal_point)
526 return int( p * Decimal(x) )
529 assert self.decimal_point in [5,8]
530 return "BTC" if self.decimal_point == 8 else "mBTC"
533 def update_status(self):
534 if self.network is None or not self.network.is_running():
536 icon = QIcon(":icons/status_disconnected.png")
538 elif self.network.is_connected():
539 if not self.wallet.up_to_date:
540 text = _("Synchronizing...")
541 icon = QIcon(":icons/status_waiting.png")
542 elif self.network.server_lag > 1:
543 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
544 icon = QIcon(":icons/status_lagging.png")
546 c, u = self.wallet.get_account_balance(self.current_account)
547 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
548 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
551 run_hook('set_quote_text', c+u, r)
554 text += " (%s)"%quote
556 self.tray.setToolTip(text)
557 icon = QIcon(":icons/status_connected.png")
559 text = _("Not connected")
560 icon = QIcon(":icons/status_disconnected.png")
562 self.balance_label.setText(text)
563 self.status_button.setIcon( icon )
566 def update_wallet(self):
568 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
569 self.update_history_tab()
570 self.update_receive_tab()
571 self.update_contacts_tab()
572 self.update_completions()
575 def create_history_tab(self):
576 self.history_list = l = MyTreeWidget(self)
578 for i,width in enumerate(self.column_widths['history']):
579 l.setColumnWidth(i, width)
580 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
581 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
582 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
584 l.customContextMenuRequested.connect(self.create_history_menu)
588 def create_history_menu(self, position):
589 self.history_list.selectedIndexes()
590 item = self.history_list.currentItem()
592 tx_hash = str(item.data(0, Qt.UserRole).toString())
593 if not tx_hash: return
595 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
596 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
597 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
598 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
599 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
602 def show_transaction(self, tx):
603 import transaction_dialog
604 d = transaction_dialog.TxDialog(tx, self)
607 def tx_label_clicked(self, item, column):
608 if column==2 and item.isSelected():
610 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
611 self.history_list.editItem( item, column )
612 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
615 def tx_label_changed(self, item, column):
619 tx_hash = str(item.data(0, Qt.UserRole).toString())
620 tx = self.wallet.transactions.get(tx_hash)
621 text = unicode( item.text(2) )
622 self.wallet.set_label(tx_hash, text)
624 item.setForeground(2, QBrush(QColor('black')))
626 text = self.wallet.get_default_label(tx_hash)
627 item.setText(2, text)
628 item.setForeground(2, QBrush(QColor('gray')))
632 def edit_label(self, is_recv):
633 l = self.receive_list if is_recv else self.contacts_list
634 item = l.currentItem()
635 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
636 l.editItem( item, 1 )
637 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
641 def address_label_clicked(self, item, column, l, column_addr, column_label):
642 if column == column_label and item.isSelected():
643 is_editable = item.data(0, 32).toBool()
646 addr = unicode( item.text(column_addr) )
647 label = unicode( item.text(column_label) )
648 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
649 l.editItem( item, column )
650 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
653 def address_label_changed(self, item, column, l, column_addr, column_label):
654 if column == column_label:
655 addr = unicode( item.text(column_addr) )
656 text = unicode( item.text(column_label) )
657 is_editable = item.data(0, 32).toBool()
661 changed = self.wallet.set_label(addr, text)
663 self.update_history_tab()
664 self.update_completions()
666 self.current_item_changed(item)
668 run_hook('item_changed', item, column)
671 def current_item_changed(self, a):
672 run_hook('current_item_changed', a)
676 def update_history_tab(self):
678 self.history_list.clear()
679 for item in self.wallet.get_tx_history(self.current_account):
680 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
681 time_str = _("unknown")
684 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
686 time_str = _("error")
689 time_str = 'unverified'
690 icon = QIcon(":icons/unconfirmed.png")
693 icon = QIcon(":icons/unconfirmed.png")
695 icon = QIcon(":icons/clock%d.png"%conf)
697 icon = QIcon(":icons/confirmed.png")
699 if value is not None:
700 v_str = self.format_amount(value, True, whitespaces=True)
704 balance_str = self.format_amount(balance, whitespaces=True)
707 label, is_default_label = self.wallet.get_label(tx_hash)
709 label = _('Pruned transaction outputs')
710 is_default_label = False
712 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
713 item.setFont(2, QFont(MONOSPACE_FONT))
714 item.setFont(3, QFont(MONOSPACE_FONT))
715 item.setFont(4, QFont(MONOSPACE_FONT))
717 item.setForeground(3, QBrush(QColor("#BC1E1E")))
719 item.setData(0, Qt.UserRole, tx_hash)
720 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
722 item.setForeground(2, QBrush(QColor('grey')))
724 item.setIcon(0, icon)
725 self.history_list.insertTopLevelItem(0,item)
728 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
731 def create_send_tab(self):
736 grid.setColumnMinimumWidth(3,300)
737 grid.setColumnStretch(5,1)
740 self.payto_e = QLineEdit()
741 grid.addWidget(QLabel(_('Pay to')), 1, 0)
742 grid.addWidget(self.payto_e, 1, 1, 1, 3)
744 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)
746 completer = QCompleter()
747 completer.setCaseSensitivity(False)
748 self.payto_e.setCompleter(completer)
749 completer.setModel(self.completions)
751 self.message_e = QLineEdit()
752 grid.addWidget(QLabel(_('Description')), 2, 0)
753 grid.addWidget(self.message_e, 2, 1, 1, 3)
754 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)
756 self.from_label = QLabel(_('From'))
757 grid.addWidget(self.from_label, 3, 0)
758 self.from_list = QTreeWidget(self)
759 self.from_list.setColumnCount(2)
760 self.from_list.setColumnWidth(0, 350)
761 self.from_list.setColumnWidth(1, 50)
762 self.from_list.setHeaderHidden (True)
763 self.from_list.setMaximumHeight(80)
764 grid.addWidget(self.from_list, 3, 1, 1, 3)
765 self.set_pay_from([])
767 self.amount_e = AmountEdit(self.base_unit)
768 grid.addWidget(QLabel(_('Amount')), 4, 0)
769 grid.addWidget(self.amount_e, 4, 1, 1, 2)
770 grid.addWidget(HelpButton(
771 _('Amount to be sent.') + '\n\n' \
772 + _('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.') \
773 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
775 self.fee_e = AmountEdit(self.base_unit)
776 grid.addWidget(QLabel(_('Fee')), 5, 0)
777 grid.addWidget(self.fee_e, 5, 1, 1, 2)
778 grid.addWidget(HelpButton(
779 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
780 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
781 + _('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)
784 self.send_button = EnterButton(_("Send"), self.do_send)
785 grid.addWidget(self.send_button, 6, 1)
787 b = EnterButton(_("Clear"),self.do_clear)
788 grid.addWidget(b, 6, 2)
790 self.payto_sig = QLabel('')
791 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
793 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
794 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
803 def entry_changed( is_fee ):
804 self.funds_error = False
806 if self.amount_e.is_shortcut:
807 self.amount_e.is_shortcut = False
808 sendable = self.get_sendable_balance()
809 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, self.get_payment_sources())
810 fee = self.wallet.estimated_fee(inputs)
812 self.amount_e.setText( self.format_amount(amount) )
813 self.fee_e.setText( self.format_amount( fee ) )
816 amount = self.read_amount(str(self.amount_e.text()))
817 fee = self.read_amount(str(self.fee_e.text()))
819 if not is_fee: fee = None
822 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, self.get_payment_sources())
824 self.fee_e.setText( self.format_amount( fee ) )
827 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
831 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
832 self.funds_error = True
833 text = _( "Not enough funds" )
834 c, u = self.wallet.get_frozen_balance()
835 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
837 self.statusBar().showMessage(text)
838 self.amount_e.setPalette(palette)
839 self.fee_e.setPalette(palette)
841 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
842 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
844 run_hook('create_send_tab', grid)
848 def set_pay_from(self, l):
850 self.from_list.clear()
851 self.from_label.setHidden(len(self.pay_from) == 0)
852 self.from_list.setHidden(len(self.pay_from) == 0)
853 for addr in self.pay_from:
854 c, u = self.wallet.get_addr_balance(addr)
855 balance = self.format_amount(c + u)
856 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
859 def update_completions(self):
861 for addr,label in self.wallet.labels.items():
862 if addr in self.wallet.addressbook:
863 l.append( label + ' <' + addr + '>')
865 run_hook('update_completions', l)
866 self.completions.setStringList(l)
870 return lambda s, *args: s.do_protect(func, args)
875 label = unicode( self.message_e.text() )
876 r = unicode( self.payto_e.text() )
879 # label or alias, with address in brackets
880 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
881 to_address = m.group(2) if m else r
883 if not is_valid(to_address):
884 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
888 amount = self.read_amount(unicode( self.amount_e.text()))
890 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
893 fee = self.read_amount(unicode( self.fee_e.text()))
895 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
898 confirm_amount = self.config.get('confirm_amount', 100000000)
899 if amount >= confirm_amount:
900 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
903 confirm_fee = self.config.get('confirm_fee', 100000)
904 if fee >= confirm_fee:
905 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()}):
908 self.send_tx(to_address, amount, fee, label)
912 def send_tx(self, to_address, amount, fee, label, password):
914 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
915 domain=self.get_payment_sources())
916 except Exception as e:
917 traceback.print_exc(file=sys.stdout)
918 self.show_message(str(e))
921 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
922 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
926 self.wallet.set_label(tx.hash(), label)
929 h = self.wallet.send_tx(tx)
930 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
931 status, msg = self.wallet.receive_tx( h, tx )
933 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
935 self.update_contacts_tab()
937 QMessageBox.warning(self, _('Error'), msg, _('OK'))
940 self.show_transaction(tx)
942 # add recipient to addressbook
943 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
944 self.wallet.addressbook.append(to_address)
949 def set_url(self, url):
950 address, amount, label, message, signature, identity, url = util.parse_url(url)
953 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
954 elif amount: amount = str(Decimal(amount))
957 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
960 self.mini.set_payment_fields(address, amount)
962 if label and self.wallet.labels.get(address) != label:
963 if self.question('Give label "%s" to address %s ?'%(label,address)):
964 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
965 self.wallet.addressbook.append(address)
966 self.wallet.set_label(address, label)
968 run_hook('set_url', url, self.show_message, self.question)
970 self.tabs.setCurrentIndex(1)
971 label = self.wallet.labels.get(address)
972 m_addr = label + ' <'+ address +'>' if label else address
973 self.payto_e.setText(m_addr)
975 self.message_e.setText(message)
977 self.amount_e.setText(amount)
980 self.set_frozen(self.payto_e,True)
981 self.set_frozen(self.amount_e,True)
982 self.set_frozen(self.message_e,True)
983 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
985 self.payto_sig.setVisible(False)
988 self.payto_sig.setVisible(False)
989 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
991 self.set_frozen(e,False)
993 self.set_pay_from([])
996 def set_frozen(self,entry,frozen):
998 entry.setReadOnly(True)
999 entry.setFrame(False)
1000 palette = QPalette()
1001 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1002 entry.setPalette(palette)
1004 entry.setReadOnly(False)
1005 entry.setFrame(True)
1006 palette = QPalette()
1007 palette.setColor(entry.backgroundRole(), QColor('white'))
1008 entry.setPalette(palette)
1011 def set_addrs_frozen(self,addrs,freeze):
1013 if not addr: continue
1014 if addr in self.wallet.frozen_addresses and not freeze:
1015 self.wallet.unfreeze(addr)
1016 elif addr not in self.wallet.frozen_addresses and freeze:
1017 self.wallet.freeze(addr)
1018 self.update_receive_tab()
1022 def create_list_tab(self, headers):
1023 "generic tab creation method"
1024 l = MyTreeWidget(self)
1025 l.setColumnCount( len(headers) )
1026 l.setHeaderLabels( headers )
1029 vbox = QVBoxLayout()
1036 vbox.addWidget(buttons)
1038 hbox = QHBoxLayout()
1041 buttons.setLayout(hbox)
1046 def create_receive_tab(self):
1047 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1048 l.setContextMenuPolicy(Qt.CustomContextMenu)
1049 l.customContextMenuRequested.connect(self.create_receive_menu)
1050 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1051 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1052 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1053 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1054 self.receive_list = l
1055 self.receive_buttons_hbox = hbox
1062 def save_column_widths(self):
1063 self.column_widths["receive"] = []
1064 for i in range(self.receive_list.columnCount() -1):
1065 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1067 self.column_widths["history"] = []
1068 for i in range(self.history_list.columnCount() - 1):
1069 self.column_widths["history"].append(self.history_list.columnWidth(i))
1071 self.column_widths["contacts"] = []
1072 for i in range(self.contacts_list.columnCount() - 1):
1073 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1075 self.config.set_key("column_widths_2", self.column_widths, True)
1078 def create_contacts_tab(self):
1079 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1080 l.setContextMenuPolicy(Qt.CustomContextMenu)
1081 l.customContextMenuRequested.connect(self.create_contact_menu)
1082 for i,width in enumerate(self.column_widths['contacts']):
1083 l.setColumnWidth(i, width)
1085 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1086 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1087 self.contacts_list = l
1088 self.contacts_buttons_hbox = hbox
1093 def delete_imported_key(self, addr):
1094 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1095 self.wallet.delete_imported_key(addr)
1096 self.update_receive_tab()
1097 self.update_history_tab()
1099 def edit_account_label(self, k):
1100 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1102 label = unicode(text)
1103 self.wallet.set_label(k,label)
1104 self.update_receive_tab()
1106 def account_set_expanded(self, item, k, b):
1108 self.accounts_expanded[k] = b
1110 def create_account_menu(self, position, k, item):
1112 if item.isExpanded():
1113 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1115 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1116 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1117 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1118 if self.wallet.account_is_pending(k):
1119 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1120 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1122 def delete_pending_account(self, k):
1123 self.wallet.delete_pending_account(k)
1124 self.update_receive_tab()
1126 def create_receive_menu(self, position):
1127 # fixme: this function apparently has a side effect.
1128 # if it is not called the menu pops up several times
1129 #self.receive_list.selectedIndexes()
1131 selected = self.receive_list.selectedItems()
1132 multi_select = len(selected) > 1
1133 addrs = [unicode(item.text(0)) for item in selected]
1134 if not multi_select:
1135 item = self.receive_list.itemAt(position)
1139 if not is_valid(addr):
1140 k = str(item.data(0,32).toString())
1142 self.create_account_menu(position, k, item)
1144 item.setExpanded(not item.isExpanded())
1148 if not multi_select:
1149 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1150 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1151 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1152 if self.wallet.seed:
1153 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1154 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1155 if addr in self.wallet.imported_keys:
1156 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1158 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1159 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1160 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1161 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1163 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1164 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1166 run_hook('receive_menu', menu, addrs)
1167 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1170 def get_sendable_balance(self):
1171 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1174 def get_payment_sources(self):
1176 return self.pay_from
1178 return self.wallet.get_account_addresses(self.current_account)
1181 def send_from_addresses(self, addrs):
1182 self.set_pay_from( addrs )
1183 self.tabs.setCurrentIndex(1)
1186 def payto(self, addr):
1188 label = self.wallet.labels.get(addr)
1189 m_addr = label + ' <' + addr + '>' if label else addr
1190 self.tabs.setCurrentIndex(1)
1191 self.payto_e.setText(m_addr)
1192 self.amount_e.setFocus()
1195 def delete_contact(self, x):
1196 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1197 self.wallet.delete_contact(x)
1198 self.wallet.set_label(x, None)
1199 self.update_history_tab()
1200 self.update_contacts_tab()
1201 self.update_completions()
1204 def create_contact_menu(self, position):
1205 item = self.contacts_list.itemAt(position)
1208 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1210 addr = unicode(item.text(0))
1211 label = unicode(item.text(1))
1212 is_editable = item.data(0,32).toBool()
1213 payto_addr = item.data(0,33).toString()
1214 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1215 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1216 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1218 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1219 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1221 run_hook('create_contact_menu', menu, item)
1222 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1225 def update_receive_item(self, item):
1226 item.setFont(0, QFont(MONOSPACE_FONT))
1227 address = str(item.data(0,0).toString())
1228 label = self.wallet.labels.get(address,'')
1229 item.setData(1,0,label)
1230 item.setData(0,32, True) # is editable
1232 run_hook('update_receive_item', address, item)
1234 if not self.wallet.is_mine(address): return
1236 c, u = self.wallet.get_addr_balance(address)
1237 balance = self.format_amount(c + u)
1238 item.setData(2,0,balance)
1240 if address in self.wallet.frozen_addresses:
1241 item.setBackgroundColor(0, QColor('lightblue'))
1244 def update_receive_tab(self):
1245 l = self.receive_list
1248 l.setColumnHidden(2, False)
1249 l.setColumnHidden(3, False)
1250 for i,width in enumerate(self.column_widths['receive']):
1251 l.setColumnWidth(i, width)
1253 if self.current_account is None:
1254 account_items = self.wallet.accounts.items()
1255 elif self.current_account != -1:
1256 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1260 for k, account in account_items:
1261 name = self.wallet.get_account_name(k)
1262 c,u = self.wallet.get_account_balance(k)
1263 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1264 l.addTopLevelItem(account_item)
1265 account_item.setExpanded(self.accounts_expanded.get(k, True))
1266 account_item.setData(0, 32, k)
1268 if not self.wallet.is_seeded(k):
1269 icon = QIcon(":icons/key.png")
1270 account_item.setIcon(0, icon)
1272 for is_change in ([0,1]):
1273 name = _("Receiving") if not is_change else _("Change")
1274 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1275 account_item.addChild(seq_item)
1276 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1278 if not is_change: seq_item.setExpanded(True)
1283 for address in account.get_addresses(is_change):
1284 h = self.wallet.history.get(address,[])
1288 if gap > self.wallet.gap_limit:
1293 c, u = self.wallet.get_addr_balance(address)
1294 num_tx = '*' if h == ['*'] else "%d"%len(h)
1295 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1296 self.update_receive_item(item)
1298 item.setBackgroundColor(1, QColor('red'))
1299 if len(h) > 0 and c == -u:
1301 seq_item.insertChild(0,used_item)
1303 used_item.addChild(item)
1305 seq_item.addChild(item)
1308 for k, addr in self.wallet.get_pending_accounts():
1309 name = self.wallet.labels.get(k,'')
1310 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1311 self.update_receive_item(item)
1312 l.addTopLevelItem(account_item)
1313 account_item.setExpanded(True)
1314 account_item.setData(0, 32, k)
1315 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1316 account_item.addChild(item)
1317 self.update_receive_item(item)
1320 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1321 c,u = self.wallet.get_imported_balance()
1322 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1323 l.addTopLevelItem(account_item)
1324 account_item.setExpanded(True)
1325 for address in self.wallet.imported_keys.keys():
1326 item = QTreeWidgetItem( [ address, '', '', ''] )
1327 self.update_receive_item(item)
1328 account_item.addChild(item)
1331 # we use column 1 because column 0 may be hidden
1332 l.setCurrentItem(l.topLevelItem(0),1)
1335 def update_contacts_tab(self):
1336 l = self.contacts_list
1339 for address in self.wallet.addressbook:
1340 label = self.wallet.labels.get(address,'')
1341 n = self.wallet.get_num_tx(address)
1342 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1343 item.setFont(0, QFont(MONOSPACE_FONT))
1344 # 32 = label can be edited (bool)
1345 item.setData(0,32, True)
1347 item.setData(0,33, address)
1348 l.addTopLevelItem(item)
1350 run_hook('update_contacts_tab', l)
1351 l.setCurrentItem(l.topLevelItem(0))
1355 def create_console_tab(self):
1356 from console import Console
1357 self.console = console = Console()
1361 def update_console(self):
1362 console = self.console
1363 console.history = self.config.get("console-history",[])
1364 console.history_index = len(console.history)
1366 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1367 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1369 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1371 def mkfunc(f, method):
1372 return lambda *args: apply( f, (method, args, self.password_dialog ))
1374 if m[0]=='_' or m in ['network','wallet']: continue
1375 methods[m] = mkfunc(c._run, m)
1377 console.updateNamespace(methods)
1380 def change_account(self,s):
1381 if s == _("All accounts"):
1382 self.current_account = None
1384 accounts = self.wallet.get_account_names()
1385 for k, v in accounts.items():
1387 self.current_account = k
1388 self.update_history_tab()
1389 self.update_status()
1390 self.update_receive_tab()
1392 def create_status_bar(self):
1395 sb.setFixedHeight(35)
1396 qtVersion = qVersion()
1398 self.balance_label = QLabel("")
1399 sb.addWidget(self.balance_label)
1401 from version_getter import UpdateLabel
1402 self.updatelabel = UpdateLabel(self.config, sb)
1404 self.account_selector = QComboBox()
1405 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1406 sb.addPermanentWidget(self.account_selector)
1408 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1409 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1411 self.lock_icon = QIcon()
1412 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1413 sb.addPermanentWidget( self.password_button )
1415 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1416 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1417 sb.addPermanentWidget( self.seed_button )
1418 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1419 sb.addPermanentWidget( self.status_button )
1421 run_hook('create_status_bar', (sb,))
1423 self.setStatusBar(sb)
1426 def update_lock_icon(self):
1427 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1428 self.password_button.setIcon( icon )
1431 def update_buttons_on_seed(self):
1432 if not self.wallet.is_watching_only():
1433 self.seed_button.show()
1434 self.password_button.show()
1435 self.send_button.setText(_("Send"))
1437 self.password_button.hide()
1438 self.seed_button.hide()
1439 self.send_button.setText(_("Create unsigned transaction"))
1442 def change_password_dialog(self):
1443 from password_dialog import PasswordDialog
1444 d = PasswordDialog(self.wallet, self)
1446 self.update_lock_icon()
1449 def new_contact_dialog(self):
1452 vbox = QVBoxLayout(d)
1453 vbox.addWidget(QLabel(_('New Contact')+':'))
1455 grid = QGridLayout()
1458 grid.addWidget(QLabel(_("Address")), 1, 0)
1459 grid.addWidget(line1, 1, 1)
1460 grid.addWidget(QLabel(_("Name")), 2, 0)
1461 grid.addWidget(line2, 2, 1)
1463 vbox.addLayout(grid)
1464 vbox.addLayout(ok_cancel_buttons(d))
1469 address = str(line1.text())
1470 label = unicode(line2.text())
1472 if not is_valid(address):
1473 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1476 self.wallet.add_contact(address)
1478 self.wallet.set_label(address, label)
1480 self.update_contacts_tab()
1481 self.update_history_tab()
1482 self.update_completions()
1483 self.tabs.setCurrentIndex(3)
1486 def new_account_dialog(self):
1488 dialog = QDialog(self)
1490 dialog.setWindowTitle(_("New Account"))
1492 vbox = QVBoxLayout()
1493 vbox.addWidget(QLabel(_('Account name')+':'))
1496 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1497 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1502 vbox.addLayout(ok_cancel_buttons(dialog))
1503 dialog.setLayout(vbox)
1507 name = str(e.text())
1510 self.wallet.create_pending_account('1', name)
1511 self.update_receive_tab()
1512 self.tabs.setCurrentIndex(2)
1516 def show_master_public_key_old(self):
1517 dialog = QDialog(self)
1519 dialog.setWindowTitle(_("Master Public Key"))
1521 main_text = QTextEdit()
1522 main_text.setText(self.wallet.get_master_public_key())
1523 main_text.setReadOnly(True)
1524 main_text.setMaximumHeight(170)
1525 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1527 ok_button = QPushButton(_("OK"))
1528 ok_button.setDefault(True)
1529 ok_button.clicked.connect(dialog.accept)
1531 main_layout = QGridLayout()
1532 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1534 main_layout.addWidget(main_text, 1, 0)
1535 main_layout.addWidget(qrw, 1, 1 )
1537 vbox = QVBoxLayout()
1538 vbox.addLayout(main_layout)
1539 vbox.addLayout(close_button(dialog))
1540 dialog.setLayout(vbox)
1544 def show_master_public_key(self):
1546 if self.wallet.seed_version == 4:
1547 self.show_master_public_key_old()
1550 dialog = QDialog(self)
1552 dialog.setWindowTitle(_("Master Public Keys"))
1554 chain_text = QTextEdit()
1555 chain_text.setReadOnly(True)
1556 chain_text.setMaximumHeight(170)
1557 chain_qrw = QRCodeWidget()
1559 mpk_text = QTextEdit()
1560 mpk_text.setReadOnly(True)
1561 mpk_text.setMaximumHeight(170)
1562 mpk_qrw = QRCodeWidget()
1564 main_layout = QGridLayout()
1566 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1567 main_layout.addWidget(mpk_text, 1, 1)
1568 main_layout.addWidget(mpk_qrw, 1, 2)
1570 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1571 main_layout.addWidget(chain_text, 2, 1)
1572 main_layout.addWidget(chain_qrw, 2, 2)
1575 c, K, cK = self.wallet.master_public_keys[str(key)]
1576 chain_text.setText(c)
1577 chain_qrw.set_addr(c)
1578 chain_qrw.update_qr()
1583 key_selector = QComboBox()
1584 keys = sorted(self.wallet.master_public_keys.keys())
1585 key_selector.addItems(keys)
1587 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1588 main_layout.addWidget(key_selector, 0, 1)
1589 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1593 vbox = QVBoxLayout()
1594 vbox.addLayout(main_layout)
1595 vbox.addLayout(close_button(dialog))
1597 dialog.setLayout(vbox)
1602 def show_seed_dialog(self, password):
1603 if self.wallet.is_watching_only():
1604 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1607 if self.wallet.seed:
1609 mnemonic = self.wallet.get_mnemonic(password)
1611 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1613 from seed_dialog import SeedDialog
1614 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1618 for k in self.wallet.master_private_keys.keys():
1619 pk = self.wallet.get_master_private_key(k, password)
1621 from seed_dialog import PrivateKeysDialog
1622 d = PrivateKeysDialog(self,l)
1629 def show_qrcode(self, data, title = _("QR code")):
1633 d.setWindowTitle(title)
1634 d.setMinimumSize(270, 300)
1635 vbox = QVBoxLayout()
1636 qrw = QRCodeWidget(data)
1637 vbox.addWidget(qrw, 1)
1638 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1639 hbox = QHBoxLayout()
1642 filename = os.path.join(self.config.path, "qrcode.bmp")
1645 bmp.save_qrcode(qrw.qr, filename)
1646 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1648 def copy_to_clipboard():
1649 bmp.save_qrcode(qrw.qr, filename)
1650 self.app.clipboard().setImage(QImage(filename))
1651 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1653 b = QPushButton(_("Copy"))
1655 b.clicked.connect(copy_to_clipboard)
1657 b = QPushButton(_("Save"))
1659 b.clicked.connect(print_qr)
1661 b = QPushButton(_("Close"))
1663 b.clicked.connect(d.accept)
1666 vbox.addLayout(hbox)
1671 def do_protect(self, func, args):
1672 if self.wallet.use_encryption:
1673 password = self.password_dialog()
1679 if args != (False,):
1680 args = (self,) + args + (password,)
1682 args = (self,password)
1687 def show_private_key(self, address, password):
1688 if not address: return
1690 pk_list = self.wallet.get_private_key(address, password)
1691 except Exception as e:
1692 self.show_message(str(e))
1696 d.setMinimumSize(600, 200)
1698 vbox = QVBoxLayout()
1699 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1700 vbox.addWidget( QLabel(_("Private key") + ':'))
1702 keys.setReadOnly(True)
1703 keys.setText('\n'.join(pk_list))
1704 vbox.addWidget(keys)
1705 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1706 vbox.addLayout(close_button(d))
1712 def do_sign(self, address, message, signature, password):
1713 message = unicode(message.toPlainText())
1714 message = message.encode('utf-8')
1716 sig = self.wallet.sign_message(str(address.text()), message, password)
1717 signature.setText(sig)
1718 except Exception as e:
1719 self.show_message(str(e))
1721 def sign_message(self, address):
1722 if not address: return
1725 d.setWindowTitle(_('Sign Message'))
1726 d.setMinimumSize(410, 290)
1728 tab_widget = QTabWidget()
1730 layout = QGridLayout(tab)
1732 sign_address = QLineEdit()
1734 sign_address.setText(address)
1735 layout.addWidget(QLabel(_('Address')), 1, 0)
1736 layout.addWidget(sign_address, 1, 1)
1738 sign_message = QTextEdit()
1739 layout.addWidget(QLabel(_('Message')), 2, 0)
1740 layout.addWidget(sign_message, 2, 1)
1741 layout.setRowStretch(2,3)
1743 sign_signature = QTextEdit()
1744 layout.addWidget(QLabel(_('Signature')), 3, 0)
1745 layout.addWidget(sign_signature, 3, 1)
1746 layout.setRowStretch(3,1)
1749 hbox = QHBoxLayout()
1750 b = QPushButton(_("Sign"))
1752 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1753 b = QPushButton(_("Close"))
1754 b.clicked.connect(d.accept)
1756 layout.addLayout(hbox, 4, 1)
1757 tab_widget.addTab(tab, _("Sign"))
1761 layout = QGridLayout(tab)
1763 verify_address = QLineEdit()
1764 layout.addWidget(QLabel(_('Address')), 1, 0)
1765 layout.addWidget(verify_address, 1, 1)
1767 verify_message = QTextEdit()
1768 layout.addWidget(QLabel(_('Message')), 2, 0)
1769 layout.addWidget(verify_message, 2, 1)
1770 layout.setRowStretch(2,3)
1772 verify_signature = QTextEdit()
1773 layout.addWidget(QLabel(_('Signature')), 3, 0)
1774 layout.addWidget(verify_signature, 3, 1)
1775 layout.setRowStretch(3,1)
1778 message = unicode(verify_message.toPlainText())
1779 message = message.encode('utf-8')
1780 if bitcoin.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1781 self.show_message(_("Signature verified"))
1783 self.show_message(_("Error: wrong signature"))
1785 hbox = QHBoxLayout()
1786 b = QPushButton(_("Verify"))
1787 b.clicked.connect(do_verify)
1789 b = QPushButton(_("Close"))
1790 b.clicked.connect(d.accept)
1792 layout.addLayout(hbox, 4, 1)
1793 tab_widget.addTab(tab, _("Verify"))
1795 vbox = QVBoxLayout()
1796 vbox.addWidget(tab_widget)
1803 def question(self, msg):
1804 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1806 def show_message(self, msg):
1807 QMessageBox.information(self, _('Message'), msg, _('OK'))
1809 def password_dialog(self ):
1816 vbox = QVBoxLayout()
1817 msg = _('Please enter your password')
1818 vbox.addWidget(QLabel(msg))
1820 grid = QGridLayout()
1822 grid.addWidget(QLabel(_('Password')), 1, 0)
1823 grid.addWidget(pw, 1, 1)
1824 vbox.addLayout(grid)
1826 vbox.addLayout(ok_cancel_buttons(d))
1829 run_hook('password_dialog', pw, grid, 1)
1830 if not d.exec_(): return
1831 return unicode(pw.text())
1840 def tx_from_text(self, txt):
1841 "json or raw hexadecimal"
1844 tx = Transaction(txt)
1850 tx_dict = json.loads(str(txt))
1851 assert "hex" in tx_dict.keys()
1852 assert "complete" in tx_dict.keys()
1853 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1854 if not tx_dict["complete"]:
1855 assert "input_info" in tx_dict.keys()
1856 input_info = json.loads(tx_dict['input_info'])
1857 tx.add_input_info(input_info)
1862 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1866 def read_tx_from_file(self):
1867 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1871 with open(fileName, "r") as f:
1872 file_content = f.read()
1873 except (ValueError, IOError, os.error), reason:
1874 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1876 return self.tx_from_text(file_content)
1880 def sign_raw_transaction(self, tx, input_info, password):
1881 self.wallet.signrawtransaction(tx, input_info, [], password)
1883 def do_process_from_text(self):
1884 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1887 tx = self.tx_from_text(text)
1889 self.show_transaction(tx)
1891 def do_process_from_file(self):
1892 tx = self.read_tx_from_file()
1894 self.show_transaction(tx)
1896 def do_process_from_txid(self):
1897 from electrum import transaction
1898 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1900 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1902 tx = transaction.Transaction(r)
1904 self.show_transaction(tx)
1906 self.show_message("unknown transaction")
1908 def do_process_from_csvReader(self, csvReader):
1913 for position, row in enumerate(csvReader):
1915 if not is_valid(address):
1916 errors.append((position, address))
1918 amount = Decimal(row[1])
1919 amount = int(100000000*amount)
1920 outputs.append((address, amount))
1921 except (ValueError, IOError, os.error), reason:
1922 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1926 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1927 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1931 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1932 except Exception as e:
1933 self.show_message(str(e))
1936 self.show_transaction(tx)
1938 def do_process_from_csv_file(self):
1939 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1943 with open(fileName, "r") as f:
1944 csvReader = csv.reader(f)
1945 self.do_process_from_csvReader(csvReader)
1946 except (ValueError, IOError, os.error), reason:
1947 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1950 def do_process_from_csv_text(self):
1951 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1952 + _("Format: address, amount. One output per line"), _("Load CSV"))
1955 f = StringIO.StringIO(text)
1956 csvReader = csv.reader(f)
1957 self.do_process_from_csvReader(csvReader)
1962 def do_export_privkeys(self, password):
1963 if not self.wallet.seed:
1964 self.show_message(_("This wallet has no seed"))
1967 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.")))
1970 select_export = _('Select file to export your private keys to')
1971 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1973 with open(fileName, "w+") as csvfile:
1974 transaction = csv.writer(csvfile)
1975 transaction.writerow(["address", "private_key"])
1977 addresses = self.wallet.addresses(True)
1979 for addr in addresses:
1980 pk = "".join(self.wallet.get_private_key(addr, password))
1981 transaction.writerow(["%34s"%addr,pk])
1983 self.show_message(_("Private keys exported."))
1985 except (IOError, os.error), reason:
1986 export_error_label = _("Electrum was unable to produce a private key-export.")
1987 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1989 except Exception as e:
1990 self.show_message(str(e))
1994 def do_import_labels(self):
1995 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1996 if not labelsFile: return
1998 f = open(labelsFile, 'r')
2001 for key, value in json.loads(data).items():
2002 self.wallet.set_label(key, value)
2003 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2004 except (IOError, os.error), reason:
2005 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2008 def do_export_labels(self):
2009 labels = self.wallet.labels
2011 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2013 with open(fileName, 'w+') as f:
2014 json.dump(labels, f)
2015 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2016 except (IOError, os.error), reason:
2017 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2020 def do_export_history(self):
2021 from lite_window import csv_transaction
2022 csv_transaction(self.wallet)
2026 def do_import_privkey(self, password):
2027 if not self.wallet.imported_keys:
2028 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2029 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2030 + _('Are you sure you understand what you are doing?'), 3, 4)
2033 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2036 text = str(text).split()
2041 addr = self.wallet.import_key(key, password)
2042 except Exception as e:
2048 addrlist.append(addr)
2050 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2052 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2053 self.update_receive_tab()
2054 self.update_history_tab()
2057 def settings_dialog(self):
2059 d.setWindowTitle(_('Electrum Settings'))
2061 vbox = QVBoxLayout()
2062 grid = QGridLayout()
2063 grid.setColumnStretch(0,1)
2065 nz_label = QLabel(_('Display zeros') + ':')
2066 grid.addWidget(nz_label, 0, 0)
2067 nz_e = AmountEdit(None,True)
2068 nz_e.setText("%d"% self.num_zeros)
2069 grid.addWidget(nz_e, 0, 1)
2070 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2071 grid.addWidget(HelpButton(msg), 0, 2)
2072 if not self.config.is_modifiable('num_zeros'):
2073 for w in [nz_e, nz_label]: w.setEnabled(False)
2075 lang_label=QLabel(_('Language') + ':')
2076 grid.addWidget(lang_label, 1, 0)
2077 lang_combo = QComboBox()
2078 from electrum.i18n import languages
2079 lang_combo.addItems(languages.values())
2081 index = languages.keys().index(self.config.get("language",''))
2084 lang_combo.setCurrentIndex(index)
2085 grid.addWidget(lang_combo, 1, 1)
2086 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2087 if not self.config.is_modifiable('language'):
2088 for w in [lang_combo, lang_label]: w.setEnabled(False)
2091 fee_label = QLabel(_('Transaction fee') + ':')
2092 grid.addWidget(fee_label, 2, 0)
2093 fee_e = AmountEdit(self.base_unit)
2094 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2095 grid.addWidget(fee_e, 2, 1)
2096 msg = _('Fee per kilobyte of transaction.') + ' ' \
2097 + _('Recommended value') + ': ' + self.format_amount(20000)
2098 grid.addWidget(HelpButton(msg), 2, 2)
2099 if not self.config.is_modifiable('fee_per_kb'):
2100 for w in [fee_e, fee_label]: w.setEnabled(False)
2102 units = ['BTC', 'mBTC']
2103 unit_label = QLabel(_('Base unit') + ':')
2104 grid.addWidget(unit_label, 3, 0)
2105 unit_combo = QComboBox()
2106 unit_combo.addItems(units)
2107 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2108 grid.addWidget(unit_combo, 3, 1)
2109 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2110 + '\n1BTC=1000mBTC.\n' \
2111 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2113 usechange_cb = QCheckBox(_('Use change addresses'))
2114 usechange_cb.setChecked(self.wallet.use_change)
2115 grid.addWidget(usechange_cb, 4, 0)
2116 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2117 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2119 grid.setRowStretch(5,1)
2121 vbox.addLayout(grid)
2122 vbox.addLayout(ok_cancel_buttons(d))
2126 if not d.exec_(): return
2128 fee = unicode(fee_e.text())
2130 fee = self.read_amount(fee)
2132 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2135 self.wallet.set_fee(fee)
2137 nz = unicode(nz_e.text())
2142 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2145 if self.num_zeros != nz:
2147 self.config.set_key('num_zeros', nz, True)
2148 self.update_history_tab()
2149 self.update_receive_tab()
2151 usechange_result = usechange_cb.isChecked()
2152 if self.wallet.use_change != usechange_result:
2153 self.wallet.use_change = usechange_result
2154 self.wallet.storage.put('use_change', self.wallet.use_change)
2156 unit_result = units[unit_combo.currentIndex()]
2157 if self.base_unit() != unit_result:
2158 self.decimal_point = 8 if unit_result == 'BTC' else 5
2159 self.config.set_key('decimal_point', self.decimal_point, True)
2160 self.update_history_tab()
2161 self.update_status()
2163 need_restart = False
2165 lang_request = languages.keys()[lang_combo.currentIndex()]
2166 if lang_request != self.config.get('language'):
2167 self.config.set_key("language", lang_request, True)
2170 run_hook('close_settings_dialog')
2173 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2176 def run_network_dialog(self):
2177 if not self.network:
2179 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2181 def closeEvent(self, event):
2184 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2185 self.save_column_widths()
2186 self.config.set_key("console-history", self.console.history[-50:], True)
2187 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2192 def plugins_dialog(self):
2193 from electrum.plugins import plugins
2196 d.setWindowTitle(_('Electrum Plugins'))
2199 vbox = QVBoxLayout(d)
2202 scroll = QScrollArea()
2203 scroll.setEnabled(True)
2204 scroll.setWidgetResizable(True)
2205 scroll.setMinimumSize(400,250)
2206 vbox.addWidget(scroll)
2210 w.setMinimumHeight(len(plugins)*35)
2212 grid = QGridLayout()
2213 grid.setColumnStretch(0,1)
2216 def do_toggle(cb, p, w):
2219 if w: w.setEnabled(r)
2221 def mk_toggle(cb, p, w):
2222 return lambda: do_toggle(cb,p,w)
2224 for i, p in enumerate(plugins):
2226 cb = QCheckBox(p.fullname())
2227 cb.setDisabled(not p.is_available())
2228 cb.setChecked(p.is_enabled())
2229 grid.addWidget(cb, i, 0)
2230 if p.requires_settings():
2231 w = p.settings_widget(self)
2232 w.setEnabled( p.is_enabled() )
2233 grid.addWidget(w, i, 1)
2236 cb.clicked.connect(mk_toggle(cb,p,w))
2237 grid.addWidget(HelpButton(p.description()), i, 2)
2239 print_msg(_("Error: cannot display plugin"), p)
2240 traceback.print_exc(file=sys.stdout)
2241 grid.setRowStretch(i+1,1)
2243 vbox.addLayout(close_button(d))
2248 def show_account_details(self, k):
2250 d.setWindowTitle(_('Account Details'))
2253 vbox = QVBoxLayout(d)
2254 roots = self.wallet.get_roots(k)
2256 name = self.wallet.get_account_name(k)
2257 label = QLabel('Name: ' + name)
2258 vbox.addWidget(label)
2260 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2261 vbox.addWidget(QLabel('Type: ' + acctype))
2263 label = QLabel('Derivation: ' + k)
2264 vbox.addWidget(label)
2267 # mpk = self.wallet.master_public_keys[root]
2268 # text = QTextEdit()
2269 # text.setReadOnly(True)
2270 # text.setMaximumHeight(120)
2271 # text.setText(repr(mpk))
2272 # vbox.addWidget(text)
2274 vbox.addLayout(close_button(d))