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():
120 def __init__(self, config, network):
121 QMainWindow.__init__(self)
124 self.network = network
126 self._close_electrum = False
129 if sys.platform == 'darwin':
130 self.icon = QIcon(":icons/electrum_dark_icon.png")
131 #self.icon = QIcon(":icons/lock.png")
133 self.icon = QIcon(':icons/electrum_light_icon.png')
135 self.tray = QSystemTrayIcon(self.icon, self)
136 self.tray.setToolTip('Electrum')
137 self.tray.activated.connect(self.tray_activated)
141 self.create_status_bar()
143 self.need_update = threading.Event()
145 self.decimal_point = config.get('decimal_point', 8)
146 self.num_zeros = int(config.get('num_zeros',0))
148 set_language(config.get('language'))
150 self.funds_error = False
151 self.completions = QStringListModel()
153 self.tabs = tabs = QTabWidget(self)
154 self.column_widths = self.config.get("column_widths_2", default_column_widths )
155 tabs.addTab(self.create_history_tab(), _('History') )
156 tabs.addTab(self.create_send_tab(), _('Send') )
157 tabs.addTab(self.create_receive_tab(), _('Receive') )
158 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
159 tabs.addTab(self.create_console_tab(), _('Console') )
160 tabs.setMinimumSize(600, 400)
161 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
162 self.setCentralWidget(tabs)
164 g = self.config.get("winpos-qt",[100, 100, 840, 400])
165 self.setGeometry(g[0], g[1], g[2], g[3])
167 self.setWindowIcon(QIcon(":icons/electrum.png"))
170 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
171 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
172 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
173 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
174 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
176 for i in range(tabs.count()):
177 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
179 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
180 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
181 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
183 self.history_list.setFocus(True)
187 self.network.register_callback('updated', lambda: self.need_update.set())
188 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
189 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
190 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
191 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
193 # set initial message
194 self.console.showMessage(self.network.banner)
201 self.config.set_key('lite_mode', False, True)
207 self.config.set_key('lite_mode', True, True)
215 if not self.check_qt_version():
216 if self.config.get('lite_mode') is True:
217 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
218 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
219 self.config.set_key('lite_mode', False, True)
226 actuator = lite_window.MiniActuator(self)
228 actuator.load_theme()
230 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
232 driver = lite_window.MiniDriver(self, self.mini)
234 if self.config.get('lite_mode') is True:
240 def check_qt_version(self):
241 qtVersion = qVersion()
242 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
245 def update_account_selector(self):
247 accounts = self.wallet.get_account_names()
248 self.account_selector.clear()
249 if len(accounts) > 1:
250 self.account_selector.addItems([_("All accounts")] + accounts.values())
251 self.account_selector.setCurrentIndex(0)
252 self.account_selector.show()
254 self.account_selector.hide()
257 def load_wallet(self, wallet):
260 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
261 self.current_account = self.wallet.storage.get("current_account", None)
263 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
264 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
265 self.setWindowTitle( title )
267 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
268 self.notify_transactions()
269 self.update_account_selector()
270 self.new_account.setEnabled(self.wallet.seed_version>4)
271 self.update_lock_icon()
272 self.update_buttons_on_seed()
273 self.update_console()
275 run_hook('load_wallet', wallet)
278 def open_wallet(self):
279 wallet_folder = self.wallet.storage.path
280 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
284 storage = WalletStorage({'wallet_path': filename})
285 if not storage.file_exists:
286 self.show_message("file not found "+ filename)
289 self.wallet.stop_threads()
292 wallet = Wallet(storage)
293 wallet.start_threads(self.network)
295 self.load_wallet(wallet)
299 def backup_wallet(self):
301 path = self.wallet.storage.path
302 wallet_folder = os.path.dirname(path)
303 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
307 new_path = os.path.join(wallet_folder, filename)
310 shutil.copy2(path, new_path)
311 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
312 except (IOError, os.error), reason:
313 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
316 def new_wallet(self):
319 wallet_folder = os.path.dirname(self.wallet.storage.path)
320 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
323 filename = os.path.join(wallet_folder, filename)
325 storage = WalletStorage({'wallet_path': filename})
326 if storage.file_exists:
327 QMessageBox.critical(None, "Error", _("File exists"))
330 wizard = installwizard.InstallWizard(self.config, self.network, storage)
331 wallet = wizard.run()
333 self.load_wallet(wallet)
337 def init_menubar(self):
340 file_menu = menubar.addMenu(_("&File"))
341 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
342 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
343 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
344 file_menu.addAction(_("&Quit"), self.close)
346 wallet_menu = menubar.addMenu(_("&Wallet"))
347 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
348 self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
350 wallet_menu.addSeparator()
352 wallet_menu.addAction(_("&Password"), self.change_password_dialog)
353 wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
354 wallet_menu.addAction(_("&Master Public Key"), self.show_master_public_key)
356 wallet_menu.addSeparator()
357 labels_menu = wallet_menu.addMenu(_("&Labels"))
358 labels_menu.addAction(_("&Import"), self.do_import_labels)
359 labels_menu.addAction(_("&Export"), self.do_export_labels)
361 keys_menu = wallet_menu.addMenu(_("&Private keys"))
362 keys_menu.addAction(_("&Import"), self.do_import_privkey)
363 keys_menu.addAction(_("&Export"), self.do_export_privkeys)
365 wallet_menu.addAction(_("&Export History"), self.do_export_history)
367 tools_menu = menubar.addMenu(_("&Tools"))
369 # Settings / Preferences are all reserved keywords in OSX using this as work around
370 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
371 tools_menu.addAction(_("&Network"), self.run_network_dialog)
372 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
373 tools_menu.addSeparator()
374 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
375 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
376 tools_menu.addSeparator()
378 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
379 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
380 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
382 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
383 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
384 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
385 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
387 help_menu = menubar.addMenu(_("&Help"))
388 help_menu.addAction(_("&About"), self.show_about)
389 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
390 help_menu.addSeparator()
391 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
392 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
394 self.setMenuBar(menubar)
396 def show_about(self):
397 QMessageBox.about(self, "Electrum",
398 _("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."))
400 def show_report_bug(self):
401 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
402 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
405 def notify_transactions(self):
406 if not self.network or not self.network.is_connected():
409 print_error("Notifying GUI")
410 if len(self.network.interface.pending_transactions_for_notifications) > 0:
411 # Combine the transactions if there are more then three
412 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
415 for tx in self.network.interface.pending_transactions_for_notifications:
416 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
420 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
421 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
423 self.network.interface.pending_transactions_for_notifications = []
425 for tx in self.network.interface.pending_transactions_for_notifications:
427 self.network.interface.pending_transactions_for_notifications.remove(tx)
428 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
430 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
432 def notify(self, message):
433 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
437 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
438 def getOpenFileName(self, title, filter = ""):
439 directory = self.config.get('io_dir', os.path.expanduser('~'))
440 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
441 if fileName and directory != os.path.dirname(fileName):
442 self.config.set_key('io_dir', os.path.dirname(fileName), True)
445 def getSaveFileName(self, title, filename, filter = ""):
446 directory = self.config.get('io_dir', os.path.expanduser('~'))
447 path = os.path.join( directory, filename )
448 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
449 if fileName and directory != os.path.dirname(fileName):
450 self.config.set_key('io_dir', os.path.dirname(fileName), True)
454 QMainWindow.close(self)
455 run_hook('close_main_window')
457 def connect_slots(self, sender):
458 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
459 self.previous_payto_e=''
461 def timer_actions(self):
462 if self.need_update.is_set():
464 self.need_update.clear()
465 run_hook('timer_actions')
467 def format_amount(self, x, is_diff=False, whitespaces=False):
468 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
470 def read_amount(self, x):
471 if x in['.', '']: return None
472 p = pow(10, self.decimal_point)
473 return int( p * Decimal(x) )
476 assert self.decimal_point in [5,8]
477 return "BTC" if self.decimal_point == 8 else "mBTC"
480 def update_status(self):
481 if self.network is None or not self.network.is_running():
483 icon = QIcon(":icons/status_disconnected.png")
485 elif self.network.is_connected():
486 if not self.wallet.up_to_date:
487 text = _("Synchronizing...")
488 icon = QIcon(":icons/status_waiting.png")
489 elif self.network.server_lag > 1:
490 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
491 icon = QIcon(":icons/status_lagging.png")
493 c, u = self.wallet.get_account_balance(self.current_account)
494 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
495 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
497 # append fiat balance and price from exchange rate plugin
499 run_hook('get_fiat_status_text', c+u, r)
504 self.tray.setToolTip(text)
505 icon = QIcon(":icons/status_connected.png")
507 text = _("Not connected")
508 icon = QIcon(":icons/status_disconnected.png")
510 self.balance_label.setText(text)
511 self.status_button.setIcon( icon )
514 def update_wallet(self):
516 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
517 self.update_history_tab()
518 self.update_receive_tab()
519 self.update_contacts_tab()
520 self.update_completions()
523 def create_history_tab(self):
524 self.history_list = l = MyTreeWidget(self)
526 for i,width in enumerate(self.column_widths['history']):
527 l.setColumnWidth(i, width)
528 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
529 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
530 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
532 l.customContextMenuRequested.connect(self.create_history_menu)
536 def create_history_menu(self, position):
537 self.history_list.selectedIndexes()
538 item = self.history_list.currentItem()
540 tx_hash = str(item.data(0, Qt.UserRole).toString())
541 if not tx_hash: return
543 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
544 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
545 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
546 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
547 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
550 def show_transaction(self, tx):
551 import transaction_dialog
552 d = transaction_dialog.TxDialog(tx, self)
555 def tx_label_clicked(self, item, column):
556 if column==2 and item.isSelected():
558 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
559 self.history_list.editItem( item, column )
560 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
563 def tx_label_changed(self, item, column):
567 tx_hash = str(item.data(0, Qt.UserRole).toString())
568 tx = self.wallet.transactions.get(tx_hash)
569 text = unicode( item.text(2) )
570 self.wallet.set_label(tx_hash, text)
572 item.setForeground(2, QBrush(QColor('black')))
574 text = self.wallet.get_default_label(tx_hash)
575 item.setText(2, text)
576 item.setForeground(2, QBrush(QColor('gray')))
580 def edit_label(self, is_recv):
581 l = self.receive_list if is_recv else self.contacts_list
582 item = l.currentItem()
583 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
584 l.editItem( item, 1 )
585 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
589 def address_label_clicked(self, item, column, l, column_addr, column_label):
590 if column == column_label and item.isSelected():
591 is_editable = item.data(0, 32).toBool()
594 addr = unicode( item.text(column_addr) )
595 label = unicode( item.text(column_label) )
596 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
597 l.editItem( item, column )
598 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
601 def address_label_changed(self, item, column, l, column_addr, column_label):
602 if column == column_label:
603 addr = unicode( item.text(column_addr) )
604 text = unicode( item.text(column_label) )
605 is_editable = item.data(0, 32).toBool()
609 changed = self.wallet.set_label(addr, text)
611 self.update_history_tab()
612 self.update_completions()
614 self.current_item_changed(item)
616 run_hook('item_changed', item, column)
619 def current_item_changed(self, a):
620 run_hook('current_item_changed', a)
624 def update_history_tab(self):
626 self.history_list.clear()
627 for item in self.wallet.get_tx_history(self.current_account):
628 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
629 time_str = _("unknown")
632 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
634 time_str = _("error")
637 time_str = 'unverified'
638 icon = QIcon(":icons/unconfirmed.png")
641 icon = QIcon(":icons/unconfirmed.png")
643 icon = QIcon(":icons/clock%d.png"%conf)
645 icon = QIcon(":icons/confirmed.png")
647 if value is not None:
648 v_str = self.format_amount(value, True, whitespaces=True)
652 balance_str = self.format_amount(balance, whitespaces=True)
655 label, is_default_label = self.wallet.get_label(tx_hash)
657 label = _('Pruned transaction outputs')
658 is_default_label = False
660 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
661 item.setFont(2, QFont(MONOSPACE_FONT))
662 item.setFont(3, QFont(MONOSPACE_FONT))
663 item.setFont(4, QFont(MONOSPACE_FONT))
665 item.setForeground(3, QBrush(QColor("#BC1E1E")))
667 item.setData(0, Qt.UserRole, tx_hash)
668 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
670 item.setForeground(2, QBrush(QColor('grey')))
672 item.setIcon(0, icon)
673 self.history_list.insertTopLevelItem(0,item)
676 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
677 run_hook('history_tab_update')
680 def create_send_tab(self):
685 grid.setColumnMinimumWidth(3,300)
686 grid.setColumnStretch(5,1)
689 self.payto_e = QLineEdit()
690 grid.addWidget(QLabel(_('Pay to')), 1, 0)
691 grid.addWidget(self.payto_e, 1, 1, 1, 3)
693 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)
695 completer = QCompleter()
696 completer.setCaseSensitivity(False)
697 self.payto_e.setCompleter(completer)
698 completer.setModel(self.completions)
700 self.message_e = QLineEdit()
701 grid.addWidget(QLabel(_('Description')), 2, 0)
702 grid.addWidget(self.message_e, 2, 1, 1, 3)
703 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)
705 self.from_label = QLabel(_('From'))
706 grid.addWidget(self.from_label, 3, 0)
707 self.from_list = QTreeWidget(self)
708 self.from_list.setColumnCount(2)
709 self.from_list.setColumnWidth(0, 350)
710 self.from_list.setColumnWidth(1, 50)
711 self.from_list.setHeaderHidden (True)
712 self.from_list.setMaximumHeight(80)
713 grid.addWidget(self.from_list, 3, 1, 1, 3)
714 self.set_pay_from([])
716 self.amount_e = AmountEdit(self.base_unit)
717 grid.addWidget(QLabel(_('Amount')), 4, 0)
718 grid.addWidget(self.amount_e, 4, 1, 1, 2)
719 grid.addWidget(HelpButton(
720 _('Amount to be sent.') + '\n\n' \
721 + _('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.') \
722 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
724 self.fee_e = AmountEdit(self.base_unit)
725 grid.addWidget(QLabel(_('Fee')), 5, 0)
726 grid.addWidget(self.fee_e, 5, 1, 1, 2)
727 grid.addWidget(HelpButton(
728 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
729 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
730 + _('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)
732 run_hook('exchange_rate_button', grid)
734 self.send_button = EnterButton(_("Send"), self.do_send)
735 grid.addWidget(self.send_button, 6, 1)
737 b = EnterButton(_("Clear"),self.do_clear)
738 grid.addWidget(b, 6, 2)
740 self.payto_sig = QLabel('')
741 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
743 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
744 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
753 def entry_changed( is_fee ):
754 self.funds_error = False
756 if self.amount_e.is_shortcut:
757 self.amount_e.is_shortcut = False
758 sendable = self.get_sendable_balance()
759 # there is only one output because we are completely spending inputs
760 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
761 fee = self.wallet.estimated_fee(inputs, 1)
763 self.amount_e.setText( self.format_amount(amount) )
764 self.fee_e.setText( self.format_amount( fee ) )
767 amount = self.read_amount(str(self.amount_e.text()))
768 fee = self.read_amount(str(self.fee_e.text()))
770 if not is_fee: fee = None
773 # assume that there will be 2 outputs (one for change)
774 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
776 self.fee_e.setText( self.format_amount( fee ) )
779 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
783 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
784 self.funds_error = True
785 text = _( "Not enough funds" )
786 c, u = self.wallet.get_frozen_balance()
787 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
789 self.statusBar().showMessage(text)
790 self.amount_e.setPalette(palette)
791 self.fee_e.setPalette(palette)
793 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
794 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
796 run_hook('create_send_tab', grid)
800 def set_pay_from(self, l):
802 self.from_list.clear()
803 self.from_label.setHidden(len(self.pay_from) == 0)
804 self.from_list.setHidden(len(self.pay_from) == 0)
805 for addr in self.pay_from:
806 c, u = self.wallet.get_addr_balance(addr)
807 balance = self.format_amount(c + u)
808 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
811 def update_completions(self):
813 for addr,label in self.wallet.labels.items():
814 if addr in self.wallet.addressbook:
815 l.append( label + ' <' + addr + '>')
817 run_hook('update_completions', l)
818 self.completions.setStringList(l)
822 return lambda s, *args: s.do_protect(func, args)
827 label = unicode( self.message_e.text() )
828 r = unicode( self.payto_e.text() )
831 # label or alias, with address in brackets
832 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
833 to_address = m.group(2) if m else r
835 if not is_valid(to_address):
836 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
840 amount = self.read_amount(unicode( self.amount_e.text()))
842 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
845 fee = self.read_amount(unicode( self.fee_e.text()))
847 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
850 confirm_amount = self.config.get('confirm_amount', 100000000)
851 if amount >= confirm_amount:
852 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
855 confirm_fee = self.config.get('confirm_fee', 100000)
856 if fee >= confirm_fee:
857 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()}):
860 self.send_tx(to_address, amount, fee, label)
863 def waiting_dialog(self, message):
865 d.setWindowTitle('Please wait')
867 vbox = QVBoxLayout(d)
874 def send_tx(self, to_address, amount, fee, label, password):
876 # first, create an unsigned tx
877 domain = self.get_payment_sources()
878 outputs = [(to_address, amount)]
880 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
882 except Exception as e:
883 traceback.print_exc(file=sys.stdout)
884 self.show_message(str(e))
887 # call hook to see if plugin needs gui interaction
888 run_hook('send_tx', tx)
893 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
894 self.wallet.sign_transaction(tx, keypairs, password)
896 self.emit(SIGNAL('send_tx2'))
899 dialog = self.waiting_dialog('Signing..')
900 self.connect(self, QtCore.SIGNAL('send_tx2'), lambda: self.send_tx2(self.signed_tx, fee, label, dialog, password))
901 threading.Thread(target=sign_thread).start()
903 # add recipient to addressbook
904 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
905 self.wallet.addressbook.append(to_address)
908 def send_tx2(self, tx, fee, label, dialog, password):
912 self.show_message(tx.error)
915 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
916 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
920 self.wallet.set_label(tx.hash(), label)
924 d = self.waiting_dialog('Broadcasting...')
925 h = self.wallet.send_tx(tx)
926 self.wallet.tx_event.wait()
929 status, msg = self.wallet.receive_tx( h, tx )
931 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
933 self.update_contacts_tab()
935 QMessageBox.warning(self, _('Error'), msg, _('OK'))
938 self.show_transaction(tx)
943 def set_url(self, url):
944 address, amount, label, message, signature, identity, url = util.parse_url(url)
947 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
948 elif amount: amount = str(Decimal(amount))
951 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
954 self.mini.set_payment_fields(address, amount)
956 if label and self.wallet.labels.get(address) != label:
957 if self.question('Give label "%s" to address %s ?'%(label,address)):
958 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
959 self.wallet.addressbook.append(address)
960 self.wallet.set_label(address, label)
962 run_hook('set_url', url, self.show_message, self.question)
964 self.tabs.setCurrentIndex(1)
965 label = self.wallet.labels.get(address)
966 m_addr = label + ' <'+ address +'>' if label else address
967 self.payto_e.setText(m_addr)
969 self.message_e.setText(message)
971 self.amount_e.setText(amount)
974 self.set_frozen(self.payto_e,True)
975 self.set_frozen(self.amount_e,True)
976 self.set_frozen(self.message_e,True)
977 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
979 self.payto_sig.setVisible(False)
982 self.payto_sig.setVisible(False)
983 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
985 self.set_frozen(e,False)
987 self.set_pay_from([])
990 def set_frozen(self,entry,frozen):
992 entry.setReadOnly(True)
993 entry.setFrame(False)
995 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
996 entry.setPalette(palette)
998 entry.setReadOnly(False)
1000 palette = QPalette()
1001 palette.setColor(entry.backgroundRole(), QColor('white'))
1002 entry.setPalette(palette)
1005 def set_addrs_frozen(self,addrs,freeze):
1007 if not addr: continue
1008 if addr in self.wallet.frozen_addresses and not freeze:
1009 self.wallet.unfreeze(addr)
1010 elif addr not in self.wallet.frozen_addresses and freeze:
1011 self.wallet.freeze(addr)
1012 self.update_receive_tab()
1016 def create_list_tab(self, headers):
1017 "generic tab creation method"
1018 l = MyTreeWidget(self)
1019 l.setColumnCount( len(headers) )
1020 l.setHeaderLabels( headers )
1023 vbox = QVBoxLayout()
1030 vbox.addWidget(buttons)
1032 hbox = QHBoxLayout()
1035 buttons.setLayout(hbox)
1040 def create_receive_tab(self):
1041 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1042 l.setContextMenuPolicy(Qt.CustomContextMenu)
1043 l.customContextMenuRequested.connect(self.create_receive_menu)
1044 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1045 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1046 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1047 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1048 self.receive_list = l
1049 self.receive_buttons_hbox = hbox
1056 def save_column_widths(self):
1057 self.column_widths["receive"] = []
1058 for i in range(self.receive_list.columnCount() -1):
1059 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1061 self.column_widths["history"] = []
1062 for i in range(self.history_list.columnCount() - 1):
1063 self.column_widths["history"].append(self.history_list.columnWidth(i))
1065 self.column_widths["contacts"] = []
1066 for i in range(self.contacts_list.columnCount() - 1):
1067 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1069 self.config.set_key("column_widths_2", self.column_widths, True)
1072 def create_contacts_tab(self):
1073 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1074 l.setContextMenuPolicy(Qt.CustomContextMenu)
1075 l.customContextMenuRequested.connect(self.create_contact_menu)
1076 for i,width in enumerate(self.column_widths['contacts']):
1077 l.setColumnWidth(i, width)
1079 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1080 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1081 self.contacts_list = l
1082 self.contacts_buttons_hbox = hbox
1087 def delete_imported_key(self, addr):
1088 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1089 self.wallet.delete_imported_key(addr)
1090 self.update_receive_tab()
1091 self.update_history_tab()
1093 def edit_account_label(self, k):
1094 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1096 label = unicode(text)
1097 self.wallet.set_label(k,label)
1098 self.update_receive_tab()
1100 def account_set_expanded(self, item, k, b):
1102 self.accounts_expanded[k] = b
1104 def create_account_menu(self, position, k, item):
1106 if item.isExpanded():
1107 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1109 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1110 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1111 if self.wallet.seed_version > 4:
1112 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1113 if self.wallet.account_is_pending(k):
1114 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1115 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1117 def delete_pending_account(self, k):
1118 self.wallet.delete_pending_account(k)
1119 self.update_receive_tab()
1121 def create_receive_menu(self, position):
1122 # fixme: this function apparently has a side effect.
1123 # if it is not called the menu pops up several times
1124 #self.receive_list.selectedIndexes()
1126 selected = self.receive_list.selectedItems()
1127 multi_select = len(selected) > 1
1128 addrs = [unicode(item.text(0)) for item in selected]
1129 if not multi_select:
1130 item = self.receive_list.itemAt(position)
1134 if not is_valid(addr):
1135 k = str(item.data(0,32).toString())
1137 self.create_account_menu(position, k, item)
1139 item.setExpanded(not item.isExpanded())
1143 if not multi_select:
1144 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1145 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1146 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1147 if self.wallet.seed:
1148 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1149 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1150 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1151 if addr in self.wallet.imported_keys:
1152 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1154 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1155 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1156 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1157 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1159 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1160 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1162 run_hook('receive_menu', menu, addrs)
1163 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1166 def get_sendable_balance(self):
1167 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1170 def get_payment_sources(self):
1172 return self.pay_from
1174 return self.wallet.get_account_addresses(self.current_account)
1177 def send_from_addresses(self, addrs):
1178 self.set_pay_from( addrs )
1179 self.tabs.setCurrentIndex(1)
1182 def payto(self, addr):
1184 label = self.wallet.labels.get(addr)
1185 m_addr = label + ' <' + addr + '>' if label else addr
1186 self.tabs.setCurrentIndex(1)
1187 self.payto_e.setText(m_addr)
1188 self.amount_e.setFocus()
1191 def delete_contact(self, x):
1192 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1193 self.wallet.delete_contact(x)
1194 self.wallet.set_label(x, None)
1195 self.update_history_tab()
1196 self.update_contacts_tab()
1197 self.update_completions()
1200 def create_contact_menu(self, position):
1201 item = self.contacts_list.itemAt(position)
1204 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1206 addr = unicode(item.text(0))
1207 label = unicode(item.text(1))
1208 is_editable = item.data(0,32).toBool()
1209 payto_addr = item.data(0,33).toString()
1210 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1211 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1212 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1214 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1215 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1217 run_hook('create_contact_menu', menu, item)
1218 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1221 def update_receive_item(self, item):
1222 item.setFont(0, QFont(MONOSPACE_FONT))
1223 address = str(item.data(0,0).toString())
1224 label = self.wallet.labels.get(address,'')
1225 item.setData(1,0,label)
1226 item.setData(0,32, True) # is editable
1228 run_hook('update_receive_item', address, item)
1230 if not self.wallet.is_mine(address): return
1232 c, u = self.wallet.get_addr_balance(address)
1233 balance = self.format_amount(c + u)
1234 item.setData(2,0,balance)
1236 if address in self.wallet.frozen_addresses:
1237 item.setBackgroundColor(0, QColor('lightblue'))
1240 def update_receive_tab(self):
1241 l = self.receive_list
1244 l.setColumnHidden(2, False)
1245 l.setColumnHidden(3, False)
1246 for i,width in enumerate(self.column_widths['receive']):
1247 l.setColumnWidth(i, width)
1249 if self.current_account is None:
1250 account_items = self.wallet.accounts.items()
1251 elif self.current_account != -1:
1252 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1256 for k, account in account_items:
1257 name = self.wallet.get_account_name(k)
1258 c,u = self.wallet.get_account_balance(k)
1259 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1260 l.addTopLevelItem(account_item)
1261 account_item.setExpanded(self.accounts_expanded.get(k, True))
1262 account_item.setData(0, 32, k)
1264 if not self.wallet.is_seeded(k):
1265 icon = QIcon(":icons/key.png")
1266 account_item.setIcon(0, icon)
1268 for is_change in ([0,1]):
1269 name = _("Receiving") if not is_change else _("Change")
1270 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1271 account_item.addChild(seq_item)
1272 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1274 if not is_change: seq_item.setExpanded(True)
1279 for address in account.get_addresses(is_change):
1280 h = self.wallet.history.get(address,[])
1284 if gap > self.wallet.gap_limit:
1289 c, u = self.wallet.get_addr_balance(address)
1290 num_tx = '*' if h == ['*'] else "%d"%len(h)
1291 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1292 self.update_receive_item(item)
1294 item.setBackgroundColor(1, QColor('red'))
1295 if len(h) > 0 and c == -u:
1297 seq_item.insertChild(0,used_item)
1299 used_item.addChild(item)
1301 seq_item.addChild(item)
1304 for k, addr in self.wallet.get_pending_accounts():
1305 name = self.wallet.labels.get(k,'')
1306 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1307 self.update_receive_item(item)
1308 l.addTopLevelItem(account_item)
1309 account_item.setExpanded(True)
1310 account_item.setData(0, 32, k)
1311 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1312 account_item.addChild(item)
1313 self.update_receive_item(item)
1316 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1317 c,u = self.wallet.get_imported_balance()
1318 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1319 l.addTopLevelItem(account_item)
1320 account_item.setExpanded(True)
1321 for address in self.wallet.imported_keys.keys():
1322 item = QTreeWidgetItem( [ address, '', '', ''] )
1323 self.update_receive_item(item)
1324 account_item.addChild(item)
1327 # we use column 1 because column 0 may be hidden
1328 l.setCurrentItem(l.topLevelItem(0),1)
1331 def update_contacts_tab(self):
1332 l = self.contacts_list
1335 for address in self.wallet.addressbook:
1336 label = self.wallet.labels.get(address,'')
1337 n = self.wallet.get_num_tx(address)
1338 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1339 item.setFont(0, QFont(MONOSPACE_FONT))
1340 # 32 = label can be edited (bool)
1341 item.setData(0,32, True)
1343 item.setData(0,33, address)
1344 l.addTopLevelItem(item)
1346 run_hook('update_contacts_tab', l)
1347 l.setCurrentItem(l.topLevelItem(0))
1351 def create_console_tab(self):
1352 from console import Console
1353 self.console = console = Console()
1357 def update_console(self):
1358 console = self.console
1359 console.history = self.config.get("console-history",[])
1360 console.history_index = len(console.history)
1362 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1363 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1365 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1367 def mkfunc(f, method):
1368 return lambda *args: apply( f, (method, args, self.password_dialog ))
1370 if m[0]=='_' or m in ['network','wallet']: continue
1371 methods[m] = mkfunc(c._run, m)
1373 console.updateNamespace(methods)
1376 def change_account(self,s):
1377 if s == _("All accounts"):
1378 self.current_account = None
1380 accounts = self.wallet.get_account_names()
1381 for k, v in accounts.items():
1383 self.current_account = k
1384 self.update_history_tab()
1385 self.update_status()
1386 self.update_receive_tab()
1388 def create_status_bar(self):
1391 sb.setFixedHeight(35)
1392 qtVersion = qVersion()
1394 self.balance_label = QLabel("")
1395 sb.addWidget(self.balance_label)
1397 from version_getter import UpdateLabel
1398 self.updatelabel = UpdateLabel(self.config, sb)
1400 self.account_selector = QComboBox()
1401 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1402 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1403 sb.addPermanentWidget(self.account_selector)
1405 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1406 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1408 self.lock_icon = QIcon()
1409 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1410 sb.addPermanentWidget( self.password_button )
1412 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1413 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1414 sb.addPermanentWidget( self.seed_button )
1415 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1416 sb.addPermanentWidget( self.status_button )
1418 run_hook('create_status_bar', (sb,))
1420 self.setStatusBar(sb)
1423 def update_lock_icon(self):
1424 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1425 self.password_button.setIcon( icon )
1428 def update_buttons_on_seed(self):
1429 if not self.wallet.is_watching_only():
1430 self.seed_button.show()
1431 self.password_button.show()
1432 self.send_button.setText(_("Send"))
1434 self.password_button.hide()
1435 self.seed_button.hide()
1436 self.send_button.setText(_("Create unsigned transaction"))
1439 def change_password_dialog(self):
1440 from password_dialog import PasswordDialog
1441 d = PasswordDialog(self.wallet, self)
1443 self.update_lock_icon()
1446 def new_contact_dialog(self):
1449 d.setWindowTitle(_("New Contact"))
1450 vbox = QVBoxLayout(d)
1451 vbox.addWidget(QLabel(_('New Contact')+':'))
1453 grid = QGridLayout()
1456 grid.addWidget(QLabel(_("Address")), 1, 0)
1457 grid.addWidget(line1, 1, 1)
1458 grid.addWidget(QLabel(_("Name")), 2, 0)
1459 grid.addWidget(line2, 2, 1)
1461 vbox.addLayout(grid)
1462 vbox.addLayout(ok_cancel_buttons(d))
1467 address = str(line1.text())
1468 label = unicode(line2.text())
1470 if not is_valid(address):
1471 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1474 self.wallet.add_contact(address)
1476 self.wallet.set_label(address, label)
1478 self.update_contacts_tab()
1479 self.update_history_tab()
1480 self.update_completions()
1481 self.tabs.setCurrentIndex(3)
1485 def new_account_dialog(self, password):
1487 dialog = QDialog(self)
1489 dialog.setWindowTitle(_("New Account"))
1491 vbox = QVBoxLayout()
1492 vbox.addWidget(QLabel(_('Account name')+':'))
1495 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1496 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1501 vbox.addLayout(ok_cancel_buttons(dialog))
1502 dialog.setLayout(vbox)
1506 name = str(e.text())
1509 self.wallet.create_pending_account('1of1', name, password)
1510 self.update_receive_tab()
1511 self.tabs.setCurrentIndex(2)
1515 def show_master_public_key_old(self):
1516 dialog = QDialog(self)
1518 dialog.setWindowTitle(_("Master Public Key"))
1520 main_text = QTextEdit()
1521 main_text.setText(self.wallet.get_master_public_key())
1522 main_text.setReadOnly(True)
1523 main_text.setMaximumHeight(170)
1524 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1526 ok_button = QPushButton(_("OK"))
1527 ok_button.setDefault(True)
1528 ok_button.clicked.connect(dialog.accept)
1530 main_layout = QGridLayout()
1531 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1533 main_layout.addWidget(main_text, 1, 0)
1534 main_layout.addWidget(qrw, 1, 1 )
1536 vbox = QVBoxLayout()
1537 vbox.addLayout(main_layout)
1538 vbox.addLayout(close_button(dialog))
1539 dialog.setLayout(vbox)
1543 def show_master_public_key(self):
1545 if self.wallet.seed_version == 4:
1546 self.show_master_public_key_old()
1549 dialog = QDialog(self)
1551 dialog.setWindowTitle(_("Master Public Keys"))
1553 mpk_text = QTextEdit()
1554 mpk_text.setReadOnly(True)
1555 mpk_text.setMaximumHeight(170)
1556 mpk_qrw = QRCodeWidget()
1558 main_layout = QGridLayout()
1560 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1561 main_layout.addWidget(mpk_text, 1, 1)
1562 main_layout.addWidget(mpk_qrw, 1, 2)
1565 xpub = self.wallet.master_public_keys[str(key)]
1566 mpk_text.setText(xpub)
1567 mpk_qrw.set_addr(xpub)
1570 key_selector = QComboBox()
1571 keys = sorted(self.wallet.master_public_keys.keys())
1572 key_selector.addItems(keys)
1574 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1575 main_layout.addWidget(key_selector, 0, 1)
1576 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1580 vbox = QVBoxLayout()
1581 vbox.addLayout(main_layout)
1582 vbox.addLayout(close_button(dialog))
1584 dialog.setLayout(vbox)
1589 def show_seed_dialog(self, password):
1590 if self.wallet.is_watching_only():
1591 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1594 if self.wallet.seed:
1596 mnemonic = self.wallet.get_mnemonic(password)
1598 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1600 from seed_dialog import SeedDialog
1601 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1605 for k in self.wallet.master_private_keys.keys():
1606 pk = self.wallet.get_master_private_key(k, password)
1608 from seed_dialog import PrivateKeysDialog
1609 d = PrivateKeysDialog(self,l)
1616 def show_qrcode(self, data, title = _("QR code")):
1620 d.setWindowTitle(title)
1621 d.setMinimumSize(270, 300)
1622 vbox = QVBoxLayout()
1623 qrw = QRCodeWidget(data)
1624 vbox.addWidget(qrw, 1)
1625 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1626 hbox = QHBoxLayout()
1629 filename = os.path.join(self.config.path, "qrcode.bmp")
1632 bmp.save_qrcode(qrw.qr, filename)
1633 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1635 def copy_to_clipboard():
1636 bmp.save_qrcode(qrw.qr, filename)
1637 self.app.clipboard().setImage(QImage(filename))
1638 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1640 b = QPushButton(_("Copy"))
1642 b.clicked.connect(copy_to_clipboard)
1644 b = QPushButton(_("Save"))
1646 b.clicked.connect(print_qr)
1648 b = QPushButton(_("Close"))
1650 b.clicked.connect(d.accept)
1653 vbox.addLayout(hbox)
1658 def do_protect(self, func, args):
1659 if self.wallet.use_encryption:
1660 password = self.password_dialog()
1666 if args != (False,):
1667 args = (self,) + args + (password,)
1669 args = (self,password)
1674 def show_private_key(self, address, password):
1675 if not address: return
1677 pk_list = self.wallet.get_private_key(address, password)
1678 except Exception as e:
1679 traceback.print_exc(file=sys.stdout)
1680 self.show_message(str(e))
1684 d.setMinimumSize(600, 200)
1686 vbox = QVBoxLayout()
1687 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1688 vbox.addWidget( QLabel(_("Private key") + ':'))
1690 keys.setReadOnly(True)
1691 keys.setText('\n'.join(pk_list))
1692 vbox.addWidget(keys)
1693 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1694 vbox.addLayout(close_button(d))
1700 def do_sign(self, address, message, signature, password):
1701 message = unicode(message.toPlainText())
1702 message = message.encode('utf-8')
1704 sig = self.wallet.sign_message(str(address.text()), message, password)
1705 signature.setText(sig)
1706 except Exception as e:
1707 self.show_message(str(e))
1709 def do_verify(self, address, message, signature):
1710 message = unicode(message.toPlainText())
1711 message = message.encode('utf-8')
1712 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1713 self.show_message(_("Signature verified"))
1715 self.show_message(_("Error: wrong signature"))
1718 def sign_verify_message(self, address=''):
1721 d.setWindowTitle(_('Sign/verify Message'))
1722 d.setMinimumSize(410, 290)
1724 layout = QGridLayout(d)
1726 message_e = QTextEdit()
1727 layout.addWidget(QLabel(_('Message')), 1, 0)
1728 layout.addWidget(message_e, 1, 1)
1729 layout.setRowStretch(2,3)
1731 address_e = QLineEdit()
1732 address_e.setText(address)
1733 layout.addWidget(QLabel(_('Address')), 2, 0)
1734 layout.addWidget(address_e, 2, 1)
1736 signature_e = QTextEdit()
1737 layout.addWidget(QLabel(_('Signature')), 3, 0)
1738 layout.addWidget(signature_e, 3, 1)
1739 layout.setRowStretch(3,1)
1741 hbox = QHBoxLayout()
1743 b = QPushButton(_("Sign"))
1744 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1747 b = QPushButton(_("Verify"))
1748 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1751 b = QPushButton(_("Close"))
1752 b.clicked.connect(d.accept)
1754 layout.addLayout(hbox, 4, 1)
1759 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1761 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1762 message_e.setText(decrypted)
1763 except Exception as e:
1764 self.show_message(str(e))
1767 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1768 message = unicode(message_e.toPlainText())
1769 message = message.encode('utf-8')
1771 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1772 encrypted_e.setText(encrypted)
1773 except Exception as e:
1774 self.show_message(str(e))
1778 def encrypt_message(self, address = ''):
1781 d.setWindowTitle(_('Encrypt/decrypt Message'))
1782 d.setMinimumSize(610, 490)
1784 layout = QGridLayout(d)
1786 message_e = QTextEdit()
1787 layout.addWidget(QLabel(_('Message')), 1, 0)
1788 layout.addWidget(message_e, 1, 1)
1789 layout.setRowStretch(2,3)
1791 pubkey_e = QLineEdit()
1793 pubkey = self.wallet.getpubkeys(address)[0]
1794 pubkey_e.setText(pubkey)
1795 layout.addWidget(QLabel(_('Public key')), 2, 0)
1796 layout.addWidget(pubkey_e, 2, 1)
1798 encrypted_e = QTextEdit()
1799 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1800 layout.addWidget(encrypted_e, 3, 1)
1801 layout.setRowStretch(3,1)
1803 hbox = QHBoxLayout()
1804 b = QPushButton(_("Encrypt"))
1805 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1808 b = QPushButton(_("Decrypt"))
1809 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1812 b = QPushButton(_("Close"))
1813 b.clicked.connect(d.accept)
1816 layout.addLayout(hbox, 4, 1)
1820 def question(self, msg):
1821 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1823 def show_message(self, msg):
1824 QMessageBox.information(self, _('Message'), msg, _('OK'))
1826 def password_dialog(self ):
1829 d.setWindowTitle(_("Enter Password"))
1834 vbox = QVBoxLayout()
1835 msg = _('Please enter your password')
1836 vbox.addWidget(QLabel(msg))
1838 grid = QGridLayout()
1840 grid.addWidget(QLabel(_('Password')), 1, 0)
1841 grid.addWidget(pw, 1, 1)
1842 vbox.addLayout(grid)
1844 vbox.addLayout(ok_cancel_buttons(d))
1847 run_hook('password_dialog', pw, grid, 1)
1848 if not d.exec_(): return
1849 return unicode(pw.text())
1858 def tx_from_text(self, txt):
1859 "json or raw hexadecimal"
1862 tx = Transaction(txt)
1868 tx_dict = json.loads(str(txt))
1869 assert "hex" in tx_dict.keys()
1870 assert "complete" in tx_dict.keys()
1871 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1872 if not tx_dict["complete"]:
1873 assert "input_info" in tx_dict.keys()
1874 input_info = json.loads(tx_dict['input_info'])
1875 tx.add_input_info(input_info)
1880 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1884 def read_tx_from_file(self):
1885 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1889 with open(fileName, "r") as f:
1890 file_content = f.read()
1891 except (ValueError, IOError, os.error), reason:
1892 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1894 return self.tx_from_text(file_content)
1898 def sign_raw_transaction(self, tx, input_info, password):
1899 self.wallet.signrawtransaction(tx, input_info, [], password)
1901 def do_process_from_text(self):
1902 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1905 tx = self.tx_from_text(text)
1907 self.show_transaction(tx)
1909 def do_process_from_file(self):
1910 tx = self.read_tx_from_file()
1912 self.show_transaction(tx)
1914 def do_process_from_txid(self):
1915 from electrum import transaction
1916 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1918 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1920 tx = transaction.Transaction(r)
1922 self.show_transaction(tx)
1924 self.show_message("unknown transaction")
1926 def do_process_from_csvReader(self, csvReader):
1931 for position, row in enumerate(csvReader):
1933 if not is_valid(address):
1934 errors.append((position, address))
1936 amount = Decimal(row[1])
1937 amount = int(100000000*amount)
1938 outputs.append((address, amount))
1939 except (ValueError, IOError, os.error), reason:
1940 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1944 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1945 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1949 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1950 except Exception as e:
1951 self.show_message(str(e))
1954 self.show_transaction(tx)
1956 def do_process_from_csv_file(self):
1957 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1961 with open(fileName, "r") as f:
1962 csvReader = csv.reader(f)
1963 self.do_process_from_csvReader(csvReader)
1964 except (ValueError, IOError, os.error), reason:
1965 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1968 def do_process_from_csv_text(self):
1969 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1970 + _("Format: address, amount. One output per line"), _("Load CSV"))
1973 f = StringIO.StringIO(text)
1974 csvReader = csv.reader(f)
1975 self.do_process_from_csvReader(csvReader)
1980 def do_export_privkeys(self, password):
1981 if not self.wallet.seed:
1982 self.show_message(_("This wallet has no seed"))
1985 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.")))
1988 select_export = _('Select file to export your private keys to')
1989 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1991 with open(fileName, "w+") as csvfile:
1992 transaction = csv.writer(csvfile)
1993 transaction.writerow(["address", "private_key"])
1995 addresses = self.wallet.addresses(True)
1997 for addr in addresses:
1998 pk = "".join(self.wallet.get_private_key(addr, password))
1999 transaction.writerow(["%34s"%addr,pk])
2001 self.show_message(_("Private keys exported."))
2003 except (IOError, os.error), reason:
2004 export_error_label = _("Electrum was unable to produce a private key-export.")
2005 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2007 except Exception as e:
2008 self.show_message(str(e))
2012 def do_import_labels(self):
2013 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2014 if not labelsFile: return
2016 f = open(labelsFile, 'r')
2019 for key, value in json.loads(data).items():
2020 self.wallet.set_label(key, value)
2021 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2022 except (IOError, os.error), reason:
2023 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2026 def do_export_labels(self):
2027 labels = self.wallet.labels
2029 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2031 with open(fileName, 'w+') as f:
2032 json.dump(labels, f)
2033 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2034 except (IOError, os.error), reason:
2035 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2038 def do_export_history(self):
2039 from lite_window import csv_transaction
2040 csv_transaction(self.wallet)
2044 def do_import_privkey(self, password):
2045 if not self.wallet.imported_keys:
2046 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2047 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2048 + _('Are you sure you understand what you are doing?'), 3, 4)
2051 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2054 text = str(text).split()
2059 addr = self.wallet.import_key(key, password)
2060 except Exception as e:
2066 addrlist.append(addr)
2068 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2070 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2071 self.update_receive_tab()
2072 self.update_history_tab()
2075 def settings_dialog(self):
2077 d.setWindowTitle(_('Electrum Settings'))
2079 vbox = QVBoxLayout()
2080 grid = QGridLayout()
2081 grid.setColumnStretch(0,1)
2083 nz_label = QLabel(_('Display zeros') + ':')
2084 grid.addWidget(nz_label, 0, 0)
2085 nz_e = AmountEdit(None,True)
2086 nz_e.setText("%d"% self.num_zeros)
2087 grid.addWidget(nz_e, 0, 1)
2088 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2089 grid.addWidget(HelpButton(msg), 0, 2)
2090 if not self.config.is_modifiable('num_zeros'):
2091 for w in [nz_e, nz_label]: w.setEnabled(False)
2093 lang_label=QLabel(_('Language') + ':')
2094 grid.addWidget(lang_label, 1, 0)
2095 lang_combo = QComboBox()
2096 from electrum.i18n import languages
2097 lang_combo.addItems(languages.values())
2099 index = languages.keys().index(self.config.get("language",''))
2102 lang_combo.setCurrentIndex(index)
2103 grid.addWidget(lang_combo, 1, 1)
2104 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2105 if not self.config.is_modifiable('language'):
2106 for w in [lang_combo, lang_label]: w.setEnabled(False)
2109 fee_label = QLabel(_('Transaction fee') + ':')
2110 grid.addWidget(fee_label, 2, 0)
2111 fee_e = AmountEdit(self.base_unit)
2112 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2113 grid.addWidget(fee_e, 2, 1)
2114 msg = _('Fee per kilobyte of transaction.') + ' ' \
2115 + _('Recommended value') + ': ' + self.format_amount(20000)
2116 grid.addWidget(HelpButton(msg), 2, 2)
2117 if not self.config.is_modifiable('fee_per_kb'):
2118 for w in [fee_e, fee_label]: w.setEnabled(False)
2120 units = ['BTC', 'mBTC']
2121 unit_label = QLabel(_('Base unit') + ':')
2122 grid.addWidget(unit_label, 3, 0)
2123 unit_combo = QComboBox()
2124 unit_combo.addItems(units)
2125 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2126 grid.addWidget(unit_combo, 3, 1)
2127 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2128 + '\n1BTC=1000mBTC.\n' \
2129 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2131 usechange_cb = QCheckBox(_('Use change addresses'))
2132 usechange_cb.setChecked(self.wallet.use_change)
2133 grid.addWidget(usechange_cb, 4, 0)
2134 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2135 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2137 grid.setRowStretch(5,1)
2139 vbox.addLayout(grid)
2140 vbox.addLayout(ok_cancel_buttons(d))
2144 if not d.exec_(): return
2146 fee = unicode(fee_e.text())
2148 fee = self.read_amount(fee)
2150 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2153 self.wallet.set_fee(fee)
2155 nz = unicode(nz_e.text())
2160 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2163 if self.num_zeros != nz:
2165 self.config.set_key('num_zeros', nz, True)
2166 self.update_history_tab()
2167 self.update_receive_tab()
2169 usechange_result = usechange_cb.isChecked()
2170 if self.wallet.use_change != usechange_result:
2171 self.wallet.use_change = usechange_result
2172 self.wallet.storage.put('use_change', self.wallet.use_change)
2174 unit_result = units[unit_combo.currentIndex()]
2175 if self.base_unit() != unit_result:
2176 self.decimal_point = 8 if unit_result == 'BTC' else 5
2177 self.config.set_key('decimal_point', self.decimal_point, True)
2178 self.update_history_tab()
2179 self.update_status()
2181 need_restart = False
2183 lang_request = languages.keys()[lang_combo.currentIndex()]
2184 if lang_request != self.config.get('language'):
2185 self.config.set_key("language", lang_request, True)
2188 run_hook('close_settings_dialog')
2191 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2194 def run_network_dialog(self):
2195 if not self.network:
2197 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2199 def closeEvent(self, event):
2202 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2203 self.save_column_widths()
2204 self.config.set_key("console-history", self.console.history[-50:], True)
2205 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2209 def plugins_dialog(self):
2210 from electrum.plugins import plugins
2213 d.setWindowTitle(_('Electrum Plugins'))
2216 vbox = QVBoxLayout(d)
2219 scroll = QScrollArea()
2220 scroll.setEnabled(True)
2221 scroll.setWidgetResizable(True)
2222 scroll.setMinimumSize(400,250)
2223 vbox.addWidget(scroll)
2227 w.setMinimumHeight(len(plugins)*35)
2229 grid = QGridLayout()
2230 grid.setColumnStretch(0,1)
2233 def do_toggle(cb, p, w):
2236 if w: w.setEnabled(r)
2238 def mk_toggle(cb, p, w):
2239 return lambda: do_toggle(cb,p,w)
2241 for i, p in enumerate(plugins):
2243 cb = QCheckBox(p.fullname())
2244 cb.setDisabled(not p.is_available())
2245 cb.setChecked(p.is_enabled())
2246 grid.addWidget(cb, i, 0)
2247 if p.requires_settings():
2248 w = p.settings_widget(self)
2249 w.setEnabled( p.is_enabled() )
2250 grid.addWidget(w, i, 1)
2253 cb.clicked.connect(mk_toggle(cb,p,w))
2254 grid.addWidget(HelpButton(p.description()), i, 2)
2256 print_msg(_("Error: cannot display plugin"), p)
2257 traceback.print_exc(file=sys.stdout)
2258 grid.setRowStretch(i+1,1)
2260 vbox.addLayout(close_button(d))
2265 def show_account_details(self, k):
2266 account = self.wallet.accounts[k]
2269 d.setWindowTitle(_('Account Details'))
2272 vbox = QVBoxLayout(d)
2273 name = self.wallet.get_account_name(k)
2274 label = QLabel('Name: ' + name)
2275 vbox.addWidget(label)
2277 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2279 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2281 vbox.addWidget(QLabel(_('Master Public Key:')))
2284 text.setReadOnly(True)
2285 text.setMaximumHeight(170)
2286 vbox.addWidget(text)
2288 mpk_text = '\n'.join( account.get_master_pubkeys() )
2289 text.setText(mpk_text)
2291 vbox.addLayout(close_button(d))