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() )
182 self.connect(self, QtCore.SIGNAL('send_tx2'), self.send_tx2)
184 self.history_list.setFocus(True)
188 self.network.register_callback('updated', lambda: self.need_update.set())
189 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
190 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
191 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
192 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
194 # set initial message
195 self.console.showMessage(self.network.banner)
202 self.config.set_key('lite_mode', False, True)
208 self.config.set_key('lite_mode', True, True)
216 if not self.check_qt_version():
217 if self.config.get('lite_mode') is True:
218 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
219 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
220 self.config.set_key('lite_mode', False, True)
227 actuator = lite_window.MiniActuator(self)
229 actuator.load_theme()
231 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
233 driver = lite_window.MiniDriver(self, self.mini)
235 if self.config.get('lite_mode') is True:
241 def check_qt_version(self):
242 qtVersion = qVersion()
243 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
246 def update_account_selector(self):
248 accounts = self.wallet.get_account_names()
249 self.account_selector.clear()
250 if len(accounts) > 1:
251 self.account_selector.addItems([_("All accounts")] + accounts.values())
252 self.account_selector.setCurrentIndex(0)
253 self.account_selector.show()
255 self.account_selector.hide()
258 def load_wallet(self, wallet):
261 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
262 self.current_account = self.wallet.storage.get("current_account", None)
264 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
265 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
266 self.setWindowTitle( title )
268 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
269 self.notify_transactions()
270 self.update_account_selector()
271 self.new_account.setEnabled(self.wallet.seed_version>4)
272 self.update_lock_icon()
273 self.update_buttons_on_seed()
274 self.update_console()
276 run_hook('load_wallet', wallet)
279 def open_wallet(self):
280 wallet_folder = self.wallet.storage.path
281 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
285 storage = WalletStorage({'wallet_path': filename})
286 if not storage.file_exists:
287 self.show_message("file not found "+ filename)
290 self.wallet.stop_threads()
293 wallet = Wallet(storage)
294 wallet.start_threads(self.network)
296 self.load_wallet(wallet)
300 def backup_wallet(self):
302 path = self.wallet.storage.path
303 wallet_folder = os.path.dirname(path)
304 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
308 new_path = os.path.join(wallet_folder, filename)
311 shutil.copy2(path, new_path)
312 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
313 except (IOError, os.error), reason:
314 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
317 def new_wallet(self):
320 wallet_folder = os.path.dirname(self.wallet.storage.path)
321 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
324 filename = os.path.join(wallet_folder, filename)
326 storage = WalletStorage({'wallet_path': filename})
327 if storage.file_exists:
328 QMessageBox.critical(None, "Error", _("File exists"))
331 wizard = installwizard.InstallWizard(self.config, self.network, storage)
332 wallet = wizard.run()
334 self.load_wallet(wallet)
338 def init_menubar(self):
341 file_menu = menubar.addMenu(_("&File"))
342 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
343 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
344 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
345 file_menu.addAction(_("&Quit"), self.close)
347 wallet_menu = menubar.addMenu(_("&Wallet"))
348 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
349 self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
351 wallet_menu.addSeparator()
353 wallet_menu.addAction(_("&Password"), self.change_password_dialog)
354 wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
355 wallet_menu.addAction(_("&Master Public Key"), self.show_master_public_key)
357 wallet_menu.addSeparator()
358 labels_menu = wallet_menu.addMenu(_("&Labels"))
359 labels_menu.addAction(_("&Import"), self.do_import_labels)
360 labels_menu.addAction(_("&Export"), self.do_export_labels)
362 keys_menu = wallet_menu.addMenu(_("&Private keys"))
363 keys_menu.addAction(_("&Import"), self.do_import_privkey)
364 keys_menu.addAction(_("&Export"), self.do_export_privkeys)
366 wallet_menu.addAction(_("&Export History"), self.do_export_history)
368 tools_menu = menubar.addMenu(_("&Tools"))
370 # Settings / Preferences are all reserved keywords in OSX using this as work around
371 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
372 tools_menu.addAction(_("&Network"), self.run_network_dialog)
373 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
374 tools_menu.addSeparator()
375 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
376 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
377 tools_menu.addSeparator()
379 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
380 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
381 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
383 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
384 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
385 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
386 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
388 help_menu = menubar.addMenu(_("&Help"))
389 help_menu.addAction(_("&About"), self.show_about)
390 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
391 help_menu.addSeparator()
392 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
393 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
395 self.setMenuBar(menubar)
397 def show_about(self):
398 QMessageBox.about(self, "Electrum",
399 _("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."))
401 def show_report_bug(self):
402 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
403 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
406 def notify_transactions(self):
407 if not self.network or not self.network.is_connected():
410 print_error("Notifying GUI")
411 if len(self.network.interface.pending_transactions_for_notifications) > 0:
412 # Combine the transactions if there are more then three
413 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
416 for tx in self.network.interface.pending_transactions_for_notifications:
417 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
421 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
422 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
424 self.network.interface.pending_transactions_for_notifications = []
426 for tx in self.network.interface.pending_transactions_for_notifications:
428 self.network.interface.pending_transactions_for_notifications.remove(tx)
429 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
431 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
433 def notify(self, message):
434 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
438 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
439 def getOpenFileName(self, title, filter = ""):
440 directory = self.config.get('io_dir', os.path.expanduser('~'))
441 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
442 if fileName and directory != os.path.dirname(fileName):
443 self.config.set_key('io_dir', os.path.dirname(fileName), True)
446 def getSaveFileName(self, title, filename, filter = ""):
447 directory = self.config.get('io_dir', os.path.expanduser('~'))
448 path = os.path.join( directory, filename )
449 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
450 if fileName and directory != os.path.dirname(fileName):
451 self.config.set_key('io_dir', os.path.dirname(fileName), True)
455 QMainWindow.close(self)
456 run_hook('close_main_window')
458 def connect_slots(self, sender):
459 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
460 self.previous_payto_e=''
462 def timer_actions(self):
463 if self.need_update.is_set():
465 self.need_update.clear()
466 run_hook('timer_actions')
468 def format_amount(self, x, is_diff=False, whitespaces=False):
469 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
471 def read_amount(self, x):
472 if x in['.', '']: return None
473 p = pow(10, self.decimal_point)
474 return int( p * Decimal(x) )
477 assert self.decimal_point in [5,8]
478 return "BTC" if self.decimal_point == 8 else "mBTC"
481 def update_status(self):
482 if self.network is None or not self.network.is_running():
484 icon = QIcon(":icons/status_disconnected.png")
486 elif self.network.is_connected():
487 if not self.wallet.up_to_date:
488 text = _("Synchronizing...")
489 icon = QIcon(":icons/status_waiting.png")
490 elif self.network.server_lag > 1:
491 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
492 icon = QIcon(":icons/status_lagging.png")
494 c, u = self.wallet.get_account_balance(self.current_account)
495 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
496 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
498 # append fiat balance and price from exchange rate plugin
500 run_hook('get_fiat_status_text', c+u, r)
505 self.tray.setToolTip(text)
506 icon = QIcon(":icons/status_connected.png")
508 text = _("Not connected")
509 icon = QIcon(":icons/status_disconnected.png")
511 self.balance_label.setText(text)
512 self.status_button.setIcon( icon )
515 def update_wallet(self):
517 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
518 self.update_history_tab()
519 self.update_receive_tab()
520 self.update_contacts_tab()
521 self.update_completions()
524 def create_history_tab(self):
525 self.history_list = l = MyTreeWidget(self)
527 for i,width in enumerate(self.column_widths['history']):
528 l.setColumnWidth(i, width)
529 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
530 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
531 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
533 l.customContextMenuRequested.connect(self.create_history_menu)
537 def create_history_menu(self, position):
538 self.history_list.selectedIndexes()
539 item = self.history_list.currentItem()
541 tx_hash = str(item.data(0, Qt.UserRole).toString())
542 if not tx_hash: return
544 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
545 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
546 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
547 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
548 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
551 def show_transaction(self, tx):
552 import transaction_dialog
553 d = transaction_dialog.TxDialog(tx, self)
556 def tx_label_clicked(self, item, column):
557 if column==2 and item.isSelected():
559 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
560 self.history_list.editItem( item, column )
561 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
564 def tx_label_changed(self, item, column):
568 tx_hash = str(item.data(0, Qt.UserRole).toString())
569 tx = self.wallet.transactions.get(tx_hash)
570 text = unicode( item.text(2) )
571 self.wallet.set_label(tx_hash, text)
573 item.setForeground(2, QBrush(QColor('black')))
575 text = self.wallet.get_default_label(tx_hash)
576 item.setText(2, text)
577 item.setForeground(2, QBrush(QColor('gray')))
581 def edit_label(self, is_recv):
582 l = self.receive_list if is_recv else self.contacts_list
583 item = l.currentItem()
584 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
585 l.editItem( item, 1 )
586 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
590 def address_label_clicked(self, item, column, l, column_addr, column_label):
591 if column == column_label and item.isSelected():
592 is_editable = item.data(0, 32).toBool()
595 addr = unicode( item.text(column_addr) )
596 label = unicode( item.text(column_label) )
597 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
598 l.editItem( item, column )
599 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
602 def address_label_changed(self, item, column, l, column_addr, column_label):
603 if column == column_label:
604 addr = unicode( item.text(column_addr) )
605 text = unicode( item.text(column_label) )
606 is_editable = item.data(0, 32).toBool()
610 changed = self.wallet.set_label(addr, text)
612 self.update_history_tab()
613 self.update_completions()
615 self.current_item_changed(item)
617 run_hook('item_changed', item, column)
620 def current_item_changed(self, a):
621 run_hook('current_item_changed', a)
625 def update_history_tab(self):
627 self.history_list.clear()
628 for item in self.wallet.get_tx_history(self.current_account):
629 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
630 time_str = _("unknown")
633 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
635 time_str = _("error")
638 time_str = 'unverified'
639 icon = QIcon(":icons/unconfirmed.png")
642 icon = QIcon(":icons/unconfirmed.png")
644 icon = QIcon(":icons/clock%d.png"%conf)
646 icon = QIcon(":icons/confirmed.png")
648 if value is not None:
649 v_str = self.format_amount(value, True, whitespaces=True)
653 balance_str = self.format_amount(balance, whitespaces=True)
656 label, is_default_label = self.wallet.get_label(tx_hash)
658 label = _('Pruned transaction outputs')
659 is_default_label = False
661 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
662 item.setFont(2, QFont(MONOSPACE_FONT))
663 item.setFont(3, QFont(MONOSPACE_FONT))
664 item.setFont(4, QFont(MONOSPACE_FONT))
666 item.setForeground(3, QBrush(QColor("#BC1E1E")))
668 item.setData(0, Qt.UserRole, tx_hash)
669 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
671 item.setForeground(2, QBrush(QColor('grey')))
673 item.setIcon(0, icon)
674 self.history_list.insertTopLevelItem(0,item)
677 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
678 run_hook('history_tab_update')
681 def create_send_tab(self):
686 grid.setColumnMinimumWidth(3,300)
687 grid.setColumnStretch(5,1)
690 self.payto_e = QLineEdit()
691 grid.addWidget(QLabel(_('Pay to')), 1, 0)
692 grid.addWidget(self.payto_e, 1, 1, 1, 3)
694 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)
696 completer = QCompleter()
697 completer.setCaseSensitivity(False)
698 self.payto_e.setCompleter(completer)
699 completer.setModel(self.completions)
701 self.message_e = QLineEdit()
702 grid.addWidget(QLabel(_('Description')), 2, 0)
703 grid.addWidget(self.message_e, 2, 1, 1, 3)
704 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)
706 self.from_label = QLabel(_('From'))
707 grid.addWidget(self.from_label, 3, 0)
708 self.from_list = QTreeWidget(self)
709 self.from_list.setColumnCount(2)
710 self.from_list.setColumnWidth(0, 350)
711 self.from_list.setColumnWidth(1, 50)
712 self.from_list.setHeaderHidden (True)
713 self.from_list.setMaximumHeight(80)
714 grid.addWidget(self.from_list, 3, 1, 1, 3)
715 self.set_pay_from([])
717 self.amount_e = AmountEdit(self.base_unit)
718 grid.addWidget(QLabel(_('Amount')), 4, 0)
719 grid.addWidget(self.amount_e, 4, 1, 1, 2)
720 grid.addWidget(HelpButton(
721 _('Amount to be sent.') + '\n\n' \
722 + _('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.') \
723 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
725 self.fee_e = AmountEdit(self.base_unit)
726 grid.addWidget(QLabel(_('Fee')), 5, 0)
727 grid.addWidget(self.fee_e, 5, 1, 1, 2)
728 grid.addWidget(HelpButton(
729 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
730 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
731 + _('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)
733 run_hook('exchange_rate_button', grid)
735 self.send_button = EnterButton(_("Send"), self.do_send)
736 grid.addWidget(self.send_button, 6, 1)
738 b = EnterButton(_("Clear"),self.do_clear)
739 grid.addWidget(b, 6, 2)
741 self.payto_sig = QLabel('')
742 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
744 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
745 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
754 def entry_changed( is_fee ):
755 self.funds_error = False
757 if self.amount_e.is_shortcut:
758 self.amount_e.is_shortcut = False
759 sendable = self.get_sendable_balance()
760 # there is only one output because we are completely spending inputs
761 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
762 fee = self.wallet.estimated_fee(inputs, 1)
764 self.amount_e.setText( self.format_amount(amount) )
765 self.fee_e.setText( self.format_amount( fee ) )
768 amount = self.read_amount(str(self.amount_e.text()))
769 fee = self.read_amount(str(self.fee_e.text()))
771 if not is_fee: fee = None
774 # assume that there will be 2 outputs (one for change)
775 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
777 self.fee_e.setText( self.format_amount( fee ) )
780 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
784 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
785 self.funds_error = True
786 text = _( "Not enough funds" )
787 c, u = self.wallet.get_frozen_balance()
788 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
790 self.statusBar().showMessage(text)
791 self.amount_e.setPalette(palette)
792 self.fee_e.setPalette(palette)
794 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
795 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
797 run_hook('create_send_tab', grid)
801 def set_pay_from(self, l):
803 self.from_list.clear()
804 self.from_label.setHidden(len(self.pay_from) == 0)
805 self.from_list.setHidden(len(self.pay_from) == 0)
806 for addr in self.pay_from:
807 c, u = self.wallet.get_addr_balance(addr)
808 balance = self.format_amount(c + u)
809 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
812 def update_completions(self):
814 for addr,label in self.wallet.labels.items():
815 if addr in self.wallet.addressbook:
816 l.append( label + ' <' + addr + '>')
818 run_hook('update_completions', l)
819 self.completions.setStringList(l)
823 return lambda s, *args: s.do_protect(func, args)
828 label = unicode( self.message_e.text() )
829 r = unicode( self.payto_e.text() )
832 # label or alias, with address in brackets
833 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
834 to_address = m.group(2) if m else r
836 if not is_valid(to_address):
837 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
841 amount = self.read_amount(unicode( self.amount_e.text()))
843 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
846 fee = self.read_amount(unicode( self.fee_e.text()))
848 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
851 confirm_amount = self.config.get('confirm_amount', 100000000)
852 if amount >= confirm_amount:
853 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
856 confirm_fee = self.config.get('confirm_fee', 100000)
857 if fee >= confirm_fee:
858 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()}):
861 self.send_tx(to_address, amount, fee, label)
864 def waiting_dialog(self, message):
866 d.setWindowTitle('Please wait')
868 vbox = QVBoxLayout(d)
875 def send_tx(self, to_address, amount, fee, label, password):
877 # first, create an unsigned tx
878 domain = self.get_payment_sources()
879 outputs = [(to_address, amount)]
881 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
883 except Exception as e:
884 traceback.print_exc(file=sys.stdout)
885 self.show_message(str(e))
888 # call hook to see if plugin needs gui interaction
889 run_hook('send_tx', tx)
894 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
895 self.wallet.sign_transaction(tx, keypairs, password)
896 self.signed_tx_data = (tx, fee, label)
897 self.emit(SIGNAL('send_tx2'))
900 self.tx_wait_dialog = self.waiting_dialog('Signing..')
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)
909 tx, fee, label = self.signed_tx_data
910 self.tx_wait_dialog.accept()
913 self.show_message(tx.error)
916 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
917 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
921 self.wallet.set_label(tx.hash(), label)
925 d = self.waiting_dialog('Broadcasting...')
926 h = self.wallet.send_tx(tx)
927 self.wallet.tx_event.wait()
930 status, msg = self.wallet.receive_tx( h, tx )
932 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
934 self.update_contacts_tab()
936 QMessageBox.warning(self, _('Error'), msg, _('OK'))
939 self.show_transaction(tx)
944 def set_url(self, url):
945 address, amount, label, message, signature, identity, url = util.parse_url(url)
948 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
949 elif amount: amount = str(Decimal(amount))
952 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
955 self.mini.set_payment_fields(address, amount)
957 if label and self.wallet.labels.get(address) != label:
958 if self.question('Give label "%s" to address %s ?'%(label,address)):
959 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
960 self.wallet.addressbook.append(address)
961 self.wallet.set_label(address, label)
963 run_hook('set_url', url, self.show_message, self.question)
965 self.tabs.setCurrentIndex(1)
966 label = self.wallet.labels.get(address)
967 m_addr = label + ' <'+ address +'>' if label else address
968 self.payto_e.setText(m_addr)
970 self.message_e.setText(message)
972 self.amount_e.setText(amount)
975 self.set_frozen(self.payto_e,True)
976 self.set_frozen(self.amount_e,True)
977 self.set_frozen(self.message_e,True)
978 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
980 self.payto_sig.setVisible(False)
983 self.payto_sig.setVisible(False)
984 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
986 self.set_frozen(e,False)
988 self.set_pay_from([])
991 def set_frozen(self,entry,frozen):
993 entry.setReadOnly(True)
994 entry.setFrame(False)
996 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
997 entry.setPalette(palette)
999 entry.setReadOnly(False)
1000 entry.setFrame(True)
1001 palette = QPalette()
1002 palette.setColor(entry.backgroundRole(), QColor('white'))
1003 entry.setPalette(palette)
1006 def set_addrs_frozen(self,addrs,freeze):
1008 if not addr: continue
1009 if addr in self.wallet.frozen_addresses and not freeze:
1010 self.wallet.unfreeze(addr)
1011 elif addr not in self.wallet.frozen_addresses and freeze:
1012 self.wallet.freeze(addr)
1013 self.update_receive_tab()
1017 def create_list_tab(self, headers):
1018 "generic tab creation method"
1019 l = MyTreeWidget(self)
1020 l.setColumnCount( len(headers) )
1021 l.setHeaderLabels( headers )
1024 vbox = QVBoxLayout()
1031 vbox.addWidget(buttons)
1033 hbox = QHBoxLayout()
1036 buttons.setLayout(hbox)
1041 def create_receive_tab(self):
1042 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1043 l.setContextMenuPolicy(Qt.CustomContextMenu)
1044 l.customContextMenuRequested.connect(self.create_receive_menu)
1045 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1046 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1047 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1048 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1049 self.receive_list = l
1050 self.receive_buttons_hbox = hbox
1057 def save_column_widths(self):
1058 self.column_widths["receive"] = []
1059 for i in range(self.receive_list.columnCount() -1):
1060 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1062 self.column_widths["history"] = []
1063 for i in range(self.history_list.columnCount() - 1):
1064 self.column_widths["history"].append(self.history_list.columnWidth(i))
1066 self.column_widths["contacts"] = []
1067 for i in range(self.contacts_list.columnCount() - 1):
1068 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1070 self.config.set_key("column_widths_2", self.column_widths, True)
1073 def create_contacts_tab(self):
1074 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1075 l.setContextMenuPolicy(Qt.CustomContextMenu)
1076 l.customContextMenuRequested.connect(self.create_contact_menu)
1077 for i,width in enumerate(self.column_widths['contacts']):
1078 l.setColumnWidth(i, width)
1080 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1081 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1082 self.contacts_list = l
1083 self.contacts_buttons_hbox = hbox
1088 def delete_imported_key(self, addr):
1089 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1090 self.wallet.delete_imported_key(addr)
1091 self.update_receive_tab()
1092 self.update_history_tab()
1094 def edit_account_label(self, k):
1095 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1097 label = unicode(text)
1098 self.wallet.set_label(k,label)
1099 self.update_receive_tab()
1101 def account_set_expanded(self, item, k, b):
1103 self.accounts_expanded[k] = b
1105 def create_account_menu(self, position, k, item):
1107 if item.isExpanded():
1108 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1110 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1111 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1112 if self.wallet.seed_version > 4:
1113 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1114 if self.wallet.account_is_pending(k):
1115 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1116 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1118 def delete_pending_account(self, k):
1119 self.wallet.delete_pending_account(k)
1120 self.update_receive_tab()
1122 def create_receive_menu(self, position):
1123 # fixme: this function apparently has a side effect.
1124 # if it is not called the menu pops up several times
1125 #self.receive_list.selectedIndexes()
1127 selected = self.receive_list.selectedItems()
1128 multi_select = len(selected) > 1
1129 addrs = [unicode(item.text(0)) for item in selected]
1130 if not multi_select:
1131 item = self.receive_list.itemAt(position)
1135 if not is_valid(addr):
1136 k = str(item.data(0,32).toString())
1138 self.create_account_menu(position, k, item)
1140 item.setExpanded(not item.isExpanded())
1144 if not multi_select:
1145 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1146 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1147 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1148 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1149 if self.wallet.seed:
1150 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1151 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1152 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1153 if addr in self.wallet.imported_keys:
1154 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1156 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1157 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1158 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1159 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1161 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1162 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1164 run_hook('receive_menu', menu, addrs)
1165 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1168 def get_sendable_balance(self):
1169 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1172 def get_payment_sources(self):
1174 return self.pay_from
1176 return self.wallet.get_account_addresses(self.current_account)
1179 def send_from_addresses(self, addrs):
1180 self.set_pay_from( addrs )
1181 self.tabs.setCurrentIndex(1)
1184 def payto(self, addr):
1186 label = self.wallet.labels.get(addr)
1187 m_addr = label + ' <' + addr + '>' if label else addr
1188 self.tabs.setCurrentIndex(1)
1189 self.payto_e.setText(m_addr)
1190 self.amount_e.setFocus()
1193 def delete_contact(self, x):
1194 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1195 self.wallet.delete_contact(x)
1196 self.wallet.set_label(x, None)
1197 self.update_history_tab()
1198 self.update_contacts_tab()
1199 self.update_completions()
1202 def create_contact_menu(self, position):
1203 item = self.contacts_list.itemAt(position)
1206 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1208 addr = unicode(item.text(0))
1209 label = unicode(item.text(1))
1210 is_editable = item.data(0,32).toBool()
1211 payto_addr = item.data(0,33).toString()
1212 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1213 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1214 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1216 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1217 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1219 run_hook('create_contact_menu', menu, item)
1220 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1223 def update_receive_item(self, item):
1224 item.setFont(0, QFont(MONOSPACE_FONT))
1225 address = str(item.data(0,0).toString())
1226 label = self.wallet.labels.get(address,'')
1227 item.setData(1,0,label)
1228 item.setData(0,32, True) # is editable
1230 run_hook('update_receive_item', address, item)
1232 if not self.wallet.is_mine(address): return
1234 c, u = self.wallet.get_addr_balance(address)
1235 balance = self.format_amount(c + u)
1236 item.setData(2,0,balance)
1238 if address in self.wallet.frozen_addresses:
1239 item.setBackgroundColor(0, QColor('lightblue'))
1242 def update_receive_tab(self):
1243 l = self.receive_list
1246 l.setColumnHidden(2, False)
1247 l.setColumnHidden(3, False)
1248 for i,width in enumerate(self.column_widths['receive']):
1249 l.setColumnWidth(i, width)
1251 if self.current_account is None:
1252 account_items = self.wallet.accounts.items()
1253 elif self.current_account != -1:
1254 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1258 for k, account in account_items:
1259 name = self.wallet.get_account_name(k)
1260 c,u = self.wallet.get_account_balance(k)
1261 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1262 l.addTopLevelItem(account_item)
1263 account_item.setExpanded(self.accounts_expanded.get(k, True))
1264 account_item.setData(0, 32, k)
1266 if not self.wallet.is_seeded(k):
1267 icon = QIcon(":icons/key.png")
1268 account_item.setIcon(0, icon)
1270 for is_change in ([0,1]):
1271 name = _("Receiving") if not is_change else _("Change")
1272 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1273 account_item.addChild(seq_item)
1274 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1276 if not is_change: seq_item.setExpanded(True)
1281 for address in account.get_addresses(is_change):
1282 h = self.wallet.history.get(address,[])
1286 if gap > self.wallet.gap_limit:
1291 c, u = self.wallet.get_addr_balance(address)
1292 num_tx = '*' if h == ['*'] else "%d"%len(h)
1293 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1294 self.update_receive_item(item)
1296 item.setBackgroundColor(1, QColor('red'))
1297 if len(h) > 0 and c == -u:
1299 seq_item.insertChild(0,used_item)
1301 used_item.addChild(item)
1303 seq_item.addChild(item)
1306 for k, addr in self.wallet.get_pending_accounts():
1307 name = self.wallet.labels.get(k,'')
1308 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1309 self.update_receive_item(item)
1310 l.addTopLevelItem(account_item)
1311 account_item.setExpanded(True)
1312 account_item.setData(0, 32, k)
1313 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1314 account_item.addChild(item)
1315 self.update_receive_item(item)
1318 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1319 c,u = self.wallet.get_imported_balance()
1320 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1321 l.addTopLevelItem(account_item)
1322 account_item.setExpanded(True)
1323 for address in self.wallet.imported_keys.keys():
1324 item = QTreeWidgetItem( [ address, '', '', ''] )
1325 self.update_receive_item(item)
1326 account_item.addChild(item)
1329 # we use column 1 because column 0 may be hidden
1330 l.setCurrentItem(l.topLevelItem(0),1)
1333 def update_contacts_tab(self):
1334 l = self.contacts_list
1337 for address in self.wallet.addressbook:
1338 label = self.wallet.labels.get(address,'')
1339 n = self.wallet.get_num_tx(address)
1340 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1341 item.setFont(0, QFont(MONOSPACE_FONT))
1342 # 32 = label can be edited (bool)
1343 item.setData(0,32, True)
1345 item.setData(0,33, address)
1346 l.addTopLevelItem(item)
1348 run_hook('update_contacts_tab', l)
1349 l.setCurrentItem(l.topLevelItem(0))
1353 def create_console_tab(self):
1354 from console import Console
1355 self.console = console = Console()
1359 def update_console(self):
1360 console = self.console
1361 console.history = self.config.get("console-history",[])
1362 console.history_index = len(console.history)
1364 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1365 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1367 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1369 def mkfunc(f, method):
1370 return lambda *args: apply( f, (method, args, self.password_dialog ))
1372 if m[0]=='_' or m in ['network','wallet']: continue
1373 methods[m] = mkfunc(c._run, m)
1375 console.updateNamespace(methods)
1378 def change_account(self,s):
1379 if s == _("All accounts"):
1380 self.current_account = None
1382 accounts = self.wallet.get_account_names()
1383 for k, v in accounts.items():
1385 self.current_account = k
1386 self.update_history_tab()
1387 self.update_status()
1388 self.update_receive_tab()
1390 def create_status_bar(self):
1393 sb.setFixedHeight(35)
1394 qtVersion = qVersion()
1396 self.balance_label = QLabel("")
1397 sb.addWidget(self.balance_label)
1399 from version_getter import UpdateLabel
1400 self.updatelabel = UpdateLabel(self.config, sb)
1402 self.account_selector = QComboBox()
1403 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1404 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1405 sb.addPermanentWidget(self.account_selector)
1407 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1408 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1410 self.lock_icon = QIcon()
1411 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1412 sb.addPermanentWidget( self.password_button )
1414 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1415 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1416 sb.addPermanentWidget( self.seed_button )
1417 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1418 sb.addPermanentWidget( self.status_button )
1420 run_hook('create_status_bar', (sb,))
1422 self.setStatusBar(sb)
1425 def update_lock_icon(self):
1426 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1427 self.password_button.setIcon( icon )
1430 def update_buttons_on_seed(self):
1431 if not self.wallet.is_watching_only():
1432 self.seed_button.show()
1433 self.password_button.show()
1434 self.send_button.setText(_("Send"))
1436 self.password_button.hide()
1437 self.seed_button.hide()
1438 self.send_button.setText(_("Create unsigned transaction"))
1441 def change_password_dialog(self):
1442 from password_dialog import PasswordDialog
1443 d = PasswordDialog(self.wallet, self)
1445 self.update_lock_icon()
1448 def new_contact_dialog(self):
1451 d.setWindowTitle(_("New Contact"))
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)
1487 def new_account_dialog(self, password):
1489 dialog = QDialog(self)
1491 dialog.setWindowTitle(_("New Account"))
1493 vbox = QVBoxLayout()
1494 vbox.addWidget(QLabel(_('Account name')+':'))
1497 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1498 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1503 vbox.addLayout(ok_cancel_buttons(dialog))
1504 dialog.setLayout(vbox)
1508 name = str(e.text())
1511 self.wallet.create_pending_account('1of1', name, password)
1512 self.update_receive_tab()
1513 self.tabs.setCurrentIndex(2)
1517 def show_master_public_key_old(self):
1518 dialog = QDialog(self)
1520 dialog.setWindowTitle(_("Master Public Key"))
1522 main_text = QTextEdit()
1523 main_text.setText(self.wallet.get_master_public_key())
1524 main_text.setReadOnly(True)
1525 main_text.setMaximumHeight(170)
1526 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1528 ok_button = QPushButton(_("OK"))
1529 ok_button.setDefault(True)
1530 ok_button.clicked.connect(dialog.accept)
1532 main_layout = QGridLayout()
1533 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1535 main_layout.addWidget(main_text, 1, 0)
1536 main_layout.addWidget(qrw, 1, 1 )
1538 vbox = QVBoxLayout()
1539 vbox.addLayout(main_layout)
1540 vbox.addLayout(close_button(dialog))
1541 dialog.setLayout(vbox)
1545 def show_master_public_key(self):
1547 if self.wallet.seed_version == 4:
1548 self.show_master_public_key_old()
1551 dialog = QDialog(self)
1553 dialog.setWindowTitle(_("Master Public Keys"))
1555 mpk_text = QTextEdit()
1556 mpk_text.setReadOnly(True)
1557 mpk_text.setMaximumHeight(170)
1558 mpk_qrw = QRCodeWidget()
1560 main_layout = QGridLayout()
1562 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1563 main_layout.addWidget(mpk_text, 1, 1)
1564 main_layout.addWidget(mpk_qrw, 1, 2)
1567 xpub = self.wallet.master_public_keys[str(key)]
1568 mpk_text.setText(xpub)
1569 mpk_qrw.set_addr(xpub)
1572 key_selector = QComboBox()
1573 keys = sorted(self.wallet.master_public_keys.keys())
1574 key_selector.addItems(keys)
1576 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1577 main_layout.addWidget(key_selector, 0, 1)
1578 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1582 vbox = QVBoxLayout()
1583 vbox.addLayout(main_layout)
1584 vbox.addLayout(close_button(dialog))
1586 dialog.setLayout(vbox)
1591 def show_seed_dialog(self, password):
1592 if self.wallet.is_watching_only():
1593 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1596 if self.wallet.seed:
1598 mnemonic = self.wallet.get_mnemonic(password)
1600 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1602 from seed_dialog import SeedDialog
1603 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1607 for k in self.wallet.master_private_keys.keys():
1608 pk = self.wallet.get_master_private_key(k, password)
1610 from seed_dialog import PrivateKeysDialog
1611 d = PrivateKeysDialog(self,l)
1618 def show_qrcode(self, data, title = _("QR code")):
1622 d.setWindowTitle(title)
1623 d.setMinimumSize(270, 300)
1624 vbox = QVBoxLayout()
1625 qrw = QRCodeWidget(data)
1626 vbox.addWidget(qrw, 1)
1627 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1628 hbox = QHBoxLayout()
1631 filename = os.path.join(self.config.path, "qrcode.bmp")
1634 bmp.save_qrcode(qrw.qr, filename)
1635 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1637 def copy_to_clipboard():
1638 bmp.save_qrcode(qrw.qr, filename)
1639 self.app.clipboard().setImage(QImage(filename))
1640 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1642 b = QPushButton(_("Copy"))
1644 b.clicked.connect(copy_to_clipboard)
1646 b = QPushButton(_("Save"))
1648 b.clicked.connect(print_qr)
1650 b = QPushButton(_("Close"))
1652 b.clicked.connect(d.accept)
1655 vbox.addLayout(hbox)
1660 def do_protect(self, func, args):
1661 if self.wallet.use_encryption:
1662 password = self.password_dialog()
1668 if args != (False,):
1669 args = (self,) + args + (password,)
1671 args = (self,password)
1675 def show_public_keys(self, address):
1676 if not address: return
1678 pubkey_list = self.wallet.get_public_keys(address)
1679 except Exception as e:
1680 traceback.print_exc(file=sys.stdout)
1681 self.show_message(str(e))
1685 d.setMinimumSize(600, 200)
1687 vbox = QVBoxLayout()
1688 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1689 vbox.addWidget( QLabel(_("Public key") + ':'))
1691 keys.setReadOnly(True)
1692 keys.setText('\n'.join(pubkey_list))
1693 vbox.addWidget(keys)
1694 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1695 vbox.addLayout(close_button(d))
1700 def show_private_key(self, address, password):
1701 if not address: return
1703 pk_list = self.wallet.get_private_key(address, password)
1704 except Exception as e:
1705 traceback.print_exc(file=sys.stdout)
1706 self.show_message(str(e))
1710 d.setMinimumSize(600, 200)
1712 vbox = QVBoxLayout()
1713 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1714 vbox.addWidget( QLabel(_("Private key") + ':'))
1716 keys.setReadOnly(True)
1717 keys.setText('\n'.join(pk_list))
1718 vbox.addWidget(keys)
1719 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1720 vbox.addLayout(close_button(d))
1726 def do_sign(self, address, message, signature, password):
1727 message = unicode(message.toPlainText())
1728 message = message.encode('utf-8')
1730 sig = self.wallet.sign_message(str(address.text()), message, password)
1731 signature.setText(sig)
1732 except Exception as e:
1733 self.show_message(str(e))
1735 def do_verify(self, address, message, signature):
1736 message = unicode(message.toPlainText())
1737 message = message.encode('utf-8')
1738 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1739 self.show_message(_("Signature verified"))
1741 self.show_message(_("Error: wrong signature"))
1744 def sign_verify_message(self, address=''):
1747 d.setWindowTitle(_('Sign/verify Message'))
1748 d.setMinimumSize(410, 290)
1750 layout = QGridLayout(d)
1752 message_e = QTextEdit()
1753 layout.addWidget(QLabel(_('Message')), 1, 0)
1754 layout.addWidget(message_e, 1, 1)
1755 layout.setRowStretch(2,3)
1757 address_e = QLineEdit()
1758 address_e.setText(address)
1759 layout.addWidget(QLabel(_('Address')), 2, 0)
1760 layout.addWidget(address_e, 2, 1)
1762 signature_e = QTextEdit()
1763 layout.addWidget(QLabel(_('Signature')), 3, 0)
1764 layout.addWidget(signature_e, 3, 1)
1765 layout.setRowStretch(3,1)
1767 hbox = QHBoxLayout()
1769 b = QPushButton(_("Sign"))
1770 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1773 b = QPushButton(_("Verify"))
1774 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1777 b = QPushButton(_("Close"))
1778 b.clicked.connect(d.accept)
1780 layout.addLayout(hbox, 4, 1)
1785 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1787 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1788 message_e.setText(decrypted)
1789 except Exception as e:
1790 self.show_message(str(e))
1793 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1794 message = unicode(message_e.toPlainText())
1795 message = message.encode('utf-8')
1797 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1798 encrypted_e.setText(encrypted)
1799 except Exception as e:
1800 self.show_message(str(e))
1804 def encrypt_message(self, address = ''):
1807 d.setWindowTitle(_('Encrypt/decrypt Message'))
1808 d.setMinimumSize(610, 490)
1810 layout = QGridLayout(d)
1812 message_e = QTextEdit()
1813 layout.addWidget(QLabel(_('Message')), 1, 0)
1814 layout.addWidget(message_e, 1, 1)
1815 layout.setRowStretch(2,3)
1817 pubkey_e = QLineEdit()
1819 pubkey = self.wallet.getpubkeys(address)[0]
1820 pubkey_e.setText(pubkey)
1821 layout.addWidget(QLabel(_('Public key')), 2, 0)
1822 layout.addWidget(pubkey_e, 2, 1)
1824 encrypted_e = QTextEdit()
1825 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1826 layout.addWidget(encrypted_e, 3, 1)
1827 layout.setRowStretch(3,1)
1829 hbox = QHBoxLayout()
1830 b = QPushButton(_("Encrypt"))
1831 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1834 b = QPushButton(_("Decrypt"))
1835 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1838 b = QPushButton(_("Close"))
1839 b.clicked.connect(d.accept)
1842 layout.addLayout(hbox, 4, 1)
1846 def question(self, msg):
1847 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1849 def show_message(self, msg):
1850 QMessageBox.information(self, _('Message'), msg, _('OK'))
1852 def password_dialog(self ):
1855 d.setWindowTitle(_("Enter Password"))
1860 vbox = QVBoxLayout()
1861 msg = _('Please enter your password')
1862 vbox.addWidget(QLabel(msg))
1864 grid = QGridLayout()
1866 grid.addWidget(QLabel(_('Password')), 1, 0)
1867 grid.addWidget(pw, 1, 1)
1868 vbox.addLayout(grid)
1870 vbox.addLayout(ok_cancel_buttons(d))
1873 run_hook('password_dialog', pw, grid, 1)
1874 if not d.exec_(): return
1875 return unicode(pw.text())
1884 def tx_from_text(self, txt):
1885 "json or raw hexadecimal"
1888 tx = Transaction(txt)
1894 tx_dict = json.loads(str(txt))
1895 assert "hex" in tx_dict.keys()
1896 assert "complete" in tx_dict.keys()
1897 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1898 if not tx_dict["complete"]:
1899 assert "input_info" in tx_dict.keys()
1900 input_info = json.loads(tx_dict['input_info'])
1901 tx.add_input_info(input_info)
1906 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1910 def read_tx_from_file(self):
1911 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1915 with open(fileName, "r") as f:
1916 file_content = f.read()
1917 except (ValueError, IOError, os.error), reason:
1918 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1920 return self.tx_from_text(file_content)
1924 def sign_raw_transaction(self, tx, input_info, password):
1925 self.wallet.signrawtransaction(tx, input_info, [], password)
1927 def do_process_from_text(self):
1928 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1931 tx = self.tx_from_text(text)
1933 self.show_transaction(tx)
1935 def do_process_from_file(self):
1936 tx = self.read_tx_from_file()
1938 self.show_transaction(tx)
1940 def do_process_from_txid(self):
1941 from electrum import transaction
1942 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1944 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1946 tx = transaction.Transaction(r)
1948 self.show_transaction(tx)
1950 self.show_message("unknown transaction")
1952 def do_process_from_csvReader(self, csvReader):
1957 for position, row in enumerate(csvReader):
1959 if not is_valid(address):
1960 errors.append((position, address))
1962 amount = Decimal(row[1])
1963 amount = int(100000000*amount)
1964 outputs.append((address, amount))
1965 except (ValueError, IOError, os.error), reason:
1966 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1970 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1971 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1975 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1976 except Exception as e:
1977 self.show_message(str(e))
1980 self.show_transaction(tx)
1982 def do_process_from_csv_file(self):
1983 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1987 with open(fileName, "r") as f:
1988 csvReader = csv.reader(f)
1989 self.do_process_from_csvReader(csvReader)
1990 except (ValueError, IOError, os.error), reason:
1991 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1994 def do_process_from_csv_text(self):
1995 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1996 + _("Format: address, amount. One output per line"), _("Load CSV"))
1999 f = StringIO.StringIO(text)
2000 csvReader = csv.reader(f)
2001 self.do_process_from_csvReader(csvReader)
2006 def do_export_privkeys(self, password):
2007 if not self.wallet.seed:
2008 self.show_message(_("This wallet has no seed"))
2011 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.")))
2014 select_export = _('Select file to export your private keys to')
2015 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
2017 with open(fileName, "w+") as csvfile:
2018 transaction = csv.writer(csvfile)
2019 transaction.writerow(["address", "private_key"])
2021 addresses = self.wallet.addresses(True)
2023 for addr in addresses:
2024 pk = "".join(self.wallet.get_private_key(addr, password))
2025 transaction.writerow(["%34s"%addr,pk])
2027 self.show_message(_("Private keys exported."))
2029 except (IOError, os.error), reason:
2030 export_error_label = _("Electrum was unable to produce a private key-export.")
2031 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2033 except Exception as e:
2034 self.show_message(str(e))
2038 def do_import_labels(self):
2039 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2040 if not labelsFile: return
2042 f = open(labelsFile, 'r')
2045 for key, value in json.loads(data).items():
2046 self.wallet.set_label(key, value)
2047 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2048 except (IOError, os.error), reason:
2049 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2052 def do_export_labels(self):
2053 labels = self.wallet.labels
2055 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2057 with open(fileName, 'w+') as f:
2058 json.dump(labels, f)
2059 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2060 except (IOError, os.error), reason:
2061 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2064 def do_export_history(self):
2065 from lite_window import csv_transaction
2066 csv_transaction(self.wallet)
2070 def do_import_privkey(self, password):
2071 if not self.wallet.imported_keys:
2072 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2073 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2074 + _('Are you sure you understand what you are doing?'), 3, 4)
2077 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2080 text = str(text).split()
2085 addr = self.wallet.import_key(key, password)
2086 except Exception as e:
2092 addrlist.append(addr)
2094 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2096 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2097 self.update_receive_tab()
2098 self.update_history_tab()
2101 def settings_dialog(self):
2103 d.setWindowTitle(_('Electrum Settings'))
2105 vbox = QVBoxLayout()
2106 grid = QGridLayout()
2107 grid.setColumnStretch(0,1)
2109 nz_label = QLabel(_('Display zeros') + ':')
2110 grid.addWidget(nz_label, 0, 0)
2111 nz_e = AmountEdit(None,True)
2112 nz_e.setText("%d"% self.num_zeros)
2113 grid.addWidget(nz_e, 0, 1)
2114 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2115 grid.addWidget(HelpButton(msg), 0, 2)
2116 if not self.config.is_modifiable('num_zeros'):
2117 for w in [nz_e, nz_label]: w.setEnabled(False)
2119 lang_label=QLabel(_('Language') + ':')
2120 grid.addWidget(lang_label, 1, 0)
2121 lang_combo = QComboBox()
2122 from electrum.i18n import languages
2123 lang_combo.addItems(languages.values())
2125 index = languages.keys().index(self.config.get("language",''))
2128 lang_combo.setCurrentIndex(index)
2129 grid.addWidget(lang_combo, 1, 1)
2130 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2131 if not self.config.is_modifiable('language'):
2132 for w in [lang_combo, lang_label]: w.setEnabled(False)
2135 fee_label = QLabel(_('Transaction fee') + ':')
2136 grid.addWidget(fee_label, 2, 0)
2137 fee_e = AmountEdit(self.base_unit)
2138 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2139 grid.addWidget(fee_e, 2, 1)
2140 msg = _('Fee per kilobyte of transaction.') + ' ' \
2141 + _('Recommended value') + ': ' + self.format_amount(20000)
2142 grid.addWidget(HelpButton(msg), 2, 2)
2143 if not self.config.is_modifiable('fee_per_kb'):
2144 for w in [fee_e, fee_label]: w.setEnabled(False)
2146 units = ['BTC', 'mBTC']
2147 unit_label = QLabel(_('Base unit') + ':')
2148 grid.addWidget(unit_label, 3, 0)
2149 unit_combo = QComboBox()
2150 unit_combo.addItems(units)
2151 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2152 grid.addWidget(unit_combo, 3, 1)
2153 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2154 + '\n1BTC=1000mBTC.\n' \
2155 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2157 usechange_cb = QCheckBox(_('Use change addresses'))
2158 usechange_cb.setChecked(self.wallet.use_change)
2159 grid.addWidget(usechange_cb, 4, 0)
2160 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2161 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2163 grid.setRowStretch(5,1)
2165 vbox.addLayout(grid)
2166 vbox.addLayout(ok_cancel_buttons(d))
2170 if not d.exec_(): return
2172 fee = unicode(fee_e.text())
2174 fee = self.read_amount(fee)
2176 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2179 self.wallet.set_fee(fee)
2181 nz = unicode(nz_e.text())
2186 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2189 if self.num_zeros != nz:
2191 self.config.set_key('num_zeros', nz, True)
2192 self.update_history_tab()
2193 self.update_receive_tab()
2195 usechange_result = usechange_cb.isChecked()
2196 if self.wallet.use_change != usechange_result:
2197 self.wallet.use_change = usechange_result
2198 self.wallet.storage.put('use_change', self.wallet.use_change)
2200 unit_result = units[unit_combo.currentIndex()]
2201 if self.base_unit() != unit_result:
2202 self.decimal_point = 8 if unit_result == 'BTC' else 5
2203 self.config.set_key('decimal_point', self.decimal_point, True)
2204 self.update_history_tab()
2205 self.update_status()
2207 need_restart = False
2209 lang_request = languages.keys()[lang_combo.currentIndex()]
2210 if lang_request != self.config.get('language'):
2211 self.config.set_key("language", lang_request, True)
2214 run_hook('close_settings_dialog')
2217 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2220 def run_network_dialog(self):
2221 if not self.network:
2223 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2225 def closeEvent(self, event):
2228 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2229 self.save_column_widths()
2230 self.config.set_key("console-history", self.console.history[-50:], True)
2231 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2235 def plugins_dialog(self):
2236 from electrum.plugins import plugins
2239 d.setWindowTitle(_('Electrum Plugins'))
2242 vbox = QVBoxLayout(d)
2245 scroll = QScrollArea()
2246 scroll.setEnabled(True)
2247 scroll.setWidgetResizable(True)
2248 scroll.setMinimumSize(400,250)
2249 vbox.addWidget(scroll)
2253 w.setMinimumHeight(len(plugins)*35)
2255 grid = QGridLayout()
2256 grid.setColumnStretch(0,1)
2259 def do_toggle(cb, p, w):
2262 if w: w.setEnabled(r)
2264 def mk_toggle(cb, p, w):
2265 return lambda: do_toggle(cb,p,w)
2267 for i, p in enumerate(plugins):
2269 cb = QCheckBox(p.fullname())
2270 cb.setDisabled(not p.is_available())
2271 cb.setChecked(p.is_enabled())
2272 grid.addWidget(cb, i, 0)
2273 if p.requires_settings():
2274 w = p.settings_widget(self)
2275 w.setEnabled( p.is_enabled() )
2276 grid.addWidget(w, i, 1)
2279 cb.clicked.connect(mk_toggle(cb,p,w))
2280 grid.addWidget(HelpButton(p.description()), i, 2)
2282 print_msg(_("Error: cannot display plugin"), p)
2283 traceback.print_exc(file=sys.stdout)
2284 grid.setRowStretch(i+1,1)
2286 vbox.addLayout(close_button(d))
2291 def show_account_details(self, k):
2292 account = self.wallet.accounts[k]
2295 d.setWindowTitle(_('Account Details'))
2298 vbox = QVBoxLayout(d)
2299 name = self.wallet.get_account_name(k)
2300 label = QLabel('Name: ' + name)
2301 vbox.addWidget(label)
2303 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2305 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2307 vbox.addWidget(QLabel(_('Master Public Key:')))
2310 text.setReadOnly(True)
2311 text.setMaximumHeight(170)
2312 vbox.addWidget(text)
2314 mpk_text = '\n'.join( account.get_master_pubkeys() )
2315 text.setText(mpk_text)
2317 vbox.addLayout(close_button(d))