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', 5)
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)
183 self.connect(self, QtCore.SIGNAL('send_tx3'), self.send_tx3)
185 self.history_list.setFocus(True)
189 self.network.register_callback('updated', lambda: self.need_update.set())
190 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
191 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
192 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
193 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
195 # set initial message
196 self.console.showMessage(self.network.banner)
203 self.config.set_key('lite_mode', False, True)
209 self.config.set_key('lite_mode', True, True)
217 if not self.check_qt_version():
218 if self.config.get('lite_mode') is True:
219 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
220 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
221 self.config.set_key('lite_mode', False, True)
228 actuator = lite_window.MiniActuator(self)
230 actuator.load_theme()
232 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
234 driver = lite_window.MiniDriver(self, self.mini)
236 if self.config.get('lite_mode') is True:
242 def check_qt_version(self):
243 qtVersion = qVersion()
244 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
247 def update_account_selector(self):
249 accounts = self.wallet.get_account_names()
250 self.account_selector.clear()
251 if len(accounts) > 1:
252 self.account_selector.addItems([_("All accounts")] + accounts.values())
253 self.account_selector.setCurrentIndex(0)
254 self.account_selector.show()
256 self.account_selector.hide()
259 def load_wallet(self, wallet):
262 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
263 self.current_account = self.wallet.storage.get("current_account", None)
265 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
266 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
267 self.setWindowTitle( title )
269 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
270 self.notify_transactions()
271 self.update_account_selector()
272 self.new_account.setEnabled(self.wallet.can_create_accounts())
273 self.update_lock_icon()
274 self.update_buttons_on_seed()
275 self.update_console()
277 run_hook('load_wallet', wallet)
280 def open_wallet(self):
281 wallet_folder = self.wallet.storage.path
282 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
286 storage = WalletStorage({'wallet_path': filename})
287 if not storage.file_exists:
288 self.show_message("file not found "+ filename)
291 self.wallet.stop_threads()
294 wallet = Wallet(storage)
295 wallet.start_threads(self.network)
297 self.load_wallet(wallet)
301 def backup_wallet(self):
303 path = self.wallet.storage.path
304 wallet_folder = os.path.dirname(path)
305 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
309 new_path = os.path.join(wallet_folder, filename)
312 shutil.copy2(path, new_path)
313 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
314 except (IOError, os.error), reason:
315 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
318 def new_wallet(self):
321 wallet_folder = os.path.dirname(self.wallet.storage.path)
322 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
325 filename = os.path.join(wallet_folder, filename)
327 storage = WalletStorage({'wallet_path': filename})
328 if storage.file_exists:
329 QMessageBox.critical(None, "Error", _("File exists"))
332 wizard = installwizard.InstallWizard(self.config, self.network, storage)
333 wallet = wizard.run()
335 self.load_wallet(wallet)
339 def init_menubar(self):
342 file_menu = menubar.addMenu(_("&File"))
343 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
344 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
345 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
346 file_menu.addAction(_("&Quit"), self.close)
348 wallet_menu = menubar.addMenu(_("&Wallet"))
349 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
350 self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
352 wallet_menu.addSeparator()
354 wallet_menu.addAction(_("&Password"), self.change_password_dialog)
355 wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
356 wallet_menu.addAction(_("&Master Public Key"), self.show_master_public_key)
358 wallet_menu.addSeparator()
359 labels_menu = wallet_menu.addMenu(_("&Labels"))
360 labels_menu.addAction(_("&Import"), self.do_import_labels)
361 labels_menu.addAction(_("&Export"), self.do_export_labels)
363 keys_menu = wallet_menu.addMenu(_("&Private keys"))
364 keys_menu.addAction(_("&Import"), self.do_import_privkey)
365 keys_menu.addAction(_("&Export"), self.do_export_privkeys)
367 wallet_menu.addAction(_("&Export History"), self.do_export_history)
369 tools_menu = menubar.addMenu(_("&Tools"))
371 # Settings / Preferences are all reserved keywords in OSX using this as work around
372 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
373 tools_menu.addAction(_("&Network"), self.run_network_dialog)
374 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
375 tools_menu.addSeparator()
376 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
377 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
378 tools_menu.addSeparator()
380 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
381 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
382 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
384 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
385 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
386 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
387 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
389 help_menu = menubar.addMenu(_("&Help"))
390 help_menu.addAction(_("&About"), self.show_about)
391 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
392 help_menu.addSeparator()
393 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
394 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
396 self.setMenuBar(menubar)
398 def show_about(self):
399 QMessageBox.about(self, "Electrum",
400 _("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."))
402 def show_report_bug(self):
403 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
404 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
407 def notify_transactions(self):
408 if not self.network or not self.network.is_connected():
411 print_error("Notifying GUI")
412 if len(self.network.pending_transactions_for_notifications) > 0:
413 # Combine the transactions if there are more then three
414 tx_amount = len(self.network.pending_transactions_for_notifications)
417 for tx in self.network.pending_transactions_for_notifications:
418 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
422 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
423 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
425 self.network.pending_transactions_for_notifications = []
427 for tx in self.network.pending_transactions_for_notifications:
429 self.network.pending_transactions_for_notifications.remove(tx)
430 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
432 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
434 def notify(self, message):
435 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
439 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
440 def getOpenFileName(self, title, filter = ""):
441 directory = self.config.get('io_dir', os.path.expanduser('~'))
442 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
443 if fileName and directory != os.path.dirname(fileName):
444 self.config.set_key('io_dir', os.path.dirname(fileName), True)
447 def getSaveFileName(self, title, filename, filter = ""):
448 directory = self.config.get('io_dir', os.path.expanduser('~'))
449 path = os.path.join( directory, filename )
450 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
451 if fileName and directory != os.path.dirname(fileName):
452 self.config.set_key('io_dir', os.path.dirname(fileName), True)
456 QMainWindow.close(self)
457 run_hook('close_main_window')
459 def connect_slots(self, sender):
460 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
461 self.previous_payto_e=''
463 def timer_actions(self):
464 if self.need_update.is_set():
466 self.need_update.clear()
467 run_hook('timer_actions')
469 def format_amount(self, x, is_diff=False, whitespaces=False):
470 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
472 def read_amount(self, x):
473 if x in['.', '']: return None
474 p = pow(10, self.decimal_point)
475 return int( p * Decimal(x) )
478 assert self.decimal_point in [5,8]
479 return "BTC" if self.decimal_point == 8 else "mBTC"
482 def update_status(self):
483 if self.network is None or not self.network.is_running():
485 icon = QIcon(":icons/status_disconnected.png")
487 elif self.network.is_connected():
488 if not self.wallet.up_to_date:
489 text = _("Synchronizing...")
490 icon = QIcon(":icons/status_waiting.png")
491 elif self.network.server_lag > 1:
492 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
493 icon = QIcon(":icons/status_lagging.png")
495 c, u = self.wallet.get_account_balance(self.current_account)
496 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
497 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
499 # append fiat balance and price from exchange rate plugin
501 run_hook('get_fiat_status_text', c+u, r)
506 self.tray.setToolTip(text)
507 icon = QIcon(":icons/status_connected.png")
509 text = _("Not connected")
510 icon = QIcon(":icons/status_disconnected.png")
512 self.balance_label.setText(text)
513 self.status_button.setIcon( icon )
516 def update_wallet(self):
518 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
519 self.update_history_tab()
520 self.update_receive_tab()
521 self.update_contacts_tab()
522 self.update_completions()
525 def create_history_tab(self):
526 self.history_list = l = MyTreeWidget(self)
528 for i,width in enumerate(self.column_widths['history']):
529 l.setColumnWidth(i, width)
530 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
531 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
532 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
534 l.customContextMenuRequested.connect(self.create_history_menu)
538 def create_history_menu(self, position):
539 self.history_list.selectedIndexes()
540 item = self.history_list.currentItem()
541 be = self.config.get('block_explorer', 'Blockchain.info')
542 if be == 'Blockchain.info':
543 block_explorer = 'https://blockchain.info/tx/'
544 elif be == 'Blockr.io':
545 block_explorer = 'https://blockr.io/tx/info/'
546 elif be == 'Insight.is':
547 block_explorer = 'http://live.insight.is/tx/'
549 tx_hash = str(item.data(0, Qt.UserRole).toString())
550 if not tx_hash: return
552 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
553 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
554 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
555 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
556 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
559 def show_transaction(self, tx):
560 import transaction_dialog
561 d = transaction_dialog.TxDialog(tx, self)
564 def tx_label_clicked(self, item, column):
565 if column==2 and item.isSelected():
567 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
568 self.history_list.editItem( item, column )
569 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
572 def tx_label_changed(self, item, column):
576 tx_hash = str(item.data(0, Qt.UserRole).toString())
577 tx = self.wallet.transactions.get(tx_hash)
578 text = unicode( item.text(2) )
579 self.wallet.set_label(tx_hash, text)
581 item.setForeground(2, QBrush(QColor('black')))
583 text = self.wallet.get_default_label(tx_hash)
584 item.setText(2, text)
585 item.setForeground(2, QBrush(QColor('gray')))
589 def edit_label(self, is_recv):
590 l = self.receive_list if is_recv else self.contacts_list
591 item = l.currentItem()
592 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
593 l.editItem( item, 1 )
594 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
598 def address_label_clicked(self, item, column, l, column_addr, column_label):
599 if column == column_label and item.isSelected():
600 is_editable = item.data(0, 32).toBool()
603 addr = unicode( item.text(column_addr) )
604 label = unicode( item.text(column_label) )
605 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
606 l.editItem( item, column )
607 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
610 def address_label_changed(self, item, column, l, column_addr, column_label):
611 if column == column_label:
612 addr = unicode( item.text(column_addr) )
613 text = unicode( item.text(column_label) )
614 is_editable = item.data(0, 32).toBool()
618 changed = self.wallet.set_label(addr, text)
620 self.update_history_tab()
621 self.update_completions()
623 self.current_item_changed(item)
625 run_hook('item_changed', item, column)
628 def current_item_changed(self, a):
629 run_hook('current_item_changed', a)
633 def update_history_tab(self):
635 self.history_list.clear()
636 for item in self.wallet.get_tx_history(self.current_account):
637 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
638 time_str = _("unknown")
641 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
643 time_str = _("error")
646 time_str = 'unverified'
647 icon = QIcon(":icons/unconfirmed.png")
650 icon = QIcon(":icons/unconfirmed.png")
652 icon = QIcon(":icons/clock%d.png"%conf)
654 icon = QIcon(":icons/confirmed.png")
656 if value is not None:
657 v_str = self.format_amount(value, True, whitespaces=True)
661 balance_str = self.format_amount(balance, whitespaces=True)
664 label, is_default_label = self.wallet.get_label(tx_hash)
666 label = _('Pruned transaction outputs')
667 is_default_label = False
669 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
670 item.setFont(2, QFont(MONOSPACE_FONT))
671 item.setFont(3, QFont(MONOSPACE_FONT))
672 item.setFont(4, QFont(MONOSPACE_FONT))
674 item.setForeground(3, QBrush(QColor("#BC1E1E")))
676 item.setData(0, Qt.UserRole, tx_hash)
677 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
679 item.setForeground(2, QBrush(QColor('grey')))
681 item.setIcon(0, icon)
682 self.history_list.insertTopLevelItem(0,item)
685 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
686 run_hook('history_tab_update')
689 def create_send_tab(self):
694 grid.setColumnMinimumWidth(3,300)
695 grid.setColumnStretch(5,1)
698 self.payto_e = QLineEdit()
699 grid.addWidget(QLabel(_('Pay to')), 1, 0)
700 grid.addWidget(self.payto_e, 1, 1, 1, 3)
702 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)
704 completer = QCompleter()
705 completer.setCaseSensitivity(False)
706 self.payto_e.setCompleter(completer)
707 completer.setModel(self.completions)
709 self.message_e = QLineEdit()
710 grid.addWidget(QLabel(_('Description')), 2, 0)
711 grid.addWidget(self.message_e, 2, 1, 1, 3)
712 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)
714 self.from_label = QLabel(_('From'))
715 grid.addWidget(self.from_label, 3, 0)
716 self.from_list = QTreeWidget(self)
717 self.from_list.setColumnCount(2)
718 self.from_list.setColumnWidth(0, 350)
719 self.from_list.setColumnWidth(1, 50)
720 self.from_list.setHeaderHidden (True)
721 self.from_list.setMaximumHeight(80)
722 grid.addWidget(self.from_list, 3, 1, 1, 3)
723 self.set_pay_from([])
725 self.amount_e = AmountEdit(self.base_unit)
726 grid.addWidget(QLabel(_('Amount')), 4, 0)
727 grid.addWidget(self.amount_e, 4, 1, 1, 2)
728 grid.addWidget(HelpButton(
729 _('Amount to be sent.') + '\n\n' \
730 + _('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.') \
731 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
733 self.fee_e = AmountEdit(self.base_unit)
734 grid.addWidget(QLabel(_('Fee')), 5, 0)
735 grid.addWidget(self.fee_e, 5, 1, 1, 2)
736 grid.addWidget(HelpButton(
737 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
738 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
739 + _('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)
741 run_hook('exchange_rate_button', grid)
743 self.send_button = EnterButton(_("Send"), self.do_send)
744 grid.addWidget(self.send_button, 6, 1)
746 b = EnterButton(_("Clear"),self.do_clear)
747 grid.addWidget(b, 6, 2)
749 self.payto_sig = QLabel('')
750 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
752 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
753 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
762 def entry_changed( is_fee ):
763 self.funds_error = False
765 if self.amount_e.is_shortcut:
766 self.amount_e.is_shortcut = False
767 sendable = self.get_sendable_balance()
768 # there is only one output because we are completely spending inputs
769 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
770 fee = self.wallet.estimated_fee(inputs, 1)
772 self.amount_e.setText( self.format_amount(amount) )
773 self.fee_e.setText( self.format_amount( fee ) )
776 amount = self.read_amount(str(self.amount_e.text()))
777 fee = self.read_amount(str(self.fee_e.text()))
779 if not is_fee: fee = None
782 # assume that there will be 2 outputs (one for change)
783 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
785 self.fee_e.setText( self.format_amount( fee ) )
788 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
792 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
793 self.funds_error = True
794 text = _( "Not enough funds" )
795 c, u = self.wallet.get_frozen_balance()
796 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
798 self.statusBar().showMessage(text)
799 self.amount_e.setPalette(palette)
800 self.fee_e.setPalette(palette)
802 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
803 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
805 run_hook('create_send_tab', grid)
809 def set_pay_from(self, l):
811 self.from_list.clear()
812 self.from_label.setHidden(len(self.pay_from) == 0)
813 self.from_list.setHidden(len(self.pay_from) == 0)
814 for addr in self.pay_from:
815 c, u = self.wallet.get_addr_balance(addr)
816 balance = self.format_amount(c + u)
817 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
820 def update_completions(self):
822 for addr,label in self.wallet.labels.items():
823 if addr in self.wallet.addressbook:
824 l.append( label + ' <' + addr + '>')
826 run_hook('update_completions', l)
827 self.completions.setStringList(l)
831 return lambda s, *args: s.do_protect(func, args)
836 label = unicode( self.message_e.text() )
837 r = unicode( self.payto_e.text() )
840 # label or alias, with address in brackets
841 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
842 to_address = m.group(2) if m else r
844 if not is_valid(to_address):
845 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
849 amount = self.read_amount(unicode( self.amount_e.text()))
851 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
854 fee = self.read_amount(unicode( self.fee_e.text()))
856 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
859 confirm_amount = self.config.get('confirm_amount', 100000000)
860 if amount >= confirm_amount:
861 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
864 confirm_fee = self.config.get('confirm_fee', 100000)
865 if fee >= confirm_fee:
866 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()}):
869 self.send_tx(to_address, amount, fee, label)
872 def waiting_dialog(self, message):
874 d.setWindowTitle('Please wait')
876 vbox = QVBoxLayout(d)
883 def send_tx(self, to_address, amount, fee, label, password):
885 # first, create an unsigned tx
886 domain = self.get_payment_sources()
887 outputs = [(to_address, amount)]
889 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
891 except Exception as e:
892 traceback.print_exc(file=sys.stdout)
893 self.show_message(str(e))
896 # call hook to see if plugin needs gui interaction
897 run_hook('send_tx', tx)
903 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
904 self.wallet.sign_transaction(tx, keypairs, password)
905 self.signed_tx_data = (tx, fee, label)
906 self.emit(SIGNAL('send_tx2'))
907 self.tx_wait_dialog = self.waiting_dialog('Signing..')
908 threading.Thread(target=sign_thread).start()
910 # add recipient to addressbook
911 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
912 self.wallet.addressbook.append(to_address)
916 tx, fee, label = self.signed_tx_data
917 self.tx_wait_dialog.accept()
920 self.show_message(tx.error)
923 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
924 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
928 self.wallet.set_label(tx.hash(), label)
930 if not tx.is_complete():
931 self.show_transaction(tx)
935 def broadcast_thread():
936 self.tx_broadcast_result = self.wallet.sendtx(tx)
937 self.emit(SIGNAL('send_tx3'))
938 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
939 threading.Thread(target=broadcast_thread).start()
943 self.tx_broadcast_dialog.accept()
944 status, msg = self.tx_broadcast_result
946 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
948 self.update_contacts_tab()
950 QMessageBox.warning(self, _('Error'), msg, _('OK'))
957 def set_url(self, url):
959 address, amount, label, message, signature, identity, url = util.parse_url(url)
961 QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URL'), _('OK'))
965 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
966 elif amount: amount = str(Decimal(amount))
969 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
972 self.mini.set_payment_fields(address, amount)
974 if label and self.wallet.labels.get(address) != label:
975 if self.question('Give label "%s" to address %s ?'%(label,address)):
976 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
977 self.wallet.addressbook.append(address)
978 self.wallet.set_label(address, label)
980 run_hook('set_url', url, self.show_message, self.question)
982 self.tabs.setCurrentIndex(1)
983 label = self.wallet.labels.get(address)
984 m_addr = label + ' <'+ address +'>' if label else address
985 self.payto_e.setText(m_addr)
987 self.message_e.setText(message)
989 self.amount_e.setText(amount)
992 self.set_frozen(self.payto_e,True)
993 self.set_frozen(self.amount_e,True)
994 self.set_frozen(self.message_e,True)
995 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
997 self.payto_sig.setVisible(False)
1000 self.payto_sig.setVisible(False)
1001 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1003 self.set_frozen(e,False)
1005 self.set_pay_from([])
1006 self.update_status()
1008 def set_frozen(self,entry,frozen):
1010 entry.setReadOnly(True)
1011 entry.setFrame(False)
1012 palette = QPalette()
1013 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1014 entry.setPalette(palette)
1016 entry.setReadOnly(False)
1017 entry.setFrame(True)
1018 palette = QPalette()
1019 palette.setColor(entry.backgroundRole(), QColor('white'))
1020 entry.setPalette(palette)
1023 def set_addrs_frozen(self,addrs,freeze):
1025 if not addr: continue
1026 if addr in self.wallet.frozen_addresses and not freeze:
1027 self.wallet.unfreeze(addr)
1028 elif addr not in self.wallet.frozen_addresses and freeze:
1029 self.wallet.freeze(addr)
1030 self.update_receive_tab()
1034 def create_list_tab(self, headers):
1035 "generic tab creation method"
1036 l = MyTreeWidget(self)
1037 l.setColumnCount( len(headers) )
1038 l.setHeaderLabels( headers )
1041 vbox = QVBoxLayout()
1048 vbox.addWidget(buttons)
1050 hbox = QHBoxLayout()
1053 buttons.setLayout(hbox)
1058 def create_receive_tab(self):
1059 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1060 l.setContextMenuPolicy(Qt.CustomContextMenu)
1061 l.customContextMenuRequested.connect(self.create_receive_menu)
1062 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1063 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1064 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1065 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1066 self.receive_list = l
1067 self.receive_buttons_hbox = hbox
1074 def save_column_widths(self):
1075 self.column_widths["receive"] = []
1076 for i in range(self.receive_list.columnCount() -1):
1077 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1079 self.column_widths["history"] = []
1080 for i in range(self.history_list.columnCount() - 1):
1081 self.column_widths["history"].append(self.history_list.columnWidth(i))
1083 self.column_widths["contacts"] = []
1084 for i in range(self.contacts_list.columnCount() - 1):
1085 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1087 self.config.set_key("column_widths_2", self.column_widths, True)
1090 def create_contacts_tab(self):
1091 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1092 l.setContextMenuPolicy(Qt.CustomContextMenu)
1093 l.customContextMenuRequested.connect(self.create_contact_menu)
1094 for i,width in enumerate(self.column_widths['contacts']):
1095 l.setColumnWidth(i, width)
1097 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1098 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1099 self.contacts_list = l
1100 self.contacts_buttons_hbox = hbox
1105 def delete_imported_key(self, addr):
1106 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1107 self.wallet.delete_imported_key(addr)
1108 self.update_receive_tab()
1109 self.update_history_tab()
1111 def edit_account_label(self, k):
1112 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1114 label = unicode(text)
1115 self.wallet.set_label(k,label)
1116 self.update_receive_tab()
1118 def account_set_expanded(self, item, k, b):
1120 self.accounts_expanded[k] = b
1122 def create_account_menu(self, position, k, item):
1124 if item.isExpanded():
1125 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1127 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1128 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1129 if self.wallet.seed_version > 4:
1130 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1131 if self.wallet.account_is_pending(k):
1132 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1133 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1135 def delete_pending_account(self, k):
1136 self.wallet.delete_pending_account(k)
1137 self.update_receive_tab()
1139 def create_receive_menu(self, position):
1140 # fixme: this function apparently has a side effect.
1141 # if it is not called the menu pops up several times
1142 #self.receive_list.selectedIndexes()
1144 selected = self.receive_list.selectedItems()
1145 multi_select = len(selected) > 1
1146 addrs = [unicode(item.text(0)) for item in selected]
1147 if not multi_select:
1148 item = self.receive_list.itemAt(position)
1152 if not is_valid(addr):
1153 k = str(item.data(0,32).toString())
1155 self.create_account_menu(position, k, item)
1157 item.setExpanded(not item.isExpanded())
1161 if not multi_select:
1162 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1163 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1164 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1165 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1166 if self.wallet.seed:
1167 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1168 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1169 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1170 if addr in self.wallet.imported_keys:
1171 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1173 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1174 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1175 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1176 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1178 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1179 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1181 run_hook('receive_menu', menu, addrs)
1182 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1185 def get_sendable_balance(self):
1186 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1189 def get_payment_sources(self):
1191 return self.pay_from
1193 return self.wallet.get_account_addresses(self.current_account)
1196 def send_from_addresses(self, addrs):
1197 self.set_pay_from( addrs )
1198 self.tabs.setCurrentIndex(1)
1201 def payto(self, addr):
1203 label = self.wallet.labels.get(addr)
1204 m_addr = label + ' <' + addr + '>' if label else addr
1205 self.tabs.setCurrentIndex(1)
1206 self.payto_e.setText(m_addr)
1207 self.amount_e.setFocus()
1210 def delete_contact(self, x):
1211 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1212 self.wallet.delete_contact(x)
1213 self.wallet.set_label(x, None)
1214 self.update_history_tab()
1215 self.update_contacts_tab()
1216 self.update_completions()
1219 def create_contact_menu(self, position):
1220 item = self.contacts_list.itemAt(position)
1223 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1225 addr = unicode(item.text(0))
1226 label = unicode(item.text(1))
1227 is_editable = item.data(0,32).toBool()
1228 payto_addr = item.data(0,33).toString()
1229 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1230 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1231 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1233 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1234 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1236 run_hook('create_contact_menu', menu, item)
1237 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1240 def update_receive_item(self, item):
1241 item.setFont(0, QFont(MONOSPACE_FONT))
1242 address = str(item.data(0,0).toString())
1243 label = self.wallet.labels.get(address,'')
1244 item.setData(1,0,label)
1245 item.setData(0,32, True) # is editable
1247 run_hook('update_receive_item', address, item)
1249 if not self.wallet.is_mine(address): return
1251 c, u = self.wallet.get_addr_balance(address)
1252 balance = self.format_amount(c + u)
1253 item.setData(2,0,balance)
1255 if address in self.wallet.frozen_addresses:
1256 item.setBackgroundColor(0, QColor('lightblue'))
1259 def update_receive_tab(self):
1260 l = self.receive_list
1263 l.setColumnHidden(2, False)
1264 l.setColumnHidden(3, False)
1265 for i,width in enumerate(self.column_widths['receive']):
1266 l.setColumnWidth(i, width)
1268 if self.current_account is None:
1269 account_items = self.wallet.accounts.items()
1270 elif self.current_account != -1:
1271 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1275 for k, account in account_items:
1276 name = self.wallet.get_account_name(k)
1277 c,u = self.wallet.get_account_balance(k)
1278 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1279 l.addTopLevelItem(account_item)
1280 account_item.setExpanded(self.accounts_expanded.get(k, True))
1281 account_item.setData(0, 32, k)
1283 if not self.wallet.is_seeded(k):
1284 icon = QIcon(":icons/key.png")
1285 account_item.setIcon(0, icon)
1287 for is_change in ([0,1]):
1288 name = _("Receiving") if not is_change else _("Change")
1289 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1290 account_item.addChild(seq_item)
1291 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1293 if not is_change: seq_item.setExpanded(True)
1298 for address in account.get_addresses(is_change):
1299 h = self.wallet.history.get(address,[])
1303 if gap > self.wallet.gap_limit:
1308 c, u = self.wallet.get_addr_balance(address)
1309 num_tx = '*' if h == ['*'] else "%d"%len(h)
1310 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1311 self.update_receive_item(item)
1313 item.setBackgroundColor(1, QColor('red'))
1314 if len(h) > 0 and c == -u:
1316 seq_item.insertChild(0,used_item)
1318 used_item.addChild(item)
1320 seq_item.addChild(item)
1323 for k, addr in self.wallet.get_pending_accounts():
1324 name = self.wallet.labels.get(k,'')
1325 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1326 self.update_receive_item(item)
1327 l.addTopLevelItem(account_item)
1328 account_item.setExpanded(True)
1329 account_item.setData(0, 32, k)
1330 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1331 account_item.addChild(item)
1332 self.update_receive_item(item)
1335 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1336 c,u = self.wallet.get_imported_balance()
1337 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1338 l.addTopLevelItem(account_item)
1339 account_item.setExpanded(True)
1340 for address in self.wallet.imported_keys.keys():
1341 item = QTreeWidgetItem( [ address, '', '', ''] )
1342 self.update_receive_item(item)
1343 account_item.addChild(item)
1346 # we use column 1 because column 0 may be hidden
1347 l.setCurrentItem(l.topLevelItem(0),1)
1350 def update_contacts_tab(self):
1351 l = self.contacts_list
1354 for address in self.wallet.addressbook:
1355 label = self.wallet.labels.get(address,'')
1356 n = self.wallet.get_num_tx(address)
1357 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1358 item.setFont(0, QFont(MONOSPACE_FONT))
1359 # 32 = label can be edited (bool)
1360 item.setData(0,32, True)
1362 item.setData(0,33, address)
1363 l.addTopLevelItem(item)
1365 run_hook('update_contacts_tab', l)
1366 l.setCurrentItem(l.topLevelItem(0))
1370 def create_console_tab(self):
1371 from console import Console
1372 self.console = console = Console()
1376 def update_console(self):
1377 console = self.console
1378 console.history = self.config.get("console-history",[])
1379 console.history_index = len(console.history)
1381 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1382 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1384 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1386 def mkfunc(f, method):
1387 return lambda *args: apply( f, (method, args, self.password_dialog ))
1389 if m[0]=='_' or m in ['network','wallet']: continue
1390 methods[m] = mkfunc(c._run, m)
1392 console.updateNamespace(methods)
1395 def change_account(self,s):
1396 if s == _("All accounts"):
1397 self.current_account = None
1399 accounts = self.wallet.get_account_names()
1400 for k, v in accounts.items():
1402 self.current_account = k
1403 self.update_history_tab()
1404 self.update_status()
1405 self.update_receive_tab()
1407 def create_status_bar(self):
1410 sb.setFixedHeight(35)
1411 qtVersion = qVersion()
1413 self.balance_label = QLabel("")
1414 sb.addWidget(self.balance_label)
1416 from version_getter import UpdateLabel
1417 self.updatelabel = UpdateLabel(self.config, sb)
1419 self.account_selector = QComboBox()
1420 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1421 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1422 sb.addPermanentWidget(self.account_selector)
1424 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1425 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1427 self.lock_icon = QIcon()
1428 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1429 sb.addPermanentWidget( self.password_button )
1431 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1432 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1433 sb.addPermanentWidget( self.seed_button )
1434 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1435 sb.addPermanentWidget( self.status_button )
1437 run_hook('create_status_bar', (sb,))
1439 self.setStatusBar(sb)
1442 def update_lock_icon(self):
1443 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1444 self.password_button.setIcon( icon )
1447 def update_buttons_on_seed(self):
1448 if not self.wallet.is_watching_only():
1449 self.seed_button.show()
1450 self.password_button.show()
1451 self.send_button.setText(_("Send"))
1453 self.password_button.hide()
1454 self.seed_button.hide()
1455 self.send_button.setText(_("Create unsigned transaction"))
1458 def change_password_dialog(self):
1459 from password_dialog import PasswordDialog
1460 d = PasswordDialog(self.wallet, self)
1462 self.update_lock_icon()
1465 def new_contact_dialog(self):
1468 d.setWindowTitle(_("New Contact"))
1469 vbox = QVBoxLayout(d)
1470 vbox.addWidget(QLabel(_('New Contact')+':'))
1472 grid = QGridLayout()
1475 grid.addWidget(QLabel(_("Address")), 1, 0)
1476 grid.addWidget(line1, 1, 1)
1477 grid.addWidget(QLabel(_("Name")), 2, 0)
1478 grid.addWidget(line2, 2, 1)
1480 vbox.addLayout(grid)
1481 vbox.addLayout(ok_cancel_buttons(d))
1486 address = str(line1.text())
1487 label = unicode(line2.text())
1489 if not is_valid(address):
1490 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1493 self.wallet.add_contact(address)
1495 self.wallet.set_label(address, label)
1497 self.update_contacts_tab()
1498 self.update_history_tab()
1499 self.update_completions()
1500 self.tabs.setCurrentIndex(3)
1504 def new_account_dialog(self, password):
1506 dialog = QDialog(self)
1508 dialog.setWindowTitle(_("New Account"))
1510 vbox = QVBoxLayout()
1511 vbox.addWidget(QLabel(_('Account name')+':'))
1514 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1515 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1520 vbox.addLayout(ok_cancel_buttons(dialog))
1521 dialog.setLayout(vbox)
1525 name = str(e.text())
1528 self.wallet.create_pending_account('1of1', name, password)
1529 self.update_receive_tab()
1530 self.tabs.setCurrentIndex(2)
1534 def show_master_public_key_old(self):
1535 dialog = QDialog(self)
1537 dialog.setWindowTitle(_("Master Public Key"))
1539 main_text = QTextEdit()
1540 main_text.setText(self.wallet.get_master_public_key())
1541 main_text.setReadOnly(True)
1542 main_text.setMaximumHeight(170)
1543 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1545 ok_button = QPushButton(_("OK"))
1546 ok_button.setDefault(True)
1547 ok_button.clicked.connect(dialog.accept)
1549 main_layout = QGridLayout()
1550 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1552 main_layout.addWidget(main_text, 1, 0)
1553 main_layout.addWidget(qrw, 1, 1 )
1555 vbox = QVBoxLayout()
1556 vbox.addLayout(main_layout)
1557 vbox.addLayout(close_button(dialog))
1558 dialog.setLayout(vbox)
1562 def show_master_public_key(self):
1564 if self.wallet.seed_version == 4:
1565 self.show_master_public_key_old()
1568 dialog = QDialog(self)
1570 dialog.setWindowTitle(_("Master Public Keys"))
1572 mpk_text = QTextEdit()
1573 mpk_text.setReadOnly(True)
1574 mpk_text.setMaximumHeight(170)
1575 mpk_qrw = QRCodeWidget()
1577 main_layout = QGridLayout()
1579 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1580 main_layout.addWidget(mpk_text, 1, 1)
1581 main_layout.addWidget(mpk_qrw, 1, 2)
1584 xpub = self.wallet.master_public_keys[str(key)]
1585 mpk_text.setText(xpub)
1586 mpk_qrw.set_addr(xpub)
1589 key_selector = QComboBox()
1590 keys = sorted(self.wallet.master_public_keys.keys())
1591 key_selector.addItems(keys)
1593 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1594 main_layout.addWidget(key_selector, 0, 1)
1595 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1599 vbox = QVBoxLayout()
1600 vbox.addLayout(main_layout)
1601 vbox.addLayout(close_button(dialog))
1603 dialog.setLayout(vbox)
1608 def show_seed_dialog(self, password):
1609 if self.wallet.is_watching_only():
1610 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1613 if self.wallet.seed:
1615 mnemonic = self.wallet.get_mnemonic(password)
1617 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1619 from seed_dialog import SeedDialog
1620 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1624 for k in self.wallet.master_private_keys.keys():
1625 pk = self.wallet.get_master_private_key(k, password)
1627 from seed_dialog import PrivateKeysDialog
1628 d = PrivateKeysDialog(self,l)
1635 def show_qrcode(self, data, title = _("QR code")):
1639 d.setWindowTitle(title)
1640 d.setMinimumSize(270, 300)
1641 vbox = QVBoxLayout()
1642 qrw = QRCodeWidget(data)
1643 vbox.addWidget(qrw, 1)
1644 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1645 hbox = QHBoxLayout()
1648 filename = os.path.join(self.config.path, "qrcode.bmp")
1651 bmp.save_qrcode(qrw.qr, filename)
1652 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1654 def copy_to_clipboard():
1655 bmp.save_qrcode(qrw.qr, filename)
1656 self.app.clipboard().setImage(QImage(filename))
1657 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1659 b = QPushButton(_("Copy"))
1661 b.clicked.connect(copy_to_clipboard)
1663 b = QPushButton(_("Save"))
1665 b.clicked.connect(print_qr)
1667 b = QPushButton(_("Close"))
1669 b.clicked.connect(d.accept)
1672 vbox.addLayout(hbox)
1677 def do_protect(self, func, args):
1678 if self.wallet.use_encryption:
1679 password = self.password_dialog()
1685 if args != (False,):
1686 args = (self,) + args + (password,)
1688 args = (self,password)
1692 def show_public_keys(self, address):
1693 if not address: return
1695 pubkey_list = self.wallet.get_public_keys(address)
1696 except Exception as e:
1697 traceback.print_exc(file=sys.stdout)
1698 self.show_message(str(e))
1702 d.setMinimumSize(600, 200)
1704 vbox = QVBoxLayout()
1705 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1706 vbox.addWidget( QLabel(_("Public key") + ':'))
1708 keys.setReadOnly(True)
1709 keys.setText('\n'.join(pubkey_list))
1710 vbox.addWidget(keys)
1711 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1712 vbox.addLayout(close_button(d))
1717 def show_private_key(self, address, password):
1718 if not address: return
1720 pk_list = self.wallet.get_private_key(address, password)
1721 except Exception as e:
1722 traceback.print_exc(file=sys.stdout)
1723 self.show_message(str(e))
1727 d.setMinimumSize(600, 200)
1729 vbox = QVBoxLayout()
1730 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1731 vbox.addWidget( QLabel(_("Private key") + ':'))
1733 keys.setReadOnly(True)
1734 keys.setText('\n'.join(pk_list))
1735 vbox.addWidget(keys)
1736 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1737 vbox.addLayout(close_button(d))
1743 def do_sign(self, address, message, signature, password):
1744 message = unicode(message.toPlainText())
1745 message = message.encode('utf-8')
1747 sig = self.wallet.sign_message(str(address.text()), message, password)
1748 signature.setText(sig)
1749 except Exception as e:
1750 self.show_message(str(e))
1752 def do_verify(self, address, message, signature):
1753 message = unicode(message.toPlainText())
1754 message = message.encode('utf-8')
1755 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1756 self.show_message(_("Signature verified"))
1758 self.show_message(_("Error: wrong signature"))
1761 def sign_verify_message(self, address=''):
1764 d.setWindowTitle(_('Sign/verify Message'))
1765 d.setMinimumSize(410, 290)
1767 layout = QGridLayout(d)
1769 message_e = QTextEdit()
1770 layout.addWidget(QLabel(_('Message')), 1, 0)
1771 layout.addWidget(message_e, 1, 1)
1772 layout.setRowStretch(2,3)
1774 address_e = QLineEdit()
1775 address_e.setText(address)
1776 layout.addWidget(QLabel(_('Address')), 2, 0)
1777 layout.addWidget(address_e, 2, 1)
1779 signature_e = QTextEdit()
1780 layout.addWidget(QLabel(_('Signature')), 3, 0)
1781 layout.addWidget(signature_e, 3, 1)
1782 layout.setRowStretch(3,1)
1784 hbox = QHBoxLayout()
1786 b = QPushButton(_("Sign"))
1787 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1790 b = QPushButton(_("Verify"))
1791 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1794 b = QPushButton(_("Close"))
1795 b.clicked.connect(d.accept)
1797 layout.addLayout(hbox, 4, 1)
1802 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1804 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1805 message_e.setText(decrypted)
1806 except Exception as e:
1807 self.show_message(str(e))
1810 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1811 message = unicode(message_e.toPlainText())
1812 message = message.encode('utf-8')
1814 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1815 encrypted_e.setText(encrypted)
1816 except Exception as e:
1817 self.show_message(str(e))
1821 def encrypt_message(self, address = ''):
1824 d.setWindowTitle(_('Encrypt/decrypt Message'))
1825 d.setMinimumSize(610, 490)
1827 layout = QGridLayout(d)
1829 message_e = QTextEdit()
1830 layout.addWidget(QLabel(_('Message')), 1, 0)
1831 layout.addWidget(message_e, 1, 1)
1832 layout.setRowStretch(2,3)
1834 pubkey_e = QLineEdit()
1836 pubkey = self.wallet.getpubkeys(address)[0]
1837 pubkey_e.setText(pubkey)
1838 layout.addWidget(QLabel(_('Public key')), 2, 0)
1839 layout.addWidget(pubkey_e, 2, 1)
1841 encrypted_e = QTextEdit()
1842 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1843 layout.addWidget(encrypted_e, 3, 1)
1844 layout.setRowStretch(3,1)
1846 hbox = QHBoxLayout()
1847 b = QPushButton(_("Encrypt"))
1848 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1851 b = QPushButton(_("Decrypt"))
1852 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1855 b = QPushButton(_("Close"))
1856 b.clicked.connect(d.accept)
1859 layout.addLayout(hbox, 4, 1)
1863 def question(self, msg):
1864 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1866 def show_message(self, msg):
1867 QMessageBox.information(self, _('Message'), msg, _('OK'))
1869 def password_dialog(self ):
1872 d.setWindowTitle(_("Enter Password"))
1877 vbox = QVBoxLayout()
1878 msg = _('Please enter your password')
1879 vbox.addWidget(QLabel(msg))
1881 grid = QGridLayout()
1883 grid.addWidget(QLabel(_('Password')), 1, 0)
1884 grid.addWidget(pw, 1, 1)
1885 vbox.addLayout(grid)
1887 vbox.addLayout(ok_cancel_buttons(d))
1890 run_hook('password_dialog', pw, grid, 1)
1891 if not d.exec_(): return
1892 return unicode(pw.text())
1901 def tx_from_text(self, txt):
1902 "json or raw hexadecimal"
1905 tx = Transaction(txt)
1911 tx_dict = json.loads(str(txt))
1912 assert "hex" in tx_dict.keys()
1913 assert "complete" in tx_dict.keys()
1914 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1915 if not tx_dict["complete"]:
1916 assert "input_info" in tx_dict.keys()
1917 input_info = json.loads(tx_dict['input_info'])
1918 tx.add_input_info(input_info)
1923 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1927 def read_tx_from_file(self):
1928 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1932 with open(fileName, "r") as f:
1933 file_content = f.read()
1934 except (ValueError, IOError, os.error), reason:
1935 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1937 return self.tx_from_text(file_content)
1941 def sign_raw_transaction(self, tx, input_info, password):
1942 self.wallet.signrawtransaction(tx, input_info, [], password)
1944 def do_process_from_text(self):
1945 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1948 tx = self.tx_from_text(text)
1950 self.show_transaction(tx)
1952 def do_process_from_file(self):
1953 tx = self.read_tx_from_file()
1955 self.show_transaction(tx)
1957 def do_process_from_txid(self):
1958 from electrum import transaction
1959 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1961 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1963 tx = transaction.Transaction(r)
1965 self.show_transaction(tx)
1967 self.show_message("unknown transaction")
1969 def do_process_from_csvReader(self, csvReader):
1974 for position, row in enumerate(csvReader):
1976 if not is_valid(address):
1977 errors.append((position, address))
1979 amount = Decimal(row[1])
1980 amount = int(100000000*amount)
1981 outputs.append((address, amount))
1982 except (ValueError, IOError, os.error), reason:
1983 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1987 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1988 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1992 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1993 except Exception as e:
1994 self.show_message(str(e))
1997 self.show_transaction(tx)
1999 def do_process_from_csv_file(self):
2000 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
2004 with open(fileName, "r") as f:
2005 csvReader = csv.reader(f)
2006 self.do_process_from_csvReader(csvReader)
2007 except (ValueError, IOError, os.error), reason:
2008 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2011 def do_process_from_csv_text(self):
2012 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
2013 + _("Format: address, amount. One output per line"), _("Load CSV"))
2016 f = StringIO.StringIO(text)
2017 csvReader = csv.reader(f)
2018 self.do_process_from_csvReader(csvReader)
2023 def do_export_privkeys(self, password):
2024 if not self.wallet.seed:
2025 self.show_message(_("This wallet has no seed"))
2028 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.")))
2031 select_export = _('Select file to export your private keys to')
2032 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
2034 with open(fileName, "w+") as csvfile:
2035 transaction = csv.writer(csvfile)
2036 transaction.writerow(["address", "private_key"])
2038 addresses = self.wallet.addresses(True)
2040 for addr in addresses:
2041 pk = "".join(self.wallet.get_private_key(addr, password))
2042 transaction.writerow(["%34s"%addr,pk])
2044 self.show_message(_("Private keys exported."))
2046 except (IOError, os.error), reason:
2047 export_error_label = _("Electrum was unable to produce a private key-export.")
2048 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2050 except Exception as e:
2051 self.show_message(str(e))
2055 def do_import_labels(self):
2056 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2057 if not labelsFile: return
2059 f = open(labelsFile, 'r')
2062 for key, value in json.loads(data).items():
2063 self.wallet.set_label(key, value)
2064 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2065 except (IOError, os.error), reason:
2066 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2069 def do_export_labels(self):
2070 labels = self.wallet.labels
2072 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2074 with open(fileName, 'w+') as f:
2075 json.dump(labels, f)
2076 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2077 except (IOError, os.error), reason:
2078 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2081 def do_export_history(self):
2082 wallet = self.wallet
2083 select_export = _('Select file to export your wallet transactions to')
2084 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
2089 with open(fileName, "w+") as csvfile:
2090 transaction = csv.writer(csvfile)
2091 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2092 for item in wallet.get_tx_history():
2093 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2095 if timestamp is not None:
2097 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2098 except [RuntimeError, TypeError, NameError] as reason:
2099 time_string = "unknown"
2102 time_string = "unknown"
2104 time_string = "pending"
2106 if value is not None:
2107 value_string = format_satoshis(value, True)
2112 fee_string = format_satoshis(fee, True)
2117 label, is_default_label = wallet.get_label(tx_hash)
2118 label = label.encode('utf-8')
2122 balance_string = format_satoshis(balance, False)
2123 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2124 QMessageBox.information(None,_("CSV Export created"), _("Your CSV export has been successfully created."))
2126 except (IOError, os.error), reason:
2127 export_error_label = _("Electrum was unable to produce a transaction export.")
2128 QMessageBox.critical(None,_("Unable to create csv"), export_error_label + "\n" + str(reason))
2133 def do_import_privkey(self, password):
2134 if not self.wallet.imported_keys:
2135 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2136 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2137 + _('Are you sure you understand what you are doing?'), 3, 4)
2140 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2143 text = str(text).split()
2148 addr = self.wallet.import_key(key, password)
2149 except Exception as e:
2155 addrlist.append(addr)
2157 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2159 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2160 self.update_receive_tab()
2161 self.update_history_tab()
2164 def settings_dialog(self):
2166 d.setWindowTitle(_('Electrum Settings'))
2168 vbox = QVBoxLayout()
2169 grid = QGridLayout()
2170 grid.setColumnStretch(0,1)
2172 nz_label = QLabel(_('Display zeros') + ':')
2173 grid.addWidget(nz_label, 0, 0)
2174 nz_e = AmountEdit(None,True)
2175 nz_e.setText("%d"% self.num_zeros)
2176 grid.addWidget(nz_e, 0, 1)
2177 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2178 grid.addWidget(HelpButton(msg), 0, 2)
2179 if not self.config.is_modifiable('num_zeros'):
2180 for w in [nz_e, nz_label]: w.setEnabled(False)
2182 lang_label=QLabel(_('Language') + ':')
2183 grid.addWidget(lang_label, 1, 0)
2184 lang_combo = QComboBox()
2185 from electrum.i18n import languages
2186 lang_combo.addItems(languages.values())
2188 index = languages.keys().index(self.config.get("language",''))
2191 lang_combo.setCurrentIndex(index)
2192 grid.addWidget(lang_combo, 1, 1)
2193 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2194 if not self.config.is_modifiable('language'):
2195 for w in [lang_combo, lang_label]: w.setEnabled(False)
2198 fee_label = QLabel(_('Transaction fee') + ':')
2199 grid.addWidget(fee_label, 2, 0)
2200 fee_e = AmountEdit(self.base_unit)
2201 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2202 grid.addWidget(fee_e, 2, 1)
2203 msg = _('Fee per kilobyte of transaction.') + ' ' \
2204 + _('Recommended value') + ': ' + self.format_amount(20000)
2205 grid.addWidget(HelpButton(msg), 2, 2)
2206 if not self.config.is_modifiable('fee_per_kb'):
2207 for w in [fee_e, fee_label]: w.setEnabled(False)
2209 units = ['BTC', 'mBTC']
2210 unit_label = QLabel(_('Base unit') + ':')
2211 grid.addWidget(unit_label, 3, 0)
2212 unit_combo = QComboBox()
2213 unit_combo.addItems(units)
2214 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2215 grid.addWidget(unit_combo, 3, 1)
2216 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2217 + '\n1BTC=1000mBTC.\n' \
2218 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2220 usechange_cb = QCheckBox(_('Use change addresses'))
2221 usechange_cb.setChecked(self.wallet.use_change)
2222 grid.addWidget(usechange_cb, 4, 0)
2223 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2224 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2226 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2227 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2228 grid.addWidget(block_ex_label, 5, 0)
2229 block_ex_combo = QComboBox()
2230 block_ex_combo.addItems(block_explorers)
2231 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2232 grid.addWidget(block_ex_combo, 5, 1)
2233 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2235 grid.setRowStretch(6,1)
2237 vbox.addLayout(grid)
2238 vbox.addLayout(ok_cancel_buttons(d))
2242 if not d.exec_(): return
2244 fee = unicode(fee_e.text())
2246 fee = self.read_amount(fee)
2248 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2251 self.wallet.set_fee(fee)
2253 nz = unicode(nz_e.text())
2258 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2261 if self.num_zeros != nz:
2263 self.config.set_key('num_zeros', nz, True)
2264 self.update_history_tab()
2265 self.update_receive_tab()
2267 usechange_result = usechange_cb.isChecked()
2268 if self.wallet.use_change != usechange_result:
2269 self.wallet.use_change = usechange_result
2270 self.wallet.storage.put('use_change', self.wallet.use_change)
2272 unit_result = units[unit_combo.currentIndex()]
2273 if self.base_unit() != unit_result:
2274 self.decimal_point = 8 if unit_result == 'BTC' else 5
2275 self.config.set_key('decimal_point', self.decimal_point, True)
2276 self.update_history_tab()
2277 self.update_status()
2279 need_restart = False
2281 lang_request = languages.keys()[lang_combo.currentIndex()]
2282 if lang_request != self.config.get('language'):
2283 self.config.set_key("language", lang_request, True)
2286 be_result = block_explorers[block_ex_combo.currentIndex()]
2287 self.config.set_key('block_explorer', be_result, True)
2289 run_hook('close_settings_dialog')
2292 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2295 def run_network_dialog(self):
2296 if not self.network:
2298 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2300 def closeEvent(self, event):
2303 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2304 self.save_column_widths()
2305 self.config.set_key("console-history", self.console.history[-50:], True)
2306 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2310 def plugins_dialog(self):
2311 from electrum.plugins import plugins
2314 d.setWindowTitle(_('Electrum Plugins'))
2317 vbox = QVBoxLayout(d)
2320 scroll = QScrollArea()
2321 scroll.setEnabled(True)
2322 scroll.setWidgetResizable(True)
2323 scroll.setMinimumSize(400,250)
2324 vbox.addWidget(scroll)
2328 w.setMinimumHeight(len(plugins)*35)
2330 grid = QGridLayout()
2331 grid.setColumnStretch(0,1)
2334 def do_toggle(cb, p, w):
2337 if w: w.setEnabled(r)
2339 def mk_toggle(cb, p, w):
2340 return lambda: do_toggle(cb,p,w)
2342 for i, p in enumerate(plugins):
2344 cb = QCheckBox(p.fullname())
2345 cb.setDisabled(not p.is_available())
2346 cb.setChecked(p.is_enabled())
2347 grid.addWidget(cb, i, 0)
2348 if p.requires_settings():
2349 w = p.settings_widget(self)
2350 w.setEnabled( p.is_enabled() )
2351 grid.addWidget(w, i, 1)
2354 cb.clicked.connect(mk_toggle(cb,p,w))
2355 grid.addWidget(HelpButton(p.description()), i, 2)
2357 print_msg(_("Error: cannot display plugin"), p)
2358 traceback.print_exc(file=sys.stdout)
2359 grid.setRowStretch(i+1,1)
2361 vbox.addLayout(close_button(d))
2366 def show_account_details(self, k):
2367 account = self.wallet.accounts[k]
2370 d.setWindowTitle(_('Account Details'))
2373 vbox = QVBoxLayout(d)
2374 name = self.wallet.get_account_name(k)
2375 label = QLabel('Name: ' + name)
2376 vbox.addWidget(label)
2378 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2380 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2382 vbox.addWidget(QLabel(_('Master Public Key:')))
2385 text.setReadOnly(True)
2386 text.setMaximumHeight(170)
2387 vbox.addWidget(text)
2389 mpk_text = '\n'.join( account.get_master_pubkeys() )
2390 text.setText(mpk_text)
2392 vbox.addLayout(close_button(d))