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.setText( self.format_amount(amount) )
721 self.fee_e.setText( self.format_amount( 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.setText( self.format_amount( 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))
820 fee = self.fee_e.get_amount()
822 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
825 confirm_amount = self.config.get('confirm_amount', 100000000)
826 if amount >= confirm_amount:
827 o = '\n'.join(map(lambda x:x[0], outputs))
828 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
831 confirm_fee = self.config.get('confirm_fee', 100000)
832 if fee >= confirm_fee:
833 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()}):
836 self.send_tx(outputs, fee, label)
841 def send_tx(self, outputs, fee, label, password):
842 self.send_button.setDisabled(True)
844 # first, create an unsigned tx
845 coins = self.get_coins()
847 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
849 except Exception as e:
850 traceback.print_exc(file=sys.stdout)
851 self.show_message(str(e))
852 self.send_button.setDisabled(False)
855 # call hook to see if plugin needs gui interaction
856 run_hook('send_tx', tx)
862 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
863 self.wallet.sign_transaction(tx, keypairs, password)
864 return tx, fee, label
866 def sign_done(tx, fee, label):
868 self.show_message(tx.error)
869 self.send_button.setDisabled(False)
871 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
872 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
873 self.send_button.setDisabled(False)
876 self.wallet.set_label(tx.hash(), label)
878 if not tx.is_complete() or self.config.get('show_before_broadcast'):
879 self.show_transaction(tx)
881 self.send_button.setDisabled(False)
884 self.broadcast_transaction(tx)
886 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
887 self.waiting_dialog.start()
891 def broadcast_transaction(self, tx):
893 def broadcast_thread():
894 if self.gui_object.payment_request:
895 refund_address = self.wallet.addresses()[0]
896 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
897 self.gui_object.payment_request = None
899 status, msg = self.wallet.sendtx(tx)
902 def broadcast_done(status, msg):
904 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
907 QMessageBox.warning(self, _('Error'), msg, _('OK'))
908 self.send_button.setDisabled(False)
910 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
911 self.waiting_dialog.start()
915 def prepare_for_payment_request(self):
916 self.tabs.setCurrentIndex(1)
917 self.payto_e.is_pr = True
918 for e in [self.payto_e, self.amount_e, self.message_e]:
920 for h in [self.payto_help, self.amount_help, self.message_help]:
922 self.payto_e.setText(_("please wait..."))
925 def payment_request_ok(self):
926 pr = self.gui_object.payment_request
929 self.invoices[pr_id] = (pr.get_domain(), pr.get_amount())
930 self.wallet.storage.put('invoices', self.invoices)
931 self.update_invoices_tab()
933 self.payto_help.show()
934 self.payto_help.set_alt(lambda: self.show_pr_details(pr))
936 self.payto_e.setGreen()
937 self.payto_e.setText(pr.domain)
938 self.amount_e.setText(self.format_amount(pr.get_amount()))
939 self.message_e.setText(pr.memo)
941 def payment_request_error(self):
943 self.show_message(self.gui_object.payment_request.error)
944 self.gui_object.payment_request = None
946 def set_send(self, address, amount, label, message):
948 if label and self.wallet.labels.get(address) != label:
949 if self.question('Give label "%s" to address %s ?'%(label,address)):
950 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
951 self.wallet.addressbook.append(address)
952 self.wallet.set_label(address, label)
954 self.tabs.setCurrentIndex(1)
955 label = self.wallet.labels.get(address)
956 m_addr = label + ' <'+ address +'>' if label else address
957 self.payto_e.setText(m_addr)
959 self.message_e.setText(message)
961 self.amount_e.setText(amount)
965 self.payto_e.is_pr = False
966 self.payto_sig.setVisible(False)
967 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
971 for h in [self.payto_help, self.amount_help, self.message_help]:
974 self.payto_help.set_alt(None)
975 self.set_pay_from([])
980 def set_addrs_frozen(self,addrs,freeze):
982 if not addr: continue
983 if addr in self.wallet.frozen_addresses and not freeze:
984 self.wallet.unfreeze(addr)
985 elif addr not in self.wallet.frozen_addresses and freeze:
986 self.wallet.freeze(addr)
987 self.update_receive_tab()
991 def create_list_tab(self, headers):
992 "generic tab creation method"
993 l = MyTreeWidget(self)
994 l.setColumnCount( len(headers) )
995 l.setHeaderLabels( headers )
1005 vbox.addWidget(buttons)
1010 def create_receive_tab(self):
1011 l, w = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1012 l.setContextMenuPolicy(Qt.CustomContextMenu)
1013 l.customContextMenuRequested.connect(self.create_receive_menu)
1014 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1015 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1016 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1017 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1018 self.receive_list = l
1024 def save_column_widths(self):
1025 self.column_widths["receive"] = []
1026 for i in range(self.receive_list.columnCount() -1):
1027 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1029 self.column_widths["history"] = []
1030 for i in range(self.history_list.columnCount() - 1):
1031 self.column_widths["history"].append(self.history_list.columnWidth(i))
1033 self.column_widths["contacts"] = []
1034 for i in range(self.contacts_list.columnCount() - 1):
1035 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1037 self.config.set_key("column_widths_2", self.column_widths, True)
1040 def create_contacts_tab(self):
1041 l, w = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1042 l.setContextMenuPolicy(Qt.CustomContextMenu)
1043 l.customContextMenuRequested.connect(self.create_contact_menu)
1044 for i,width in enumerate(self.column_widths['contacts']):
1045 l.setColumnWidth(i, width)
1047 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1048 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1049 self.contacts_list = l
1053 def create_invoices_tab(self):
1054 l, w = self.create_list_tab([_('Requestor'), _('Amount'), _('Status')])
1055 l.setContextMenuPolicy(Qt.CustomContextMenu)
1056 l.customContextMenuRequested.connect(self.create_invoice_menu)
1057 self.invoices_list = l
1060 def update_invoices_tab(self):
1061 invoices = self.wallet.storage.get('invoices', {})
1062 l = self.invoices_list
1065 for item, value in invoices.items():
1066 domain, amount = value
1067 item = QTreeWidgetItem( [ domain, self.format_amount(amount), ""] )
1068 l.addTopLevelItem(item)
1070 l.setCurrentItem(l.topLevelItem(0))
1074 def delete_imported_key(self, addr):
1075 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1076 self.wallet.delete_imported_key(addr)
1077 self.update_receive_tab()
1078 self.update_history_tab()
1080 def edit_account_label(self, k):
1081 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1083 label = unicode(text)
1084 self.wallet.set_label(k,label)
1085 self.update_receive_tab()
1087 def account_set_expanded(self, item, k, b):
1089 self.accounts_expanded[k] = b
1091 def create_account_menu(self, position, k, item):
1093 if item.isExpanded():
1094 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1096 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1097 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1098 if self.wallet.seed_version > 4:
1099 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1100 if self.wallet.account_is_pending(k):
1101 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1102 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1104 def delete_pending_account(self, k):
1105 self.wallet.delete_pending_account(k)
1106 self.update_receive_tab()
1108 def create_receive_menu(self, position):
1109 # fixme: this function apparently has a side effect.
1110 # if it is not called the menu pops up several times
1111 #self.receive_list.selectedIndexes()
1113 selected = self.receive_list.selectedItems()
1114 multi_select = len(selected) > 1
1115 addrs = [unicode(item.text(0)) for item in selected]
1116 if not multi_select:
1117 item = self.receive_list.itemAt(position)
1121 if not is_valid(addr):
1122 k = str(item.data(0,32).toString())
1124 self.create_account_menu(position, k, item)
1126 item.setExpanded(not item.isExpanded())
1130 if not multi_select:
1131 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1132 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1133 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1134 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1135 if not self.wallet.is_watching_only():
1136 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1137 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1138 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1139 if self.wallet.is_imported(addr):
1140 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1142 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1143 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1144 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1145 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1147 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1148 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1150 run_hook('receive_menu', menu, addrs)
1151 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1154 def get_sendable_balance(self):
1155 return sum(map(lambda x:x['value'], self.get_coins()))
1158 def get_coins(self):
1160 return self.pay_from
1162 domain = self.wallet.get_account_addresses(self.current_account)
1163 for i in self.wallet.frozen_addresses:
1164 if i in domain: domain.remove(i)
1165 return self.wallet.get_unspent_coins(domain)
1168 def send_from_addresses(self, addrs):
1169 self.set_pay_from( addrs )
1170 self.tabs.setCurrentIndex(1)
1173 def payto(self, addr):
1175 label = self.wallet.labels.get(addr)
1176 m_addr = label + ' <' + addr + '>' if label else addr
1177 self.tabs.setCurrentIndex(1)
1178 self.payto_e.setText(m_addr)
1179 self.amount_e.setFocus()
1182 def delete_contact(self, x):
1183 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1184 self.wallet.delete_contact(x)
1185 self.wallet.set_label(x, None)
1186 self.update_history_tab()
1187 self.update_contacts_tab()
1188 self.update_completions()
1191 def create_contact_menu(self, position):
1192 item = self.contacts_list.itemAt(position)
1195 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1197 addr = unicode(item.text(0))
1198 label = unicode(item.text(1))
1199 is_editable = item.data(0,32).toBool()
1200 payto_addr = item.data(0,33).toString()
1201 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1202 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1203 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1205 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1206 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1208 run_hook('create_contact_menu', menu, item)
1209 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1211 def delete_invoice(self, item):
1212 self.invoices.pop(key)
1213 self.wallet.storage.put('invoices', self.invoices)
1214 self.update_invoices_tab()
1216 def show_invoice(self, key):
1217 from electrum.paymentrequest import PaymentRequest
1218 domain, value = self.invoices[key]
1219 pr = PaymentRequest(self.config)
1223 self.show_pr_details(pr)
1225 def show_pr_details(self, pr):
1226 msg = 'Domain: ' + pr.domain
1227 msg += '\nStatus: ' + pr.get_status()
1228 msg += '\nMemo: ' + pr.memo
1229 msg += '\nPayment URL: ' + pr.payment_url
1230 msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[0] + ' ' + self.format_amount(x[1])+ self.base_unit(), pr.get_outputs()))
1231 QMessageBox.information(self, 'Invoice', msg , 'OK')
1233 def create_invoice_menu(self, position):
1234 item = self.invoices_list.itemAt(position)
1237 k = self.invoices_list.indexOfTopLevelItem(item)
1238 key = self.invoices.keys()[k]
1240 menu.addAction(_("Details"), lambda: self.show_invoice(key))
1241 menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
1242 menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
1245 def update_receive_item(self, item):
1246 item.setFont(0, QFont(MONOSPACE_FONT))
1247 address = str(item.data(0,0).toString())
1248 label = self.wallet.labels.get(address,'')
1249 item.setData(1,0,label)
1250 item.setData(0,32, True) # is editable
1252 run_hook('update_receive_item', address, item)
1254 if not self.wallet.is_mine(address): return
1256 c, u = self.wallet.get_addr_balance(address)
1257 balance = self.format_amount(c + u)
1258 item.setData(2,0,balance)
1260 if address in self.wallet.frozen_addresses:
1261 item.setBackgroundColor(0, QColor('lightblue'))
1264 def update_receive_tab(self):
1265 l = self.receive_list
1266 # extend the syntax for consistency
1267 l.addChild = l.addTopLevelItem
1268 l.insertChild = l.insertTopLevelItem
1271 for i,width in enumerate(self.column_widths['receive']):
1272 l.setColumnWidth(i, width)
1274 accounts = self.wallet.get_accounts()
1275 if self.current_account is None:
1276 account_items = sorted(accounts.items())
1278 account_items = [(self.current_account, accounts.get(self.current_account))]
1281 for k, account in account_items:
1283 if len(accounts) > 1:
1284 name = self.wallet.get_account_name(k)
1285 c,u = self.wallet.get_account_balance(k)
1286 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1287 l.addTopLevelItem(account_item)
1288 account_item.setExpanded(self.accounts_expanded.get(k, True))
1289 account_item.setData(0, 32, k)
1293 sequences = [0,1] if account.has_change() else [0]
1294 for is_change in sequences:
1295 if len(sequences) > 1:
1296 name = _("Receiving") if not is_change else _("Change")
1297 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1298 account_item.addChild(seq_item)
1300 seq_item.setExpanded(True)
1302 seq_item = account_item
1304 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1310 for address in account.get_addresses(is_change):
1312 num, is_used = self.wallet.is_used(address)
1315 if gap > self.wallet.gap_limit:
1320 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1321 self.update_receive_item(item)
1323 item.setBackgroundColor(1, QColor('red'))
1327 seq_item.insertChild(0,used_item)
1329 used_item.addChild(item)
1331 seq_item.addChild(item)
1333 # we use column 1 because column 0 may be hidden
1334 l.setCurrentItem(l.topLevelItem(0),1)
1337 def update_contacts_tab(self):
1338 l = self.contacts_list
1341 for address in self.wallet.addressbook:
1342 label = self.wallet.labels.get(address,'')
1343 n = self.wallet.get_num_tx(address)
1344 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1345 item.setFont(0, QFont(MONOSPACE_FONT))
1346 # 32 = label can be edited (bool)
1347 item.setData(0,32, True)
1349 item.setData(0,33, address)
1350 l.addTopLevelItem(item)
1352 run_hook('update_contacts_tab', l)
1353 l.setCurrentItem(l.topLevelItem(0))
1357 def create_console_tab(self):
1358 from console import Console
1359 self.console = console = Console()
1363 def update_console(self):
1364 console = self.console
1365 console.history = self.config.get("console-history",[])
1366 console.history_index = len(console.history)
1368 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1369 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1371 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1373 def mkfunc(f, method):
1374 return lambda *args: apply( f, (method, args, self.password_dialog ))
1376 if m[0]=='_' or m in ['network','wallet']: continue
1377 methods[m] = mkfunc(c._run, m)
1379 console.updateNamespace(methods)
1382 def change_account(self,s):
1383 if s == _("All accounts"):
1384 self.current_account = None
1386 accounts = self.wallet.get_account_names()
1387 for k, v in accounts.items():
1389 self.current_account = k
1390 self.update_history_tab()
1391 self.update_status()
1392 self.update_receive_tab()
1394 def create_status_bar(self):
1397 sb.setFixedHeight(35)
1398 qtVersion = qVersion()
1400 self.balance_label = QLabel("")
1401 sb.addWidget(self.balance_label)
1403 from version_getter import UpdateLabel
1404 self.updatelabel = UpdateLabel(self.config, sb)
1406 self.account_selector = QComboBox()
1407 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1408 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1409 sb.addPermanentWidget(self.account_selector)
1411 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1412 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1414 self.lock_icon = QIcon()
1415 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1416 sb.addPermanentWidget( self.password_button )
1418 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1419 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1420 sb.addPermanentWidget( self.seed_button )
1421 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1422 sb.addPermanentWidget( self.status_button )
1424 run_hook('create_status_bar', (sb,))
1426 self.setStatusBar(sb)
1429 def update_lock_icon(self):
1430 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1431 self.password_button.setIcon( icon )
1434 def update_buttons_on_seed(self):
1435 if self.wallet.has_seed():
1436 self.seed_button.show()
1438 self.seed_button.hide()
1440 if not self.wallet.is_watching_only():
1441 self.password_button.show()
1442 self.send_button.setText(_("Send"))
1444 self.password_button.hide()
1445 self.send_button.setText(_("Create unsigned transaction"))
1448 def change_password_dialog(self):
1449 from password_dialog import PasswordDialog
1450 d = PasswordDialog(self.wallet, self)
1452 self.update_lock_icon()
1455 def new_contact_dialog(self):
1458 d.setWindowTitle(_("New Contact"))
1459 vbox = QVBoxLayout(d)
1460 vbox.addWidget(QLabel(_('New Contact')+':'))
1462 grid = QGridLayout()
1465 grid.addWidget(QLabel(_("Address")), 1, 0)
1466 grid.addWidget(line1, 1, 1)
1467 grid.addWidget(QLabel(_("Name")), 2, 0)
1468 grid.addWidget(line2, 2, 1)
1470 vbox.addLayout(grid)
1471 vbox.addLayout(ok_cancel_buttons(d))
1476 address = str(line1.text())
1477 label = unicode(line2.text())
1479 if not is_valid(address):
1480 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1483 self.wallet.add_contact(address)
1485 self.wallet.set_label(address, label)
1487 self.update_contacts_tab()
1488 self.update_history_tab()
1489 self.update_completions()
1490 self.tabs.setCurrentIndex(3)
1494 def new_account_dialog(self, password):
1496 dialog = QDialog(self)
1498 dialog.setWindowTitle(_("New Account"))
1500 vbox = QVBoxLayout()
1501 vbox.addWidget(QLabel(_('Account name')+':'))
1504 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1505 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1510 vbox.addLayout(ok_cancel_buttons(dialog))
1511 dialog.setLayout(vbox)
1515 name = str(e.text())
1518 self.wallet.create_pending_account(name, password)
1519 self.update_receive_tab()
1520 self.tabs.setCurrentIndex(2)
1525 def show_master_public_keys(self):
1527 dialog = QDialog(self)
1529 dialog.setWindowTitle(_("Master Public Keys"))
1531 main_layout = QGridLayout()
1532 mpk_dict = self.wallet.get_master_public_keys()
1534 for key, value in mpk_dict.items():
1535 main_layout.addWidget(QLabel(key), i, 0)
1536 mpk_text = QTextEdit()
1537 mpk_text.setReadOnly(True)
1538 mpk_text.setMaximumHeight(170)
1539 mpk_text.setText(value)
1540 main_layout.addWidget(mpk_text, i + 1, 0)
1543 vbox = QVBoxLayout()
1544 vbox.addLayout(main_layout)
1545 vbox.addLayout(close_button(dialog))
1547 dialog.setLayout(vbox)
1552 def show_seed_dialog(self, password):
1553 if not self.wallet.has_seed():
1554 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1558 mnemonic = self.wallet.get_mnemonic(password)
1560 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1562 from seed_dialog import SeedDialog
1563 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1568 def show_qrcode(self, data, title = _("QR code")):
1572 d.setWindowTitle(title)
1573 d.setMinimumSize(270, 300)
1574 vbox = QVBoxLayout()
1575 qrw = QRCodeWidget(data)
1576 vbox.addWidget(qrw, 1)
1577 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1578 hbox = QHBoxLayout()
1581 filename = os.path.join(self.config.path, "qrcode.bmp")
1584 bmp.save_qrcode(qrw.qr, filename)
1585 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1587 def copy_to_clipboard():
1588 bmp.save_qrcode(qrw.qr, filename)
1589 self.app.clipboard().setImage(QImage(filename))
1590 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1592 b = QPushButton(_("Copy"))
1594 b.clicked.connect(copy_to_clipboard)
1596 b = QPushButton(_("Save"))
1598 b.clicked.connect(print_qr)
1600 b = QPushButton(_("Close"))
1602 b.clicked.connect(d.accept)
1605 vbox.addLayout(hbox)
1610 def do_protect(self, func, args):
1611 if self.wallet.use_encryption:
1612 password = self.password_dialog()
1618 if args != (False,):
1619 args = (self,) + args + (password,)
1621 args = (self,password)
1625 def show_public_keys(self, address):
1626 if not address: return
1628 pubkey_list = self.wallet.get_public_keys(address)
1629 except Exception as e:
1630 traceback.print_exc(file=sys.stdout)
1631 self.show_message(str(e))
1635 d.setMinimumSize(600, 200)
1637 vbox = QVBoxLayout()
1638 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1639 vbox.addWidget( QLabel(_("Public key") + ':'))
1641 keys.setReadOnly(True)
1642 keys.setText('\n'.join(pubkey_list))
1643 vbox.addWidget(keys)
1644 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1645 vbox.addLayout(close_button(d))
1650 def show_private_key(self, address, password):
1651 if not address: return
1653 pk_list = self.wallet.get_private_key(address, password)
1654 except Exception as e:
1655 traceback.print_exc(file=sys.stdout)
1656 self.show_message(str(e))
1660 d.setMinimumSize(600, 200)
1662 vbox = QVBoxLayout()
1663 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1664 vbox.addWidget( QLabel(_("Private key") + ':'))
1666 keys.setReadOnly(True)
1667 keys.setText('\n'.join(pk_list))
1668 vbox.addWidget(keys)
1669 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1670 vbox.addLayout(close_button(d))
1676 def do_sign(self, address, message, signature, password):
1677 message = unicode(message.toPlainText())
1678 message = message.encode('utf-8')
1680 sig = self.wallet.sign_message(str(address.text()), message, password)
1681 signature.setText(sig)
1682 except Exception as e:
1683 self.show_message(str(e))
1685 def do_verify(self, address, message, signature):
1686 message = unicode(message.toPlainText())
1687 message = message.encode('utf-8')
1688 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1689 self.show_message(_("Signature verified"))
1691 self.show_message(_("Error: wrong signature"))
1694 def sign_verify_message(self, address=''):
1697 d.setWindowTitle(_('Sign/verify Message'))
1698 d.setMinimumSize(410, 290)
1700 layout = QGridLayout(d)
1702 message_e = QTextEdit()
1703 layout.addWidget(QLabel(_('Message')), 1, 0)
1704 layout.addWidget(message_e, 1, 1)
1705 layout.setRowStretch(2,3)
1707 address_e = QLineEdit()
1708 address_e.setText(address)
1709 layout.addWidget(QLabel(_('Address')), 2, 0)
1710 layout.addWidget(address_e, 2, 1)
1712 signature_e = QTextEdit()
1713 layout.addWidget(QLabel(_('Signature')), 3, 0)
1714 layout.addWidget(signature_e, 3, 1)
1715 layout.setRowStretch(3,1)
1717 hbox = QHBoxLayout()
1719 b = QPushButton(_("Sign"))
1720 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1723 b = QPushButton(_("Verify"))
1724 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1727 b = QPushButton(_("Close"))
1728 b.clicked.connect(d.accept)
1730 layout.addLayout(hbox, 4, 1)
1735 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1737 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1738 message_e.setText(decrypted)
1739 except Exception as e:
1740 self.show_message(str(e))
1743 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1744 message = unicode(message_e.toPlainText())
1745 message = message.encode('utf-8')
1747 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1748 encrypted_e.setText(encrypted)
1749 except Exception as e:
1750 self.show_message(str(e))
1754 def encrypt_message(self, address = ''):
1757 d.setWindowTitle(_('Encrypt/decrypt Message'))
1758 d.setMinimumSize(610, 490)
1760 layout = QGridLayout(d)
1762 message_e = QTextEdit()
1763 layout.addWidget(QLabel(_('Message')), 1, 0)
1764 layout.addWidget(message_e, 1, 1)
1765 layout.setRowStretch(2,3)
1767 pubkey_e = QLineEdit()
1769 pubkey = self.wallet.getpubkeys(address)[0]
1770 pubkey_e.setText(pubkey)
1771 layout.addWidget(QLabel(_('Public key')), 2, 0)
1772 layout.addWidget(pubkey_e, 2, 1)
1774 encrypted_e = QTextEdit()
1775 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1776 layout.addWidget(encrypted_e, 3, 1)
1777 layout.setRowStretch(3,1)
1779 hbox = QHBoxLayout()
1780 b = QPushButton(_("Encrypt"))
1781 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1784 b = QPushButton(_("Decrypt"))
1785 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1788 b = QPushButton(_("Close"))
1789 b.clicked.connect(d.accept)
1792 layout.addLayout(hbox, 4, 1)
1796 def question(self, msg):
1797 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1799 def show_message(self, msg):
1800 QMessageBox.information(self, _('Message'), msg, _('OK'))
1802 def password_dialog(self, msg=None):
1805 d.setWindowTitle(_("Enter Password"))
1810 vbox = QVBoxLayout()
1812 msg = _('Please enter your password')
1813 vbox.addWidget(QLabel(msg))
1815 grid = QGridLayout()
1817 grid.addWidget(QLabel(_('Password')), 1, 0)
1818 grid.addWidget(pw, 1, 1)
1819 vbox.addLayout(grid)
1821 vbox.addLayout(ok_cancel_buttons(d))
1824 run_hook('password_dialog', pw, grid, 1)
1825 if not d.exec_(): return
1826 return unicode(pw.text())
1835 def tx_from_text(self, txt):
1836 "json or raw hexadecimal"
1839 tx = Transaction(txt)
1845 tx_dict = json.loads(str(txt))
1846 assert "hex" in tx_dict.keys()
1847 tx = Transaction(tx_dict["hex"])
1848 if tx_dict.has_key("input_info"):
1849 input_info = json.loads(tx_dict['input_info'])
1850 tx.add_input_info(input_info)
1853 traceback.print_exc(file=sys.stdout)
1856 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1860 def read_tx_from_file(self):
1861 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1865 with open(fileName, "r") as f:
1866 file_content = f.read()
1867 except (ValueError, IOError, os.error), reason:
1868 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1870 return self.tx_from_text(file_content)
1874 def sign_raw_transaction(self, tx, input_info, password):
1875 self.wallet.signrawtransaction(tx, input_info, [], password)
1877 def do_process_from_text(self):
1878 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1881 tx = self.tx_from_text(text)
1883 self.show_transaction(tx)
1885 def do_process_from_file(self):
1886 tx = self.read_tx_from_file()
1888 self.show_transaction(tx)
1890 def do_process_from_txid(self):
1891 from electrum import transaction
1892 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1894 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1896 tx = transaction.Transaction(r)
1898 self.show_transaction(tx)
1900 self.show_message("unknown transaction")
1902 def do_process_from_csvReader(self, csvReader):
1907 for position, row in enumerate(csvReader):
1909 if not is_valid(address):
1910 errors.append((position, address))
1912 amount = Decimal(row[1])
1913 amount = int(100000000*amount)
1914 outputs.append((address, amount))
1915 except (ValueError, IOError, os.error), reason:
1916 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1920 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1921 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1925 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1926 except Exception as e:
1927 self.show_message(str(e))
1930 self.show_transaction(tx)
1932 def do_process_from_csv_file(self):
1933 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1937 with open(fileName, "r") as f:
1938 csvReader = csv.reader(f)
1939 self.do_process_from_csvReader(csvReader)
1940 except (ValueError, IOError, os.error), reason:
1941 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1944 def do_process_from_csv_text(self):
1945 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1946 + _("Format: address, amount. One output per line"), _("Load CSV"))
1949 f = StringIO.StringIO(text)
1950 csvReader = csv.reader(f)
1951 self.do_process_from_csvReader(csvReader)
1956 def export_privkeys_dialog(self, password):
1957 if self.wallet.is_watching_only():
1958 self.show_message(_("This is a watching-only wallet"))
1962 d.setWindowTitle(_('Private keys'))
1963 d.setMinimumSize(850, 300)
1964 vbox = QVBoxLayout(d)
1966 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1967 _("Exposing a single private key can compromise your entire wallet!"),
1968 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1969 vbox.addWidget(QLabel(msg))
1975 defaultname = 'electrum-private-keys.csv'
1976 select_msg = _('Select file to export your private keys to')
1977 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1978 vbox.addLayout(hbox)
1980 h, b = ok_cancel_buttons2(d, _('Export'))
1985 addresses = self.wallet.addresses(True)
1987 def privkeys_thread():
1988 for addr in addresses:
1992 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1993 d.emit(SIGNAL('computing_privkeys'))
1994 d.emit(SIGNAL('show_privkeys'))
1996 def show_privkeys():
1997 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
2001 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
2002 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
2003 threading.Thread(target=privkeys_thread).start()
2009 filename = filename_e.text()
2014 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
2015 except (IOError, os.error), reason:
2016 export_error_label = _("Electrum was unable to produce a private key-export.")
2017 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2019 except Exception as e:
2020 self.show_message(str(e))
2023 self.show_message(_("Private keys exported."))
2026 def do_export_privkeys(self, fileName, pklist, is_csv):
2027 with open(fileName, "w+") as f:
2029 transaction = csv.writer(f)
2030 transaction.writerow(["address", "private_key"])
2031 for addr, pk in pklist.items():
2032 transaction.writerow(["%34s"%addr,pk])
2035 f.write(json.dumps(pklist, indent = 4))
2038 def do_import_labels(self):
2039 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2040 if not labelsFile: return
2042 f = open(labelsFile, 'r')
2045 for key, value in json.loads(data).items():
2046 self.wallet.set_label(key, value)
2047 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2048 except (IOError, os.error), reason:
2049 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2052 def do_export_labels(self):
2053 labels = self.wallet.labels
2055 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2057 with open(fileName, 'w+') as f:
2058 json.dump(labels, f)
2059 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2060 except (IOError, os.error), reason:
2061 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2064 def export_history_dialog(self):
2067 d.setWindowTitle(_('Export History'))
2068 d.setMinimumSize(400, 200)
2069 vbox = QVBoxLayout(d)
2071 defaultname = os.path.expanduser('~/electrum-history.csv')
2072 select_msg = _('Select file to export your wallet transactions to')
2074 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2075 vbox.addLayout(hbox)
2079 h, b = ok_cancel_buttons2(d, _('Export'))
2084 filename = filename_e.text()
2089 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2090 except (IOError, os.error), reason:
2091 export_error_label = _("Electrum was unable to produce a transaction export.")
2092 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2095 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2098 def do_export_history(self, wallet, fileName, is_csv):
2099 history = wallet.get_tx_history()
2101 for item in history:
2102 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2104 if timestamp is not None:
2106 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2107 except [RuntimeError, TypeError, NameError] as reason:
2108 time_string = "unknown"
2111 time_string = "unknown"
2113 time_string = "pending"
2115 if value is not None:
2116 value_string = format_satoshis(value, True)
2121 fee_string = format_satoshis(fee, True)
2126 label, is_default_label = wallet.get_label(tx_hash)
2127 label = label.encode('utf-8')
2131 balance_string = format_satoshis(balance, False)
2133 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2135 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2137 with open(fileName, "w+") as f:
2139 transaction = csv.writer(f)
2140 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2142 transaction.writerow(line)
2145 f.write(json.dumps(lines, indent = 4))
2148 def sweep_key_dialog(self):
2150 d.setWindowTitle(_('Sweep private keys'))
2151 d.setMinimumSize(600, 300)
2153 vbox = QVBoxLayout(d)
2154 vbox.addWidget(QLabel(_("Enter private keys")))
2156 keys_e = QTextEdit()
2157 keys_e.setTabChangesFocus(True)
2158 vbox.addWidget(keys_e)
2160 h, address_e = address_field(self.wallet.addresses())
2164 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2165 vbox.addLayout(hbox)
2166 button.setEnabled(False)
2169 addr = str(address_e.text())
2170 if bitcoin.is_address(addr):
2174 pk = str(keys_e.toPlainText()).strip()
2175 if Wallet.is_private_key(pk):
2178 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2179 keys_e.textChanged.connect(f)
2180 address_e.textChanged.connect(f)
2184 fee = self.wallet.fee
2185 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2186 self.show_transaction(tx)
2190 def do_import_privkey(self, password):
2191 if not self.wallet.has_imported_keys():
2192 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2193 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2194 + _('Are you sure you understand what you are doing?'), 3, 4)
2197 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2200 text = str(text).split()
2205 addr = self.wallet.import_key(key, password)
2206 except Exception as e:
2212 addrlist.append(addr)
2214 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2216 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2217 self.update_receive_tab()
2218 self.update_history_tab()
2221 def settings_dialog(self):
2223 d.setWindowTitle(_('Electrum Settings'))
2225 vbox = QVBoxLayout()
2226 grid = QGridLayout()
2227 grid.setColumnStretch(0,1)
2229 nz_label = QLabel(_('Display zeros') + ':')
2230 grid.addWidget(nz_label, 0, 0)
2231 nz_e = AmountEdit(None,True)
2232 nz_e.setText("%d"% self.num_zeros)
2233 grid.addWidget(nz_e, 0, 1)
2234 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2235 grid.addWidget(HelpButton(msg), 0, 2)
2236 if not self.config.is_modifiable('num_zeros'):
2237 for w in [nz_e, nz_label]: w.setEnabled(False)
2239 lang_label=QLabel(_('Language') + ':')
2240 grid.addWidget(lang_label, 1, 0)
2241 lang_combo = QComboBox()
2242 from electrum.i18n import languages
2243 lang_combo.addItems(languages.values())
2245 index = languages.keys().index(self.config.get("language",''))
2248 lang_combo.setCurrentIndex(index)
2249 grid.addWidget(lang_combo, 1, 1)
2250 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2251 if not self.config.is_modifiable('language'):
2252 for w in [lang_combo, lang_label]: w.setEnabled(False)
2255 fee_label = QLabel(_('Transaction fee') + ':')
2256 grid.addWidget(fee_label, 2, 0)
2257 fee_e = BTCAmountEdit(self.get_decimal_point)
2258 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2259 grid.addWidget(fee_e, 2, 1)
2260 msg = _('Fee per kilobyte of transaction.') + ' ' \
2261 + _('Recommended value') + ': ' + self.format_amount(20000)
2262 grid.addWidget(HelpButton(msg), 2, 2)
2263 if not self.config.is_modifiable('fee_per_kb'):
2264 for w in [fee_e, fee_label]: w.setEnabled(False)
2266 units = ['BTC', 'mBTC']
2267 unit_label = QLabel(_('Base unit') + ':')
2268 grid.addWidget(unit_label, 3, 0)
2269 unit_combo = QComboBox()
2270 unit_combo.addItems(units)
2271 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2272 grid.addWidget(unit_combo, 3, 1)
2273 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2274 + '\n1BTC=1000mBTC.\n' \
2275 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2277 usechange_cb = QCheckBox(_('Use change addresses'))
2278 usechange_cb.setChecked(self.wallet.use_change)
2279 grid.addWidget(usechange_cb, 4, 0)
2280 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2281 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2283 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2284 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2285 grid.addWidget(block_ex_label, 5, 0)
2286 block_ex_combo = QComboBox()
2287 block_ex_combo.addItems(block_explorers)
2288 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2289 grid.addWidget(block_ex_combo, 5, 1)
2290 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2292 show_tx = self.config.get('show_before_broadcast', False)
2293 showtx_cb = QCheckBox(_('Show before broadcast'))
2294 showtx_cb.setChecked(show_tx)
2295 grid.addWidget(showtx_cb, 6, 0)
2296 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2298 vbox.addLayout(grid)
2300 vbox.addLayout(ok_cancel_buttons(d))
2304 if not d.exec_(): return
2307 fee = self.fee_e.get_amount()
2309 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2312 self.wallet.set_fee(fee)
2314 nz = unicode(nz_e.text())
2319 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2322 if self.num_zeros != nz:
2324 self.config.set_key('num_zeros', nz, True)
2325 self.update_history_tab()
2326 self.update_receive_tab()
2328 usechange_result = usechange_cb.isChecked()
2329 if self.wallet.use_change != usechange_result:
2330 self.wallet.use_change = usechange_result
2331 self.wallet.storage.put('use_change', self.wallet.use_change)
2333 if showtx_cb.isChecked() != show_tx:
2334 self.config.set_key('show_before_broadcast', not show_tx)
2336 unit_result = units[unit_combo.currentIndex()]
2337 if self.base_unit() != unit_result:
2338 self.decimal_point = 8 if unit_result == 'BTC' else 5
2339 self.config.set_key('decimal_point', self.decimal_point, True)
2340 self.update_history_tab()
2341 self.update_status()
2343 need_restart = False
2345 lang_request = languages.keys()[lang_combo.currentIndex()]
2346 if lang_request != self.config.get('language'):
2347 self.config.set_key("language", lang_request, True)
2350 be_result = block_explorers[block_ex_combo.currentIndex()]
2351 self.config.set_key('block_explorer', be_result, True)
2353 run_hook('close_settings_dialog')
2356 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2359 def run_network_dialog(self):
2360 if not self.network:
2362 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2364 def closeEvent(self, event):
2366 self.config.set_key("is_maximized", self.isMaximized())
2367 if not self.isMaximized():
2369 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2370 self.save_column_widths()
2371 self.config.set_key("console-history", self.console.history[-50:], True)
2372 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2376 def plugins_dialog(self):
2377 from electrum.plugins import plugins
2380 d.setWindowTitle(_('Electrum Plugins'))
2383 vbox = QVBoxLayout(d)
2386 scroll = QScrollArea()
2387 scroll.setEnabled(True)
2388 scroll.setWidgetResizable(True)
2389 scroll.setMinimumSize(400,250)
2390 vbox.addWidget(scroll)
2394 w.setMinimumHeight(len(plugins)*35)
2396 grid = QGridLayout()
2397 grid.setColumnStretch(0,1)
2400 def do_toggle(cb, p, w):
2403 if w: w.setEnabled(r)
2405 def mk_toggle(cb, p, w):
2406 return lambda: do_toggle(cb,p,w)
2408 for i, p in enumerate(plugins):
2410 cb = QCheckBox(p.fullname())
2411 cb.setDisabled(not p.is_available())
2412 cb.setChecked(p.is_enabled())
2413 grid.addWidget(cb, i, 0)
2414 if p.requires_settings():
2415 w = p.settings_widget(self)
2416 w.setEnabled( p.is_enabled() )
2417 grid.addWidget(w, i, 1)
2420 cb.clicked.connect(mk_toggle(cb,p,w))
2421 grid.addWidget(HelpButton(p.description()), i, 2)
2423 print_msg(_("Error: cannot display plugin"), p)
2424 traceback.print_exc(file=sys.stdout)
2425 grid.setRowStretch(i+1,1)
2427 vbox.addLayout(close_button(d))
2432 def show_account_details(self, k):
2433 account = self.wallet.accounts[k]
2436 d.setWindowTitle(_('Account Details'))
2439 vbox = QVBoxLayout(d)
2440 name = self.wallet.get_account_name(k)
2441 label = QLabel('Name: ' + name)
2442 vbox.addWidget(label)
2444 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2446 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2448 vbox.addWidget(QLabel(_('Master Public Key:')))
2451 text.setReadOnly(True)
2452 text.setMaximumHeight(170)
2453 vbox.addWidget(text)
2455 mpk_text = '\n'.join( account.get_master_pubkeys() )
2456 text.setText(mpk_text)
2458 vbox.addLayout(close_button(d))