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, BTCAmountEdit, MyLineEdit
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):
105 def __init__(self, config, network, gui_object):
106 QMainWindow.__init__(self)
109 self.network = network
110 self.gui_object = gui_object
111 self.tray = gui_object.tray
112 self.go_lite = gui_object.go_lite
115 self.create_status_bar()
116 self.need_update = threading.Event()
118 self.decimal_point = config.get('decimal_point', 5)
119 self.num_zeros = int(config.get('num_zeros',0))
122 set_language(config.get('language'))
124 self.funds_error = False
125 self.completions = QStringListModel()
127 self.tabs = tabs = QTabWidget(self)
128 self.column_widths = self.config.get("column_widths_2", default_column_widths )
129 tabs.addTab(self.create_history_tab(), _('History') )
130 tabs.addTab(self.create_send_tab(), _('Send') )
131 tabs.addTab(self.create_receive_tab(), _('Receive') )
132 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
133 tabs.addTab(self.create_invoices_tab(), _('Invoices') )
134 tabs.addTab(self.create_console_tab(), _('Console') )
135 tabs.setMinimumSize(600, 400)
136 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
137 self.setCentralWidget(tabs)
139 g = self.config.get("winpos-qt",[100, 100, 840, 400])
140 self.setGeometry(g[0], g[1], g[2], g[3])
141 if self.config.get("is_maximized"):
144 self.setWindowIcon(QIcon(":icons/electrum.png"))
147 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
148 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
149 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
150 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
151 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
153 for i in range(tabs.count()):
154 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
156 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
157 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
158 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
159 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
160 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
162 self.history_list.setFocus(True)
166 self.network.register_callback('updated', lambda: self.need_update.set())
167 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
168 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
169 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
170 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
172 # set initial message
173 self.console.showMessage(self.network.banner)
178 def update_account_selector(self):
180 accounts = self.wallet.get_account_names()
181 self.account_selector.clear()
182 if len(accounts) > 1:
183 self.account_selector.addItems([_("All accounts")] + accounts.values())
184 self.account_selector.setCurrentIndex(0)
185 self.account_selector.show()
187 self.account_selector.hide()
190 def load_wallet(self, wallet):
194 self.update_wallet_format()
196 self.invoices = self.wallet.storage.get('invoices', {})
197 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
198 self.current_account = self.wallet.storage.get("current_account", None)
199 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
200 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
201 self.setWindowTitle( title )
203 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
204 self.notify_transactions()
205 self.update_account_selector()
207 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
208 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
209 self.password_menu.setEnabled(not self.wallet.is_watching_only())
210 self.seed_menu.setEnabled(self.wallet.has_seed())
211 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
212 self.import_menu.setEnabled(self.wallet.can_import())
214 self.update_lock_icon()
215 self.update_buttons_on_seed()
216 self.update_console()
218 run_hook('load_wallet', wallet)
221 def update_wallet_format(self):
222 # convert old-format imported keys
223 if self.wallet.imported_keys:
224 password = self.password_dialog(_("Please enter your password in order to update imported keys"))
226 self.wallet.convert_imported_keys(password)
228 self.show_message("error")
231 def open_wallet(self):
232 wallet_folder = self.wallet.storage.path
233 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
237 storage = WalletStorage({'wallet_path': filename})
238 if not storage.file_exists:
239 self.show_message("file not found "+ filename)
242 self.wallet.stop_threads()
245 wallet = Wallet(storage)
246 wallet.start_threads(self.network)
248 self.load_wallet(wallet)
252 def backup_wallet(self):
254 path = self.wallet.storage.path
255 wallet_folder = os.path.dirname(path)
256 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
260 new_path = os.path.join(wallet_folder, filename)
263 shutil.copy2(path, new_path)
264 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
265 except (IOError, os.error), reason:
266 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
269 def new_wallet(self):
272 wallet_folder = os.path.dirname(self.wallet.storage.path)
273 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
276 filename = os.path.join(wallet_folder, filename)
278 storage = WalletStorage({'wallet_path': filename})
279 if storage.file_exists:
280 QMessageBox.critical(None, "Error", _("File exists"))
283 wizard = installwizard.InstallWizard(self.config, self.network, storage)
284 wallet = wizard.run('new')
286 self.load_wallet(wallet)
290 def init_menubar(self):
293 file_menu = menubar.addMenu(_("&File"))
294 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
295 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
296 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
297 file_menu.addAction(_("&Quit"), self.close)
299 wallet_menu = menubar.addMenu(_("&Wallet"))
300 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
301 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
303 wallet_menu.addSeparator()
305 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
306 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
307 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
309 wallet_menu.addSeparator()
310 labels_menu = wallet_menu.addMenu(_("&Labels"))
311 labels_menu.addAction(_("&Import"), self.do_import_labels)
312 labels_menu.addAction(_("&Export"), self.do_export_labels)
314 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
315 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
316 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
317 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
318 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
320 tools_menu = menubar.addMenu(_("&Tools"))
322 # Settings / Preferences are all reserved keywords in OSX using this as work around
323 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
324 tools_menu.addAction(_("&Network"), self.run_network_dialog)
325 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
326 tools_menu.addSeparator()
327 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
328 tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
329 tools_menu.addSeparator()
331 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
332 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
333 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
335 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
336 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
337 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
338 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
340 help_menu = menubar.addMenu(_("&Help"))
341 help_menu.addAction(_("&About"), self.show_about)
342 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
343 help_menu.addSeparator()
344 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
345 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
347 self.setMenuBar(menubar)
349 def show_about(self):
350 QMessageBox.about(self, "Electrum",
351 _("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."))
353 def show_report_bug(self):
354 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
355 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
358 def notify_transactions(self):
359 if not self.network or not self.network.is_connected():
362 print_error("Notifying GUI")
363 if len(self.network.pending_transactions_for_notifications) > 0:
364 # Combine the transactions if there are more then three
365 tx_amount = len(self.network.pending_transactions_for_notifications)
368 for tx in self.network.pending_transactions_for_notifications:
369 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
373 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
374 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
376 self.network.pending_transactions_for_notifications = []
378 for tx in self.network.pending_transactions_for_notifications:
380 self.network.pending_transactions_for_notifications.remove(tx)
381 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
383 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
385 def notify(self, message):
386 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
390 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
391 def getOpenFileName(self, title, filter = ""):
392 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
393 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
394 if fileName and directory != os.path.dirname(fileName):
395 self.config.set_key('io_dir', os.path.dirname(fileName), True)
398 def getSaveFileName(self, title, filename, filter = ""):
399 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
400 path = os.path.join( directory, filename )
401 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
402 if fileName and directory != os.path.dirname(fileName):
403 self.config.set_key('io_dir', os.path.dirname(fileName), True)
407 QMainWindow.close(self)
408 run_hook('close_main_window')
410 def connect_slots(self, sender):
411 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
412 self.previous_payto_e=''
414 def timer_actions(self):
415 if self.need_update.is_set():
417 self.need_update.clear()
418 run_hook('timer_actions')
420 def format_amount(self, x, is_diff=False, whitespaces=False):
421 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
424 def get_decimal_point(self):
425 return self.decimal_point
429 assert self.decimal_point in [5,8]
430 return "BTC" if self.decimal_point == 8 else "mBTC"
433 def update_status(self):
434 if self.network is None or not self.network.is_running():
436 icon = QIcon(":icons/status_disconnected.png")
438 elif self.network.is_connected():
439 if not self.wallet.up_to_date:
440 text = _("Synchronizing...")
441 icon = QIcon(":icons/status_waiting.png")
442 elif self.network.server_lag > 1:
443 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
444 icon = QIcon(":icons/status_lagging.png")
446 c, u = self.wallet.get_account_balance(self.current_account)
447 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
448 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
450 # append fiat balance and price from exchange rate plugin
452 run_hook('get_fiat_status_text', c+u, r)
457 self.tray.setToolTip(text)
458 icon = QIcon(":icons/status_connected.png")
460 text = _("Not connected")
461 icon = QIcon(":icons/status_disconnected.png")
463 self.balance_label.setText(text)
464 self.status_button.setIcon( icon )
467 def update_wallet(self):
469 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
470 self.update_history_tab()
471 self.update_receive_tab()
472 self.update_contacts_tab()
473 self.update_completions()
474 self.update_invoices_tab()
477 def create_history_tab(self):
478 self.history_list = l = MyTreeWidget(self)
480 for i,width in enumerate(self.column_widths['history']):
481 l.setColumnWidth(i, width)
482 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
483 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
484 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
486 l.customContextMenuRequested.connect(self.create_history_menu)
490 def create_history_menu(self, position):
491 self.history_list.selectedIndexes()
492 item = self.history_list.currentItem()
493 be = self.config.get('block_explorer', 'Blockchain.info')
494 if be == 'Blockchain.info':
495 block_explorer = 'https://blockchain.info/tx/'
496 elif be == 'Blockr.io':
497 block_explorer = 'https://blockr.io/tx/info/'
498 elif be == 'Insight.is':
499 block_explorer = 'http://live.insight.is/tx/'
501 tx_hash = str(item.data(0, Qt.UserRole).toString())
502 if not tx_hash: return
504 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
505 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
506 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
507 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
508 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
511 def show_transaction(self, tx):
512 import transaction_dialog
513 d = transaction_dialog.TxDialog(tx, self)
516 def tx_label_clicked(self, item, column):
517 if column==2 and item.isSelected():
519 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
520 self.history_list.editItem( item, column )
521 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
524 def tx_label_changed(self, item, column):
528 tx_hash = str(item.data(0, Qt.UserRole).toString())
529 tx = self.wallet.transactions.get(tx_hash)
530 text = unicode( item.text(2) )
531 self.wallet.set_label(tx_hash, text)
533 item.setForeground(2, QBrush(QColor('black')))
535 text = self.wallet.get_default_label(tx_hash)
536 item.setText(2, text)
537 item.setForeground(2, QBrush(QColor('gray')))
541 def edit_label(self, is_recv):
542 l = self.receive_list if is_recv else self.contacts_list
543 item = l.currentItem()
544 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
545 l.editItem( item, 1 )
546 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
550 def address_label_clicked(self, item, column, l, column_addr, column_label):
551 if column == column_label and item.isSelected():
552 is_editable = item.data(0, 32).toBool()
555 addr = unicode( item.text(column_addr) )
556 label = unicode( item.text(column_label) )
557 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
558 l.editItem( item, column )
559 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
562 def address_label_changed(self, item, column, l, column_addr, column_label):
563 if column == column_label:
564 addr = unicode( item.text(column_addr) )
565 text = unicode( item.text(column_label) )
566 is_editable = item.data(0, 32).toBool()
570 changed = self.wallet.set_label(addr, text)
572 self.update_history_tab()
573 self.update_completions()
575 self.current_item_changed(item)
577 run_hook('item_changed', item, column)
580 def current_item_changed(self, a):
581 run_hook('current_item_changed', a)
585 def update_history_tab(self):
587 self.history_list.clear()
588 for item in self.wallet.get_tx_history(self.current_account):
589 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
590 time_str = _("unknown")
593 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
595 time_str = _("error")
598 time_str = 'unverified'
599 icon = QIcon(":icons/unconfirmed.png")
602 icon = QIcon(":icons/unconfirmed.png")
604 icon = QIcon(":icons/clock%d.png"%conf)
606 icon = QIcon(":icons/confirmed.png")
608 if value is not None:
609 v_str = self.format_amount(value, True, whitespaces=True)
613 balance_str = self.format_amount(balance, whitespaces=True)
616 label, is_default_label = self.wallet.get_label(tx_hash)
618 label = _('Pruned transaction outputs')
619 is_default_label = False
621 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
622 item.setFont(2, QFont(MONOSPACE_FONT))
623 item.setFont(3, QFont(MONOSPACE_FONT))
624 item.setFont(4, QFont(MONOSPACE_FONT))
626 item.setForeground(3, QBrush(QColor("#BC1E1E")))
628 item.setData(0, Qt.UserRole, tx_hash)
629 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
631 item.setForeground(2, QBrush(QColor('grey')))
633 item.setIcon(0, icon)
634 self.history_list.insertTopLevelItem(0,item)
637 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
638 run_hook('history_tab_update')
641 def create_send_tab(self):
644 self.send_grid = grid = QGridLayout(w)
646 grid.setColumnMinimumWidth(3,300)
647 grid.setColumnStretch(5,1)
648 grid.setRowStretch(8, 1)
650 from paytoedit import PayToEdit
651 self.amount_e = BTCAmountEdit(self.get_decimal_point)
652 self.payto_e = PayToEdit(self.amount_e)
653 self.payto_help = 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)'))
654 grid.addWidget(QLabel(_('Pay to')), 1, 0)
655 grid.addWidget(self.payto_e, 1, 1, 1, 3)
656 grid.addWidget(self.payto_help, 1, 4)
658 completer = QCompleter()
659 completer.setCaseSensitivity(False)
660 self.payto_e.setCompleter(completer)
661 completer.setModel(self.completions)
663 self.message_e = MyLineEdit()
664 self.message_help = 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.'))
665 grid.addWidget(QLabel(_('Description')), 2, 0)
666 grid.addWidget(self.message_e, 2, 1, 1, 3)
667 grid.addWidget(self.message_help, 2, 4)
669 self.from_label = QLabel(_('From'))
670 grid.addWidget(self.from_label, 3, 0)
671 self.from_list = MyTreeWidget(self)
672 self.from_list.setColumnCount(2)
673 self.from_list.setColumnWidth(0, 350)
674 self.from_list.setColumnWidth(1, 50)
675 self.from_list.setHeaderHidden(True)
676 self.from_list.setMaximumHeight(80)
677 self.from_list.setContextMenuPolicy(Qt.CustomContextMenu)
678 self.from_list.customContextMenuRequested.connect(self.from_list_menu)
679 grid.addWidget(self.from_list, 3, 1, 1, 3)
680 self.set_pay_from([])
682 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
683 + _('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.') \
684 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
685 grid.addWidget(QLabel(_('Amount')), 4, 0)
686 grid.addWidget(self.amount_e, 4, 1, 1, 2)
687 grid.addWidget(self.amount_help, 4, 3)
689 self.fee_e = BTCAmountEdit(self.get_decimal_point)
690 grid.addWidget(QLabel(_('Fee')), 5, 0)
691 grid.addWidget(self.fee_e, 5, 1, 1, 2)
692 grid.addWidget(HelpButton(
693 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
694 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
695 + _('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)
697 self.send_button = EnterButton(_("Send"), self.do_send)
698 grid.addWidget(self.send_button, 6, 1)
700 b = EnterButton(_("Clear"), self.do_clear)
701 grid.addWidget(b, 6, 2)
703 self.payto_sig = QLabel('')
704 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
706 #QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
707 #QShortcut(QKeySequence("Down"), w, w.focusNextChild)
710 def entry_changed( is_fee ):
711 self.funds_error = False
713 if self.amount_e.is_shortcut:
714 self.amount_e.is_shortcut = False
715 sendable = self.get_sendable_balance()
716 # there is only one output because we are completely spending inputs
717 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, coins = self.get_coins())
718 fee = self.wallet.estimated_fee(inputs, 1)
720 self.amount_e.setAmount(amount)
721 self.fee_e.setAmount(fee)
724 amount = self.amount_e.get_amount()
725 fee = self.fee_e.get_amount()
727 if not is_fee: fee = None
730 # assume that there will be 2 outputs (one for change)
731 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, coins = self.get_coins())
733 self.fee_e.setAmount(fee)
736 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
740 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
741 self.funds_error = True
742 text = _( "Not enough funds" )
743 c, u = self.wallet.get_frozen_balance()
744 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
746 self.statusBar().showMessage(text)
747 self.amount_e.setPalette(palette)
748 self.fee_e.setPalette(palette)
750 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
751 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
753 run_hook('create_send_tab', grid)
756 def from_list_delete(self, item):
757 i = self.from_list.indexOfTopLevelItem(item)
759 self.redraw_from_list()
761 def from_list_menu(self, position):
762 item = self.from_list.itemAt(position)
764 menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
765 menu.exec_(self.from_list.viewport().mapToGlobal(position))
767 def set_pay_from(self, domain = None):
768 self.pay_from = [] if domain == [] else self.wallet.get_unspent_coins(domain)
769 self.redraw_from_list()
771 def redraw_from_list(self):
772 self.from_list.clear()
773 self.from_label.setHidden(len(self.pay_from) == 0)
774 self.from_list.setHidden(len(self.pay_from) == 0)
777 h = x.get('prevout_hash')
778 return h[0:8] + '...' + h[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
780 for item in self.pay_from:
781 self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
783 def update_completions(self):
785 for addr,label in self.wallet.labels.items():
786 if addr in self.wallet.addressbook:
787 l.append( label + ' <' + addr + '>')
789 run_hook('update_completions', l)
790 self.completions.setStringList(l)
794 return lambda s, *args: s.do_protect(func, args)
798 label = unicode( self.message_e.text() )
800 if self.gui_object.payment_request:
801 outputs = self.gui_object.payment_request.outputs
803 outputs = self.payto_e.get_outputs()
806 QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
809 for addr, x in outputs:
810 if addr is None or not bitcoin.is_address(addr):
811 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address'), _('OK'))
814 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
817 amount = sum(map(lambda x:x[1], outputs))
819 fee = self.fee_e.get_amount()
821 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
824 confirm_amount = self.config.get('confirm_amount', 100000000)
825 if amount >= confirm_amount:
826 o = '\n'.join(map(lambda x:x[0], outputs))
827 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
830 confirm_fee = self.config.get('confirm_fee', 100000)
831 if fee >= confirm_fee:
832 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()}):
835 self.send_tx(outputs, fee, label)
840 def send_tx(self, outputs, fee, label, password):
841 self.send_button.setDisabled(True)
843 # first, create an unsigned tx
844 coins = self.get_coins()
846 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
848 except Exception as e:
849 traceback.print_exc(file=sys.stdout)
850 self.show_message(str(e))
851 self.send_button.setDisabled(False)
854 # call hook to see if plugin needs gui interaction
855 run_hook('send_tx', tx)
861 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
862 self.wallet.sign_transaction(tx, keypairs, password)
863 return tx, fee, label
865 def sign_done(tx, fee, label):
867 self.show_message(tx.error)
868 self.send_button.setDisabled(False)
870 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
871 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
872 self.send_button.setDisabled(False)
875 self.wallet.set_label(tx.hash(), label)
877 if not tx.is_complete() or self.config.get('show_before_broadcast'):
878 self.show_transaction(tx)
880 self.send_button.setDisabled(False)
883 self.broadcast_transaction(tx)
885 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
886 self.waiting_dialog.start()
890 def broadcast_transaction(self, tx):
892 def broadcast_thread():
893 if self.gui_object.payment_request:
894 refund_address = self.wallet.addresses()[0]
895 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
896 self.gui_object.payment_request = None
898 status, msg = self.wallet.sendtx(tx)
901 def broadcast_done(status, msg):
903 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
906 QMessageBox.warning(self, _('Error'), msg, _('OK'))
907 self.send_button.setDisabled(False)
909 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
910 self.waiting_dialog.start()
914 def prepare_for_payment_request(self):
915 self.tabs.setCurrentIndex(1)
916 self.payto_e.is_pr = True
917 for e in [self.payto_e, self.amount_e, self.message_e]:
919 for h in [self.payto_help, self.amount_help, self.message_help]:
921 self.payto_e.setText(_("please wait..."))
924 def payment_request_ok(self):
925 pr = self.gui_object.payment_request
928 self.invoices[pr_id] = (pr.get_domain(), pr.get_amount())
929 self.wallet.storage.put('invoices', self.invoices)
930 self.update_invoices_tab()
932 self.payto_help.show()
933 self.payto_help.set_alt(lambda: self.show_pr_details(pr))
935 self.payto_e.setGreen()
936 self.payto_e.setText(pr.domain)
937 self.amount_e.setText(self.format_amount(pr.get_amount()))
938 self.message_e.setText(pr.memo)
940 def payment_request_error(self):
942 self.show_message(self.gui_object.payment_request.error)
943 self.gui_object.payment_request = None
945 def set_send(self, address, amount, label, message):
947 if label and self.wallet.labels.get(address) != label:
948 if self.question('Give label "%s" to address %s ?'%(label,address)):
949 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
950 self.wallet.addressbook.append(address)
951 self.wallet.set_label(address, label)
953 self.tabs.setCurrentIndex(1)
954 label = self.wallet.labels.get(address)
955 m_addr = label + ' <'+ address +'>' if label else address
956 self.payto_e.setText(m_addr)
958 self.message_e.setText(message)
960 self.amount_e.setText(amount)
964 self.payto_e.is_pr = False
965 self.payto_sig.setVisible(False)
966 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
970 for h in [self.payto_help, self.amount_help, self.message_help]:
973 self.payto_help.set_alt(None)
974 self.set_pay_from([])
979 def set_addrs_frozen(self,addrs,freeze):
981 if not addr: continue
982 if addr in self.wallet.frozen_addresses and not freeze:
983 self.wallet.unfreeze(addr)
984 elif addr not in self.wallet.frozen_addresses and freeze:
985 self.wallet.freeze(addr)
986 self.update_receive_tab()
990 def create_list_tab(self, headers):
991 "generic tab creation method"
992 l = MyTreeWidget(self)
993 l.setColumnCount( len(headers) )
994 l.setHeaderLabels( headers )
1004 vbox.addWidget(buttons)
1009 def create_receive_tab(self):
1010 l, w = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1011 l.setContextMenuPolicy(Qt.CustomContextMenu)
1012 l.customContextMenuRequested.connect(self.create_receive_menu)
1013 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1014 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1015 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1016 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1017 self.receive_list = l
1023 def save_column_widths(self):
1024 self.column_widths["receive"] = []
1025 for i in range(self.receive_list.columnCount() -1):
1026 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1028 self.column_widths["history"] = []
1029 for i in range(self.history_list.columnCount() - 1):
1030 self.column_widths["history"].append(self.history_list.columnWidth(i))
1032 self.column_widths["contacts"] = []
1033 for i in range(self.contacts_list.columnCount() - 1):
1034 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1036 self.config.set_key("column_widths_2", self.column_widths, True)
1039 def create_contacts_tab(self):
1040 l, w = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1041 l.setContextMenuPolicy(Qt.CustomContextMenu)
1042 l.customContextMenuRequested.connect(self.create_contact_menu)
1043 for i,width in enumerate(self.column_widths['contacts']):
1044 l.setColumnWidth(i, width)
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.contacts_list = l
1052 def create_invoices_tab(self):
1053 l, w = self.create_list_tab([_('Requestor'), _('Amount'), _('Status')])
1054 l.setContextMenuPolicy(Qt.CustomContextMenu)
1055 l.customContextMenuRequested.connect(self.create_invoice_menu)
1056 self.invoices_list = l
1059 def update_invoices_tab(self):
1060 invoices = self.wallet.storage.get('invoices', {})
1061 l = self.invoices_list
1064 for item, value in invoices.items():
1065 domain, amount = value
1066 item = QTreeWidgetItem( [ domain, self.format_amount(amount), ""] )
1067 l.addTopLevelItem(item)
1069 l.setCurrentItem(l.topLevelItem(0))
1073 def delete_imported_key(self, addr):
1074 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1075 self.wallet.delete_imported_key(addr)
1076 self.update_receive_tab()
1077 self.update_history_tab()
1079 def edit_account_label(self, k):
1080 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1082 label = unicode(text)
1083 self.wallet.set_label(k,label)
1084 self.update_receive_tab()
1086 def account_set_expanded(self, item, k, b):
1088 self.accounts_expanded[k] = b
1090 def create_account_menu(self, position, k, item):
1092 if item.isExpanded():
1093 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1095 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1096 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1097 if self.wallet.seed_version > 4:
1098 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1099 if self.wallet.account_is_pending(k):
1100 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1101 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1103 def delete_pending_account(self, k):
1104 self.wallet.delete_pending_account(k)
1105 self.update_receive_tab()
1107 def create_receive_menu(self, position):
1108 # fixme: this function apparently has a side effect.
1109 # if it is not called the menu pops up several times
1110 #self.receive_list.selectedIndexes()
1112 selected = self.receive_list.selectedItems()
1113 multi_select = len(selected) > 1
1114 addrs = [unicode(item.text(0)) for item in selected]
1115 if not multi_select:
1116 item = self.receive_list.itemAt(position)
1120 if not is_valid(addr):
1121 k = str(item.data(0,32).toString())
1123 self.create_account_menu(position, k, item)
1125 item.setExpanded(not item.isExpanded())
1129 if not multi_select:
1130 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1131 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1132 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1133 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1134 if not self.wallet.is_watching_only():
1135 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1136 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1137 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1138 if self.wallet.is_imported(addr):
1139 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1141 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1142 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1143 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1144 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1146 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1147 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1149 run_hook('receive_menu', menu, addrs)
1150 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1153 def get_sendable_balance(self):
1154 return sum(map(lambda x:x['value'], self.get_coins()))
1157 def get_coins(self):
1159 return self.pay_from
1161 domain = self.wallet.get_account_addresses(self.current_account)
1162 for i in self.wallet.frozen_addresses:
1163 if i in domain: domain.remove(i)
1164 return self.wallet.get_unspent_coins(domain)
1167 def send_from_addresses(self, addrs):
1168 self.set_pay_from( addrs )
1169 self.tabs.setCurrentIndex(1)
1172 def payto(self, addr):
1174 label = self.wallet.labels.get(addr)
1175 m_addr = label + ' <' + addr + '>' if label else addr
1176 self.tabs.setCurrentIndex(1)
1177 self.payto_e.setText(m_addr)
1178 self.amount_e.setFocus()
1181 def delete_contact(self, x):
1182 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1183 self.wallet.delete_contact(x)
1184 self.wallet.set_label(x, None)
1185 self.update_history_tab()
1186 self.update_contacts_tab()
1187 self.update_completions()
1190 def create_contact_menu(self, position):
1191 item = self.contacts_list.itemAt(position)
1194 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1196 addr = unicode(item.text(0))
1197 label = unicode(item.text(1))
1198 is_editable = item.data(0,32).toBool()
1199 payto_addr = item.data(0,33).toString()
1200 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1201 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1202 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1204 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1205 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1207 run_hook('create_contact_menu', menu, item)
1208 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1210 def delete_invoice(self, item):
1211 self.invoices.pop(key)
1212 self.wallet.storage.put('invoices', self.invoices)
1213 self.update_invoices_tab()
1215 def show_invoice(self, key):
1216 from electrum.paymentrequest import PaymentRequest
1217 domain, value = self.invoices[key]
1218 pr = PaymentRequest(self.config)
1222 self.show_pr_details(pr)
1224 def show_pr_details(self, pr):
1225 msg = 'Domain: ' + pr.domain
1226 msg += '\nStatus: ' + pr.get_status()
1227 msg += '\nMemo: ' + pr.memo
1228 msg += '\nPayment URL: ' + pr.payment_url
1229 msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[0] + ' ' + self.format_amount(x[1])+ self.base_unit(), pr.get_outputs()))
1230 QMessageBox.information(self, 'Invoice', msg , 'OK')
1232 def create_invoice_menu(self, position):
1233 item = self.invoices_list.itemAt(position)
1236 k = self.invoices_list.indexOfTopLevelItem(item)
1237 key = self.invoices.keys()[k]
1239 menu.addAction(_("Details"), lambda: self.show_invoice(key))
1240 menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
1241 menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
1244 def update_receive_item(self, item):
1245 item.setFont(0, QFont(MONOSPACE_FONT))
1246 address = str(item.data(0,0).toString())
1247 label = self.wallet.labels.get(address,'')
1248 item.setData(1,0,label)
1249 item.setData(0,32, True) # is editable
1251 run_hook('update_receive_item', address, item)
1253 if not self.wallet.is_mine(address): return
1255 c, u = self.wallet.get_addr_balance(address)
1256 balance = self.format_amount(c + u)
1257 item.setData(2,0,balance)
1259 if address in self.wallet.frozen_addresses:
1260 item.setBackgroundColor(0, QColor('lightblue'))
1263 def update_receive_tab(self):
1264 l = self.receive_list
1265 # extend the syntax for consistency
1266 l.addChild = l.addTopLevelItem
1267 l.insertChild = l.insertTopLevelItem
1270 for i,width in enumerate(self.column_widths['receive']):
1271 l.setColumnWidth(i, width)
1273 accounts = self.wallet.get_accounts()
1274 if self.current_account is None:
1275 account_items = sorted(accounts.items())
1277 account_items = [(self.current_account, accounts.get(self.current_account))]
1280 for k, account in account_items:
1282 if len(accounts) > 1:
1283 name = self.wallet.get_account_name(k)
1284 c,u = self.wallet.get_account_balance(k)
1285 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1286 l.addTopLevelItem(account_item)
1287 account_item.setExpanded(self.accounts_expanded.get(k, True))
1288 account_item.setData(0, 32, k)
1292 sequences = [0,1] if account.has_change() else [0]
1293 for is_change in sequences:
1294 if len(sequences) > 1:
1295 name = _("Receiving") if not is_change else _("Change")
1296 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1297 account_item.addChild(seq_item)
1299 seq_item.setExpanded(True)
1301 seq_item = account_item
1303 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1309 for address in account.get_addresses(is_change):
1311 num, is_used = self.wallet.is_used(address)
1314 if gap > self.wallet.gap_limit:
1319 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1320 self.update_receive_item(item)
1322 item.setBackgroundColor(1, QColor('red'))
1326 seq_item.insertChild(0,used_item)
1328 used_item.addChild(item)
1330 seq_item.addChild(item)
1332 # we use column 1 because column 0 may be hidden
1333 l.setCurrentItem(l.topLevelItem(0),1)
1336 def update_contacts_tab(self):
1337 l = self.contacts_list
1340 for address in self.wallet.addressbook:
1341 label = self.wallet.labels.get(address,'')
1342 n = self.wallet.get_num_tx(address)
1343 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1344 item.setFont(0, QFont(MONOSPACE_FONT))
1345 # 32 = label can be edited (bool)
1346 item.setData(0,32, True)
1348 item.setData(0,33, address)
1349 l.addTopLevelItem(item)
1351 run_hook('update_contacts_tab', l)
1352 l.setCurrentItem(l.topLevelItem(0))
1356 def create_console_tab(self):
1357 from console import Console
1358 self.console = console = Console()
1362 def update_console(self):
1363 console = self.console
1364 console.history = self.config.get("console-history",[])
1365 console.history_index = len(console.history)
1367 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1368 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1370 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1372 def mkfunc(f, method):
1373 return lambda *args: apply( f, (method, args, self.password_dialog ))
1375 if m[0]=='_' or m in ['network','wallet']: continue
1376 methods[m] = mkfunc(c._run, m)
1378 console.updateNamespace(methods)
1381 def change_account(self,s):
1382 if s == _("All accounts"):
1383 self.current_account = None
1385 accounts = self.wallet.get_account_names()
1386 for k, v in accounts.items():
1388 self.current_account = k
1389 self.update_history_tab()
1390 self.update_status()
1391 self.update_receive_tab()
1393 def create_status_bar(self):
1396 sb.setFixedHeight(35)
1397 qtVersion = qVersion()
1399 self.balance_label = QLabel("")
1400 sb.addWidget(self.balance_label)
1402 from version_getter import UpdateLabel
1403 self.updatelabel = UpdateLabel(self.config, sb)
1405 self.account_selector = QComboBox()
1406 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1407 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1408 sb.addPermanentWidget(self.account_selector)
1410 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1411 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1413 self.lock_icon = QIcon()
1414 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1415 sb.addPermanentWidget( self.password_button )
1417 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1418 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1419 sb.addPermanentWidget( self.seed_button )
1420 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1421 sb.addPermanentWidget( self.status_button )
1423 run_hook('create_status_bar', (sb,))
1425 self.setStatusBar(sb)
1428 def update_lock_icon(self):
1429 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1430 self.password_button.setIcon( icon )
1433 def update_buttons_on_seed(self):
1434 if self.wallet.has_seed():
1435 self.seed_button.show()
1437 self.seed_button.hide()
1439 if not self.wallet.is_watching_only():
1440 self.password_button.show()
1441 self.send_button.setText(_("Send"))
1443 self.password_button.hide()
1444 self.send_button.setText(_("Create unsigned transaction"))
1447 def change_password_dialog(self):
1448 from password_dialog import PasswordDialog
1449 d = PasswordDialog(self.wallet, self)
1451 self.update_lock_icon()
1454 def new_contact_dialog(self):
1457 d.setWindowTitle(_("New Contact"))
1458 vbox = QVBoxLayout(d)
1459 vbox.addWidget(QLabel(_('New Contact')+':'))
1461 grid = QGridLayout()
1464 grid.addWidget(QLabel(_("Address")), 1, 0)
1465 grid.addWidget(line1, 1, 1)
1466 grid.addWidget(QLabel(_("Name")), 2, 0)
1467 grid.addWidget(line2, 2, 1)
1469 vbox.addLayout(grid)
1470 vbox.addLayout(ok_cancel_buttons(d))
1475 address = str(line1.text())
1476 label = unicode(line2.text())
1478 if not is_valid(address):
1479 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1482 self.wallet.add_contact(address)
1484 self.wallet.set_label(address, label)
1486 self.update_contacts_tab()
1487 self.update_history_tab()
1488 self.update_completions()
1489 self.tabs.setCurrentIndex(3)
1493 def new_account_dialog(self, password):
1495 dialog = QDialog(self)
1497 dialog.setWindowTitle(_("New Account"))
1499 vbox = QVBoxLayout()
1500 vbox.addWidget(QLabel(_('Account name')+':'))
1503 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1504 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1509 vbox.addLayout(ok_cancel_buttons(dialog))
1510 dialog.setLayout(vbox)
1514 name = str(e.text())
1517 self.wallet.create_pending_account(name, password)
1518 self.update_receive_tab()
1519 self.tabs.setCurrentIndex(2)
1524 def show_master_public_keys(self):
1526 dialog = QDialog(self)
1528 dialog.setWindowTitle(_("Master Public Keys"))
1530 main_layout = QGridLayout()
1531 mpk_dict = self.wallet.get_master_public_keys()
1533 for key, value in mpk_dict.items():
1534 main_layout.addWidget(QLabel(key), i, 0)
1535 mpk_text = QTextEdit()
1536 mpk_text.setReadOnly(True)
1537 mpk_text.setMaximumHeight(170)
1538 mpk_text.setText(value)
1539 main_layout.addWidget(mpk_text, i + 1, 0)
1542 vbox = QVBoxLayout()
1543 vbox.addLayout(main_layout)
1544 vbox.addLayout(close_button(dialog))
1546 dialog.setLayout(vbox)
1551 def show_seed_dialog(self, password):
1552 if not self.wallet.has_seed():
1553 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1557 mnemonic = self.wallet.get_mnemonic(password)
1559 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1561 from seed_dialog import SeedDialog
1562 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1567 def show_qrcode(self, data, title = _("QR code")):
1571 d.setWindowTitle(title)
1572 d.setMinimumSize(270, 300)
1573 vbox = QVBoxLayout()
1574 qrw = QRCodeWidget(data)
1575 vbox.addWidget(qrw, 1)
1576 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1577 hbox = QHBoxLayout()
1580 filename = os.path.join(self.config.path, "qrcode.bmp")
1583 bmp.save_qrcode(qrw.qr, filename)
1584 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1586 def copy_to_clipboard():
1587 bmp.save_qrcode(qrw.qr, filename)
1588 self.app.clipboard().setImage(QImage(filename))
1589 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1591 b = QPushButton(_("Copy"))
1593 b.clicked.connect(copy_to_clipboard)
1595 b = QPushButton(_("Save"))
1597 b.clicked.connect(print_qr)
1599 b = QPushButton(_("Close"))
1601 b.clicked.connect(d.accept)
1604 vbox.addLayout(hbox)
1609 def do_protect(self, func, args):
1610 if self.wallet.use_encryption:
1611 password = self.password_dialog()
1617 if args != (False,):
1618 args = (self,) + args + (password,)
1620 args = (self,password)
1624 def show_public_keys(self, address):
1625 if not address: return
1627 pubkey_list = self.wallet.get_public_keys(address)
1628 except Exception as e:
1629 traceback.print_exc(file=sys.stdout)
1630 self.show_message(str(e))
1634 d.setMinimumSize(600, 200)
1636 vbox = QVBoxLayout()
1637 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1638 vbox.addWidget( QLabel(_("Public key") + ':'))
1640 keys.setReadOnly(True)
1641 keys.setText('\n'.join(pubkey_list))
1642 vbox.addWidget(keys)
1643 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1644 vbox.addLayout(close_button(d))
1649 def show_private_key(self, address, password):
1650 if not address: return
1652 pk_list = self.wallet.get_private_key(address, password)
1653 except Exception as e:
1654 traceback.print_exc(file=sys.stdout)
1655 self.show_message(str(e))
1659 d.setMinimumSize(600, 200)
1661 vbox = QVBoxLayout()
1662 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1663 vbox.addWidget( QLabel(_("Private key") + ':'))
1665 keys.setReadOnly(True)
1666 keys.setText('\n'.join(pk_list))
1667 vbox.addWidget(keys)
1668 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1669 vbox.addLayout(close_button(d))
1675 def do_sign(self, address, message, signature, password):
1676 message = unicode(message.toPlainText())
1677 message = message.encode('utf-8')
1679 sig = self.wallet.sign_message(str(address.text()), message, password)
1680 signature.setText(sig)
1681 except Exception as e:
1682 self.show_message(str(e))
1684 def do_verify(self, address, message, signature):
1685 message = unicode(message.toPlainText())
1686 message = message.encode('utf-8')
1687 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1688 self.show_message(_("Signature verified"))
1690 self.show_message(_("Error: wrong signature"))
1693 def sign_verify_message(self, address=''):
1696 d.setWindowTitle(_('Sign/verify Message'))
1697 d.setMinimumSize(410, 290)
1699 layout = QGridLayout(d)
1701 message_e = QTextEdit()
1702 layout.addWidget(QLabel(_('Message')), 1, 0)
1703 layout.addWidget(message_e, 1, 1)
1704 layout.setRowStretch(2,3)
1706 address_e = QLineEdit()
1707 address_e.setText(address)
1708 layout.addWidget(QLabel(_('Address')), 2, 0)
1709 layout.addWidget(address_e, 2, 1)
1711 signature_e = QTextEdit()
1712 layout.addWidget(QLabel(_('Signature')), 3, 0)
1713 layout.addWidget(signature_e, 3, 1)
1714 layout.setRowStretch(3,1)
1716 hbox = QHBoxLayout()
1718 b = QPushButton(_("Sign"))
1719 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1722 b = QPushButton(_("Verify"))
1723 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1726 b = QPushButton(_("Close"))
1727 b.clicked.connect(d.accept)
1729 layout.addLayout(hbox, 4, 1)
1734 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1736 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1737 message_e.setText(decrypted)
1738 except Exception as e:
1739 self.show_message(str(e))
1742 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1743 message = unicode(message_e.toPlainText())
1744 message = message.encode('utf-8')
1746 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1747 encrypted_e.setText(encrypted)
1748 except Exception as e:
1749 self.show_message(str(e))
1753 def encrypt_message(self, address = ''):
1756 d.setWindowTitle(_('Encrypt/decrypt Message'))
1757 d.setMinimumSize(610, 490)
1759 layout = QGridLayout(d)
1761 message_e = QTextEdit()
1762 layout.addWidget(QLabel(_('Message')), 1, 0)
1763 layout.addWidget(message_e, 1, 1)
1764 layout.setRowStretch(2,3)
1766 pubkey_e = QLineEdit()
1768 pubkey = self.wallet.getpubkeys(address)[0]
1769 pubkey_e.setText(pubkey)
1770 layout.addWidget(QLabel(_('Public key')), 2, 0)
1771 layout.addWidget(pubkey_e, 2, 1)
1773 encrypted_e = QTextEdit()
1774 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1775 layout.addWidget(encrypted_e, 3, 1)
1776 layout.setRowStretch(3,1)
1778 hbox = QHBoxLayout()
1779 b = QPushButton(_("Encrypt"))
1780 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1783 b = QPushButton(_("Decrypt"))
1784 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1787 b = QPushButton(_("Close"))
1788 b.clicked.connect(d.accept)
1791 layout.addLayout(hbox, 4, 1)
1795 def question(self, msg):
1796 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1798 def show_message(self, msg):
1799 QMessageBox.information(self, _('Message'), msg, _('OK'))
1801 def password_dialog(self, msg=None):
1804 d.setWindowTitle(_("Enter Password"))
1809 vbox = QVBoxLayout()
1811 msg = _('Please enter your password')
1812 vbox.addWidget(QLabel(msg))
1814 grid = QGridLayout()
1816 grid.addWidget(QLabel(_('Password')), 1, 0)
1817 grid.addWidget(pw, 1, 1)
1818 vbox.addLayout(grid)
1820 vbox.addLayout(ok_cancel_buttons(d))
1823 run_hook('password_dialog', pw, grid, 1)
1824 if not d.exec_(): return
1825 return unicode(pw.text())
1834 def tx_from_text(self, txt):
1835 "json or raw hexadecimal"
1838 tx = Transaction(txt)
1844 tx_dict = json.loads(str(txt))
1845 assert "hex" in tx_dict.keys()
1846 tx = Transaction(tx_dict["hex"])
1847 if tx_dict.has_key("input_info"):
1848 input_info = json.loads(tx_dict['input_info'])
1849 tx.add_input_info(input_info)
1852 traceback.print_exc(file=sys.stdout)
1855 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1859 def read_tx_from_file(self):
1860 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1864 with open(fileName, "r") as f:
1865 file_content = f.read()
1866 except (ValueError, IOError, os.error), reason:
1867 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1869 return self.tx_from_text(file_content)
1873 def sign_raw_transaction(self, tx, input_info, password):
1874 self.wallet.signrawtransaction(tx, input_info, [], password)
1876 def do_process_from_text(self):
1877 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1880 tx = self.tx_from_text(text)
1882 self.show_transaction(tx)
1884 def do_process_from_file(self):
1885 tx = self.read_tx_from_file()
1887 self.show_transaction(tx)
1889 def do_process_from_txid(self):
1890 from electrum import transaction
1891 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1893 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1895 tx = transaction.Transaction(r)
1897 self.show_transaction(tx)
1899 self.show_message("unknown transaction")
1901 def do_process_from_csvReader(self, csvReader):
1906 for position, row in enumerate(csvReader):
1908 if not is_valid(address):
1909 errors.append((position, address))
1911 amount = Decimal(row[1])
1912 amount = int(100000000*amount)
1913 outputs.append((address, amount))
1914 except (ValueError, IOError, os.error), reason:
1915 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1919 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1920 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1924 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1925 except Exception as e:
1926 self.show_message(str(e))
1929 self.show_transaction(tx)
1931 def do_process_from_csv_file(self):
1932 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1936 with open(fileName, "r") as f:
1937 csvReader = csv.reader(f)
1938 self.do_process_from_csvReader(csvReader)
1939 except (ValueError, IOError, os.error), reason:
1940 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1943 def do_process_from_csv_text(self):
1944 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1945 + _("Format: address, amount. One output per line"), _("Load CSV"))
1948 f = StringIO.StringIO(text)
1949 csvReader = csv.reader(f)
1950 self.do_process_from_csvReader(csvReader)
1955 def export_privkeys_dialog(self, password):
1956 if self.wallet.is_watching_only():
1957 self.show_message(_("This is a watching-only wallet"))
1961 d.setWindowTitle(_('Private keys'))
1962 d.setMinimumSize(850, 300)
1963 vbox = QVBoxLayout(d)
1965 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1966 _("Exposing a single private key can compromise your entire wallet!"),
1967 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1968 vbox.addWidget(QLabel(msg))
1974 defaultname = 'electrum-private-keys.csv'
1975 select_msg = _('Select file to export your private keys to')
1976 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1977 vbox.addLayout(hbox)
1979 h, b = ok_cancel_buttons2(d, _('Export'))
1984 addresses = self.wallet.addresses(True)
1986 def privkeys_thread():
1987 for addr in addresses:
1991 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1992 d.emit(SIGNAL('computing_privkeys'))
1993 d.emit(SIGNAL('show_privkeys'))
1995 def show_privkeys():
1996 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
2000 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
2001 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
2002 threading.Thread(target=privkeys_thread).start()
2008 filename = filename_e.text()
2013 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
2014 except (IOError, os.error), reason:
2015 export_error_label = _("Electrum was unable to produce a private key-export.")
2016 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2018 except Exception as e:
2019 self.show_message(str(e))
2022 self.show_message(_("Private keys exported."))
2025 def do_export_privkeys(self, fileName, pklist, is_csv):
2026 with open(fileName, "w+") as f:
2028 transaction = csv.writer(f)
2029 transaction.writerow(["address", "private_key"])
2030 for addr, pk in pklist.items():
2031 transaction.writerow(["%34s"%addr,pk])
2034 f.write(json.dumps(pklist, indent = 4))
2037 def do_import_labels(self):
2038 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2039 if not labelsFile: return
2041 f = open(labelsFile, 'r')
2044 for key, value in json.loads(data).items():
2045 self.wallet.set_label(key, value)
2046 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2047 except (IOError, os.error), reason:
2048 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2051 def do_export_labels(self):
2052 labels = self.wallet.labels
2054 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2056 with open(fileName, 'w+') as f:
2057 json.dump(labels, f)
2058 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2059 except (IOError, os.error), reason:
2060 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2063 def export_history_dialog(self):
2066 d.setWindowTitle(_('Export History'))
2067 d.setMinimumSize(400, 200)
2068 vbox = QVBoxLayout(d)
2070 defaultname = os.path.expanduser('~/electrum-history.csv')
2071 select_msg = _('Select file to export your wallet transactions to')
2073 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2074 vbox.addLayout(hbox)
2078 h, b = ok_cancel_buttons2(d, _('Export'))
2083 filename = filename_e.text()
2088 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2089 except (IOError, os.error), reason:
2090 export_error_label = _("Electrum was unable to produce a transaction export.")
2091 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2094 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2097 def do_export_history(self, wallet, fileName, is_csv):
2098 history = wallet.get_tx_history()
2100 for item in history:
2101 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2103 if timestamp is not None:
2105 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2106 except [RuntimeError, TypeError, NameError] as reason:
2107 time_string = "unknown"
2110 time_string = "unknown"
2112 time_string = "pending"
2114 if value is not None:
2115 value_string = format_satoshis(value, True)
2120 fee_string = format_satoshis(fee, True)
2125 label, is_default_label = wallet.get_label(tx_hash)
2126 label = label.encode('utf-8')
2130 balance_string = format_satoshis(balance, False)
2132 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2134 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2136 with open(fileName, "w+") as f:
2138 transaction = csv.writer(f)
2139 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2141 transaction.writerow(line)
2144 f.write(json.dumps(lines, indent = 4))
2147 def sweep_key_dialog(self):
2149 d.setWindowTitle(_('Sweep private keys'))
2150 d.setMinimumSize(600, 300)
2152 vbox = QVBoxLayout(d)
2153 vbox.addWidget(QLabel(_("Enter private keys")))
2155 keys_e = QTextEdit()
2156 keys_e.setTabChangesFocus(True)
2157 vbox.addWidget(keys_e)
2159 h, address_e = address_field(self.wallet.addresses())
2163 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2164 vbox.addLayout(hbox)
2165 button.setEnabled(False)
2168 addr = str(address_e.text())
2169 if bitcoin.is_address(addr):
2173 pk = str(keys_e.toPlainText()).strip()
2174 if Wallet.is_private_key(pk):
2177 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2178 keys_e.textChanged.connect(f)
2179 address_e.textChanged.connect(f)
2183 fee = self.wallet.fee
2184 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2185 self.show_transaction(tx)
2189 def do_import_privkey(self, password):
2190 if not self.wallet.has_imported_keys():
2191 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2192 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2193 + _('Are you sure you understand what you are doing?'), 3, 4)
2196 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2199 text = str(text).split()
2204 addr = self.wallet.import_key(key, password)
2205 except Exception as e:
2211 addrlist.append(addr)
2213 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2215 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2216 self.update_receive_tab()
2217 self.update_history_tab()
2220 def settings_dialog(self):
2222 d.setWindowTitle(_('Electrum Settings'))
2224 vbox = QVBoxLayout()
2225 grid = QGridLayout()
2226 grid.setColumnStretch(0,1)
2228 nz_label = QLabel(_('Display zeros') + ':')
2229 grid.addWidget(nz_label, 0, 0)
2230 nz_e = AmountEdit(None,True)
2231 nz_e.setText("%d"% self.num_zeros)
2232 grid.addWidget(nz_e, 0, 1)
2233 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2234 grid.addWidget(HelpButton(msg), 0, 2)
2235 if not self.config.is_modifiable('num_zeros'):
2236 for w in [nz_e, nz_label]: w.setEnabled(False)
2238 lang_label=QLabel(_('Language') + ':')
2239 grid.addWidget(lang_label, 1, 0)
2240 lang_combo = QComboBox()
2241 from electrum.i18n import languages
2242 lang_combo.addItems(languages.values())
2244 index = languages.keys().index(self.config.get("language",''))
2247 lang_combo.setCurrentIndex(index)
2248 grid.addWidget(lang_combo, 1, 1)
2249 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2250 if not self.config.is_modifiable('language'):
2251 for w in [lang_combo, lang_label]: w.setEnabled(False)
2254 fee_label = QLabel(_('Transaction fee') + ':')
2255 grid.addWidget(fee_label, 2, 0)
2256 fee_e = BTCAmountEdit(self.get_decimal_point)
2257 fee_e.setAmount(self.wallet.fee)
2258 grid.addWidget(fee_e, 2, 1)
2259 msg = _('Fee per kilobyte of transaction.') + ' ' \
2260 + _('Recommended value') + ': ' + self.format_amount(20000)
2261 grid.addWidget(HelpButton(msg), 2, 2)
2262 if not self.config.is_modifiable('fee_per_kb'):
2263 for w in [fee_e, fee_label]: w.setEnabled(False)
2265 units = ['BTC', 'mBTC']
2266 unit_label = QLabel(_('Base unit') + ':')
2267 grid.addWidget(unit_label, 3, 0)
2268 unit_combo = QComboBox()
2269 unit_combo.addItems(units)
2270 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2271 grid.addWidget(unit_combo, 3, 1)
2272 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2273 + '\n1BTC=1000mBTC.\n' \
2274 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2276 usechange_cb = QCheckBox(_('Use change addresses'))
2277 usechange_cb.setChecked(self.wallet.use_change)
2278 grid.addWidget(usechange_cb, 4, 0)
2279 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2280 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2282 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2283 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2284 grid.addWidget(block_ex_label, 5, 0)
2285 block_ex_combo = QComboBox()
2286 block_ex_combo.addItems(block_explorers)
2287 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2288 grid.addWidget(block_ex_combo, 5, 1)
2289 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2291 show_tx = self.config.get('show_before_broadcast', False)
2292 showtx_cb = QCheckBox(_('Show before broadcast'))
2293 showtx_cb.setChecked(show_tx)
2294 grid.addWidget(showtx_cb, 6, 0)
2295 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2297 vbox.addLayout(grid)
2299 vbox.addLayout(ok_cancel_buttons(d))
2303 if not d.exec_(): return
2305 fee = fee_e.get_amount()
2307 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2310 self.wallet.set_fee(fee)
2312 nz = unicode(nz_e.text())
2317 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2320 if self.num_zeros != nz:
2322 self.config.set_key('num_zeros', nz, True)
2323 self.update_history_tab()
2324 self.update_receive_tab()
2326 usechange_result = usechange_cb.isChecked()
2327 if self.wallet.use_change != usechange_result:
2328 self.wallet.use_change = usechange_result
2329 self.wallet.storage.put('use_change', self.wallet.use_change)
2331 if showtx_cb.isChecked() != show_tx:
2332 self.config.set_key('show_before_broadcast', not show_tx)
2334 unit_result = units[unit_combo.currentIndex()]
2335 if self.base_unit() != unit_result:
2336 self.decimal_point = 8 if unit_result == 'BTC' else 5
2337 self.config.set_key('decimal_point', self.decimal_point, True)
2338 self.update_history_tab()
2339 self.update_status()
2341 need_restart = False
2343 lang_request = languages.keys()[lang_combo.currentIndex()]
2344 if lang_request != self.config.get('language'):
2345 self.config.set_key("language", lang_request, True)
2348 be_result = block_explorers[block_ex_combo.currentIndex()]
2349 self.config.set_key('block_explorer', be_result, True)
2351 run_hook('close_settings_dialog')
2354 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2357 def run_network_dialog(self):
2358 if not self.network:
2360 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2362 def closeEvent(self, event):
2364 self.config.set_key("is_maximized", self.isMaximized())
2365 if not self.isMaximized():
2367 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2368 self.save_column_widths()
2369 self.config.set_key("console-history", self.console.history[-50:], True)
2370 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2374 def plugins_dialog(self):
2375 from electrum.plugins import plugins
2378 d.setWindowTitle(_('Electrum Plugins'))
2381 vbox = QVBoxLayout(d)
2384 scroll = QScrollArea()
2385 scroll.setEnabled(True)
2386 scroll.setWidgetResizable(True)
2387 scroll.setMinimumSize(400,250)
2388 vbox.addWidget(scroll)
2392 w.setMinimumHeight(len(plugins)*35)
2394 grid = QGridLayout()
2395 grid.setColumnStretch(0,1)
2398 def do_toggle(cb, p, w):
2401 if w: w.setEnabled(r)
2403 def mk_toggle(cb, p, w):
2404 return lambda: do_toggle(cb,p,w)
2406 for i, p in enumerate(plugins):
2408 cb = QCheckBox(p.fullname())
2409 cb.setDisabled(not p.is_available())
2410 cb.setChecked(p.is_enabled())
2411 grid.addWidget(cb, i, 0)
2412 if p.requires_settings():
2413 w = p.settings_widget(self)
2414 w.setEnabled( p.is_enabled() )
2415 grid.addWidget(w, i, 1)
2418 cb.clicked.connect(mk_toggle(cb,p,w))
2419 grid.addWidget(HelpButton(p.description()), i, 2)
2421 print_msg(_("Error: cannot display plugin"), p)
2422 traceback.print_exc(file=sys.stdout)
2423 grid.setRowStretch(i+1,1)
2425 vbox.addLayout(close_button(d))
2430 def show_account_details(self, k):
2431 account = self.wallet.accounts[k]
2434 d.setWindowTitle(_('Account Details'))
2437 vbox = QVBoxLayout(d)
2438 name = self.wallet.get_account_name(k)
2439 label = QLabel('Name: ' + name)
2440 vbox.addWidget(label)
2442 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2444 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2446 vbox.addWidget(QLabel(_('Master Public Key:')))
2449 text.setReadOnly(True)
2450 text.setMaximumHeight(170)
2451 vbox.addWidget(text)
2453 mpk_text = '\n'.join( account.get_master_pubkeys() )
2454 text.setText(mpk_text)
2456 vbox.addLayout(close_button(d))