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_memo(), 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'), _('Memo'),_('Amount'), _('Status')])
1055 h.setStretchLastSection(False)
1056 h.setResizeMode(1, QHeaderView.Stretch)
1057 l.setContextMenuPolicy(Qt.CustomContextMenu)
1058 l.customContextMenuRequested.connect(self.create_invoice_menu)
1059 self.invoices_list = l
1062 def update_invoices_tab(self):
1063 invoices = self.wallet.storage.get('invoices', {})
1064 l = self.invoices_list
1066 for key, value in invoices.items():
1068 domain, memo, amount = value
1072 item = QTreeWidgetItem( [ domain, memo, self.format_amount(amount), ""] )
1073 l.addTopLevelItem(item)
1075 l.setCurrentItem(l.topLevelItem(0))
1079 def delete_imported_key(self, addr):
1080 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1081 self.wallet.delete_imported_key(addr)
1082 self.update_receive_tab()
1083 self.update_history_tab()
1085 def edit_account_label(self, k):
1086 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1088 label = unicode(text)
1089 self.wallet.set_label(k,label)
1090 self.update_receive_tab()
1092 def account_set_expanded(self, item, k, b):
1094 self.accounts_expanded[k] = b
1096 def create_account_menu(self, position, k, item):
1098 if item.isExpanded():
1099 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1101 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1102 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1103 if self.wallet.seed_version > 4:
1104 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1105 if self.wallet.account_is_pending(k):
1106 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1107 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1109 def delete_pending_account(self, k):
1110 self.wallet.delete_pending_account(k)
1111 self.update_receive_tab()
1113 def create_receive_menu(self, position):
1114 # fixme: this function apparently has a side effect.
1115 # if it is not called the menu pops up several times
1116 #self.receive_list.selectedIndexes()
1118 selected = self.receive_list.selectedItems()
1119 multi_select = len(selected) > 1
1120 addrs = [unicode(item.text(0)) for item in selected]
1121 if not multi_select:
1122 item = self.receive_list.itemAt(position)
1126 if not is_valid(addr):
1127 k = str(item.data(0,32).toString())
1129 self.create_account_menu(position, k, item)
1131 item.setExpanded(not item.isExpanded())
1135 if not multi_select:
1136 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1137 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1138 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1139 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1140 if not self.wallet.is_watching_only():
1141 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1142 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1143 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1144 if self.wallet.is_imported(addr):
1145 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1147 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1148 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1149 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1150 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1152 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1153 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1155 run_hook('receive_menu', menu, addrs)
1156 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1159 def get_sendable_balance(self):
1160 return sum(map(lambda x:x['value'], self.get_coins()))
1163 def get_coins(self):
1165 return self.pay_from
1167 domain = self.wallet.get_account_addresses(self.current_account)
1168 for i in self.wallet.frozen_addresses:
1169 if i in domain: domain.remove(i)
1170 return self.wallet.get_unspent_coins(domain)
1173 def send_from_addresses(self, addrs):
1174 self.set_pay_from( addrs )
1175 self.tabs.setCurrentIndex(1)
1178 def payto(self, addr):
1180 label = self.wallet.labels.get(addr)
1181 m_addr = label + ' <' + addr + '>' if label else addr
1182 self.tabs.setCurrentIndex(1)
1183 self.payto_e.setText(m_addr)
1184 self.amount_e.setFocus()
1187 def delete_contact(self, x):
1188 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1189 self.wallet.delete_contact(x)
1190 self.wallet.set_label(x, None)
1191 self.update_history_tab()
1192 self.update_contacts_tab()
1193 self.update_completions()
1196 def create_contact_menu(self, position):
1197 item = self.contacts_list.itemAt(position)
1200 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1202 addr = unicode(item.text(0))
1203 label = unicode(item.text(1))
1204 is_editable = item.data(0,32).toBool()
1205 payto_addr = item.data(0,33).toString()
1206 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1207 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1208 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1210 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1211 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1213 run_hook('create_contact_menu', menu, item)
1214 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1216 def delete_invoice(self, item):
1217 self.invoices.pop(key)
1218 self.wallet.storage.put('invoices', self.invoices)
1219 self.update_invoices_tab()
1221 def show_invoice(self, key):
1222 from electrum.paymentrequest import PaymentRequest
1223 domain, memo, value = self.invoices[key]
1224 pr = PaymentRequest(self.config)
1228 self.show_pr_details(pr)
1230 def show_pr_details(self, pr):
1231 msg = 'Domain: ' + pr.domain
1232 msg += '\nStatus: ' + pr.get_status()
1233 msg += '\nMemo: ' + pr.memo
1234 msg += '\nPayment URL: ' + pr.payment_url
1235 msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[0] + ' ' + self.format_amount(x[1])+ self.base_unit(), pr.get_outputs()))
1236 QMessageBox.information(self, 'Invoice', msg , 'OK')
1238 def create_invoice_menu(self, position):
1239 item = self.invoices_list.itemAt(position)
1242 k = self.invoices_list.indexOfTopLevelItem(item)
1243 key = self.invoices.keys()[k]
1245 menu.addAction(_("Details"), lambda: self.show_invoice(key))
1246 menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
1247 menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
1250 def update_receive_item(self, item):
1251 item.setFont(0, QFont(MONOSPACE_FONT))
1252 address = str(item.data(0,0).toString())
1253 label = self.wallet.labels.get(address,'')
1254 item.setData(1,0,label)
1255 item.setData(0,32, True) # is editable
1257 run_hook('update_receive_item', address, item)
1259 if not self.wallet.is_mine(address): return
1261 c, u = self.wallet.get_addr_balance(address)
1262 balance = self.format_amount(c + u)
1263 item.setData(2,0,balance)
1265 if address in self.wallet.frozen_addresses:
1266 item.setBackgroundColor(0, QColor('lightblue'))
1269 def update_receive_tab(self):
1270 l = self.receive_list
1271 # extend the syntax for consistency
1272 l.addChild = l.addTopLevelItem
1273 l.insertChild = l.insertTopLevelItem
1276 for i,width in enumerate(self.column_widths['receive']):
1277 l.setColumnWidth(i, width)
1279 accounts = self.wallet.get_accounts()
1280 if self.current_account is None:
1281 account_items = sorted(accounts.items())
1283 account_items = [(self.current_account, accounts.get(self.current_account))]
1286 for k, account in account_items:
1288 if len(accounts) > 1:
1289 name = self.wallet.get_account_name(k)
1290 c,u = self.wallet.get_account_balance(k)
1291 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1292 l.addTopLevelItem(account_item)
1293 account_item.setExpanded(self.accounts_expanded.get(k, True))
1294 account_item.setData(0, 32, k)
1298 sequences = [0,1] if account.has_change() else [0]
1299 for is_change in sequences:
1300 if len(sequences) > 1:
1301 name = _("Receiving") if not is_change else _("Change")
1302 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1303 account_item.addChild(seq_item)
1305 seq_item.setExpanded(True)
1307 seq_item = account_item
1309 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1315 for address in account.get_addresses(is_change):
1317 num, is_used = self.wallet.is_used(address)
1320 if gap > self.wallet.gap_limit:
1325 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1326 self.update_receive_item(item)
1328 item.setBackgroundColor(1, QColor('red'))
1332 seq_item.insertChild(0,used_item)
1334 used_item.addChild(item)
1336 seq_item.addChild(item)
1338 # we use column 1 because column 0 may be hidden
1339 l.setCurrentItem(l.topLevelItem(0),1)
1342 def update_contacts_tab(self):
1343 l = self.contacts_list
1346 for address in self.wallet.addressbook:
1347 label = self.wallet.labels.get(address,'')
1348 n = self.wallet.get_num_tx(address)
1349 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1350 item.setFont(0, QFont(MONOSPACE_FONT))
1351 # 32 = label can be edited (bool)
1352 item.setData(0,32, True)
1354 item.setData(0,33, address)
1355 l.addTopLevelItem(item)
1357 run_hook('update_contacts_tab', l)
1358 l.setCurrentItem(l.topLevelItem(0))
1362 def create_console_tab(self):
1363 from console import Console
1364 self.console = console = Console()
1368 def update_console(self):
1369 console = self.console
1370 console.history = self.config.get("console-history",[])
1371 console.history_index = len(console.history)
1373 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1374 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1376 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1378 def mkfunc(f, method):
1379 return lambda *args: apply( f, (method, args, self.password_dialog ))
1381 if m[0]=='_' or m in ['network','wallet']: continue
1382 methods[m] = mkfunc(c._run, m)
1384 console.updateNamespace(methods)
1387 def change_account(self,s):
1388 if s == _("All accounts"):
1389 self.current_account = None
1391 accounts = self.wallet.get_account_names()
1392 for k, v in accounts.items():
1394 self.current_account = k
1395 self.update_history_tab()
1396 self.update_status()
1397 self.update_receive_tab()
1399 def create_status_bar(self):
1402 sb.setFixedHeight(35)
1403 qtVersion = qVersion()
1405 self.balance_label = QLabel("")
1406 sb.addWidget(self.balance_label)
1408 from version_getter import UpdateLabel
1409 self.updatelabel = UpdateLabel(self.config, sb)
1411 self.account_selector = QComboBox()
1412 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1413 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1414 sb.addPermanentWidget(self.account_selector)
1416 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1417 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1419 self.lock_icon = QIcon()
1420 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1421 sb.addPermanentWidget( self.password_button )
1423 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1424 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1425 sb.addPermanentWidget( self.seed_button )
1426 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1427 sb.addPermanentWidget( self.status_button )
1429 run_hook('create_status_bar', (sb,))
1431 self.setStatusBar(sb)
1434 def update_lock_icon(self):
1435 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1436 self.password_button.setIcon( icon )
1439 def update_buttons_on_seed(self):
1440 if self.wallet.has_seed():
1441 self.seed_button.show()
1443 self.seed_button.hide()
1445 if not self.wallet.is_watching_only():
1446 self.password_button.show()
1447 self.send_button.setText(_("Send"))
1449 self.password_button.hide()
1450 self.send_button.setText(_("Create unsigned transaction"))
1453 def change_password_dialog(self):
1454 from password_dialog import PasswordDialog
1455 d = PasswordDialog(self.wallet, self)
1457 self.update_lock_icon()
1460 def new_contact_dialog(self):
1463 d.setWindowTitle(_("New Contact"))
1464 vbox = QVBoxLayout(d)
1465 vbox.addWidget(QLabel(_('New Contact')+':'))
1467 grid = QGridLayout()
1470 grid.addWidget(QLabel(_("Address")), 1, 0)
1471 grid.addWidget(line1, 1, 1)
1472 grid.addWidget(QLabel(_("Name")), 2, 0)
1473 grid.addWidget(line2, 2, 1)
1475 vbox.addLayout(grid)
1476 vbox.addLayout(ok_cancel_buttons(d))
1481 address = str(line1.text())
1482 label = unicode(line2.text())
1484 if not is_valid(address):
1485 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1488 self.wallet.add_contact(address)
1490 self.wallet.set_label(address, label)
1492 self.update_contacts_tab()
1493 self.update_history_tab()
1494 self.update_completions()
1495 self.tabs.setCurrentIndex(3)
1499 def new_account_dialog(self, password):
1501 dialog = QDialog(self)
1503 dialog.setWindowTitle(_("New Account"))
1505 vbox = QVBoxLayout()
1506 vbox.addWidget(QLabel(_('Account name')+':'))
1509 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1510 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1515 vbox.addLayout(ok_cancel_buttons(dialog))
1516 dialog.setLayout(vbox)
1520 name = str(e.text())
1523 self.wallet.create_pending_account(name, password)
1524 self.update_receive_tab()
1525 self.tabs.setCurrentIndex(2)
1530 def show_master_public_keys(self):
1532 dialog = QDialog(self)
1534 dialog.setWindowTitle(_("Master Public Keys"))
1536 main_layout = QGridLayout()
1537 mpk_dict = self.wallet.get_master_public_keys()
1539 for key, value in mpk_dict.items():
1540 main_layout.addWidget(QLabel(key), i, 0)
1541 mpk_text = QTextEdit()
1542 mpk_text.setReadOnly(True)
1543 mpk_text.setMaximumHeight(170)
1544 mpk_text.setText(value)
1545 main_layout.addWidget(mpk_text, i + 1, 0)
1548 vbox = QVBoxLayout()
1549 vbox.addLayout(main_layout)
1550 vbox.addLayout(close_button(dialog))
1552 dialog.setLayout(vbox)
1557 def show_seed_dialog(self, password):
1558 if not self.wallet.has_seed():
1559 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1563 mnemonic = self.wallet.get_mnemonic(password)
1565 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1567 from seed_dialog import SeedDialog
1568 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1573 def show_qrcode(self, data, title = _("QR code")):
1577 d.setWindowTitle(title)
1578 d.setMinimumSize(270, 300)
1579 vbox = QVBoxLayout()
1580 qrw = QRCodeWidget(data)
1581 vbox.addWidget(qrw, 1)
1582 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1583 hbox = QHBoxLayout()
1586 filename = os.path.join(self.config.path, "qrcode.bmp")
1589 bmp.save_qrcode(qrw.qr, filename)
1590 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1592 def copy_to_clipboard():
1593 bmp.save_qrcode(qrw.qr, filename)
1594 self.app.clipboard().setImage(QImage(filename))
1595 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1597 b = QPushButton(_("Copy"))
1599 b.clicked.connect(copy_to_clipboard)
1601 b = QPushButton(_("Save"))
1603 b.clicked.connect(print_qr)
1605 b = QPushButton(_("Close"))
1607 b.clicked.connect(d.accept)
1610 vbox.addLayout(hbox)
1615 def do_protect(self, func, args):
1616 if self.wallet.use_encryption:
1617 password = self.password_dialog()
1623 if args != (False,):
1624 args = (self,) + args + (password,)
1626 args = (self,password)
1630 def show_public_keys(self, address):
1631 if not address: return
1633 pubkey_list = self.wallet.get_public_keys(address)
1634 except Exception as e:
1635 traceback.print_exc(file=sys.stdout)
1636 self.show_message(str(e))
1640 d.setMinimumSize(600, 200)
1642 vbox = QVBoxLayout()
1643 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1644 vbox.addWidget( QLabel(_("Public key") + ':'))
1646 keys.setReadOnly(True)
1647 keys.setText('\n'.join(pubkey_list))
1648 vbox.addWidget(keys)
1649 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1650 vbox.addLayout(close_button(d))
1655 def show_private_key(self, address, password):
1656 if not address: return
1658 pk_list = self.wallet.get_private_key(address, password)
1659 except Exception as e:
1660 traceback.print_exc(file=sys.stdout)
1661 self.show_message(str(e))
1665 d.setMinimumSize(600, 200)
1667 vbox = QVBoxLayout()
1668 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1669 vbox.addWidget( QLabel(_("Private key") + ':'))
1671 keys.setReadOnly(True)
1672 keys.setText('\n'.join(pk_list))
1673 vbox.addWidget(keys)
1674 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1675 vbox.addLayout(close_button(d))
1681 def do_sign(self, address, message, signature, password):
1682 message = unicode(message.toPlainText())
1683 message = message.encode('utf-8')
1685 sig = self.wallet.sign_message(str(address.text()), message, password)
1686 signature.setText(sig)
1687 except Exception as e:
1688 self.show_message(str(e))
1690 def do_verify(self, address, message, signature):
1691 message = unicode(message.toPlainText())
1692 message = message.encode('utf-8')
1693 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1694 self.show_message(_("Signature verified"))
1696 self.show_message(_("Error: wrong signature"))
1699 def sign_verify_message(self, address=''):
1702 d.setWindowTitle(_('Sign/verify Message'))
1703 d.setMinimumSize(410, 290)
1705 layout = QGridLayout(d)
1707 message_e = QTextEdit()
1708 layout.addWidget(QLabel(_('Message')), 1, 0)
1709 layout.addWidget(message_e, 1, 1)
1710 layout.setRowStretch(2,3)
1712 address_e = QLineEdit()
1713 address_e.setText(address)
1714 layout.addWidget(QLabel(_('Address')), 2, 0)
1715 layout.addWidget(address_e, 2, 1)
1717 signature_e = QTextEdit()
1718 layout.addWidget(QLabel(_('Signature')), 3, 0)
1719 layout.addWidget(signature_e, 3, 1)
1720 layout.setRowStretch(3,1)
1722 hbox = QHBoxLayout()
1724 b = QPushButton(_("Sign"))
1725 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1728 b = QPushButton(_("Verify"))
1729 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1732 b = QPushButton(_("Close"))
1733 b.clicked.connect(d.accept)
1735 layout.addLayout(hbox, 4, 1)
1740 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1742 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1743 message_e.setText(decrypted)
1744 except Exception as e:
1745 self.show_message(str(e))
1748 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1749 message = unicode(message_e.toPlainText())
1750 message = message.encode('utf-8')
1752 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1753 encrypted_e.setText(encrypted)
1754 except Exception as e:
1755 self.show_message(str(e))
1759 def encrypt_message(self, address = ''):
1762 d.setWindowTitle(_('Encrypt/decrypt Message'))
1763 d.setMinimumSize(610, 490)
1765 layout = QGridLayout(d)
1767 message_e = QTextEdit()
1768 layout.addWidget(QLabel(_('Message')), 1, 0)
1769 layout.addWidget(message_e, 1, 1)
1770 layout.setRowStretch(2,3)
1772 pubkey_e = QLineEdit()
1774 pubkey = self.wallet.getpubkeys(address)[0]
1775 pubkey_e.setText(pubkey)
1776 layout.addWidget(QLabel(_('Public key')), 2, 0)
1777 layout.addWidget(pubkey_e, 2, 1)
1779 encrypted_e = QTextEdit()
1780 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1781 layout.addWidget(encrypted_e, 3, 1)
1782 layout.setRowStretch(3,1)
1784 hbox = QHBoxLayout()
1785 b = QPushButton(_("Encrypt"))
1786 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1789 b = QPushButton(_("Decrypt"))
1790 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1793 b = QPushButton(_("Close"))
1794 b.clicked.connect(d.accept)
1797 layout.addLayout(hbox, 4, 1)
1801 def question(self, msg):
1802 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1804 def show_message(self, msg):
1805 QMessageBox.information(self, _('Message'), msg, _('OK'))
1807 def password_dialog(self, msg=None):
1810 d.setWindowTitle(_("Enter Password"))
1815 vbox = QVBoxLayout()
1817 msg = _('Please enter your password')
1818 vbox.addWidget(QLabel(msg))
1820 grid = QGridLayout()
1822 grid.addWidget(QLabel(_('Password')), 1, 0)
1823 grid.addWidget(pw, 1, 1)
1824 vbox.addLayout(grid)
1826 vbox.addLayout(ok_cancel_buttons(d))
1829 run_hook('password_dialog', pw, grid, 1)
1830 if not d.exec_(): return
1831 return unicode(pw.text())
1840 def tx_from_text(self, txt):
1841 "json or raw hexadecimal"
1844 tx = Transaction(txt)
1850 tx_dict = json.loads(str(txt))
1851 assert "hex" in tx_dict.keys()
1852 tx = Transaction(tx_dict["hex"])
1853 if tx_dict.has_key("input_info"):
1854 input_info = json.loads(tx_dict['input_info'])
1855 tx.add_input_info(input_info)
1858 traceback.print_exc(file=sys.stdout)
1861 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1865 def read_tx_from_file(self):
1866 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1870 with open(fileName, "r") as f:
1871 file_content = f.read()
1872 except (ValueError, IOError, os.error), reason:
1873 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1875 return self.tx_from_text(file_content)
1879 def sign_raw_transaction(self, tx, input_info, password):
1880 self.wallet.signrawtransaction(tx, input_info, [], password)
1882 def do_process_from_text(self):
1883 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1886 tx = self.tx_from_text(text)
1888 self.show_transaction(tx)
1890 def do_process_from_file(self):
1891 tx = self.read_tx_from_file()
1893 self.show_transaction(tx)
1895 def do_process_from_txid(self):
1896 from electrum import transaction
1897 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1899 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1901 tx = transaction.Transaction(r)
1903 self.show_transaction(tx)
1905 self.show_message("unknown transaction")
1907 def do_process_from_csvReader(self, csvReader):
1912 for position, row in enumerate(csvReader):
1914 if not is_valid(address):
1915 errors.append((position, address))
1917 amount = Decimal(row[1])
1918 amount = int(100000000*amount)
1919 outputs.append((address, amount))
1920 except (ValueError, IOError, os.error), reason:
1921 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1925 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1926 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1930 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1931 except Exception as e:
1932 self.show_message(str(e))
1935 self.show_transaction(tx)
1937 def do_process_from_csv_file(self):
1938 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1942 with open(fileName, "r") as f:
1943 csvReader = csv.reader(f)
1944 self.do_process_from_csvReader(csvReader)
1945 except (ValueError, IOError, os.error), reason:
1946 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1949 def do_process_from_csv_text(self):
1950 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1951 + _("Format: address, amount. One output per line"), _("Load CSV"))
1954 f = StringIO.StringIO(text)
1955 csvReader = csv.reader(f)
1956 self.do_process_from_csvReader(csvReader)
1961 def export_privkeys_dialog(self, password):
1962 if self.wallet.is_watching_only():
1963 self.show_message(_("This is a watching-only wallet"))
1967 d.setWindowTitle(_('Private keys'))
1968 d.setMinimumSize(850, 300)
1969 vbox = QVBoxLayout(d)
1971 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1972 _("Exposing a single private key can compromise your entire wallet!"),
1973 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1974 vbox.addWidget(QLabel(msg))
1980 defaultname = 'electrum-private-keys.csv'
1981 select_msg = _('Select file to export your private keys to')
1982 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1983 vbox.addLayout(hbox)
1985 h, b = ok_cancel_buttons2(d, _('Export'))
1990 addresses = self.wallet.addresses(True)
1992 def privkeys_thread():
1993 for addr in addresses:
1997 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1998 d.emit(SIGNAL('computing_privkeys'))
1999 d.emit(SIGNAL('show_privkeys'))
2001 def show_privkeys():
2002 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
2006 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
2007 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
2008 threading.Thread(target=privkeys_thread).start()
2014 filename = filename_e.text()
2019 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
2020 except (IOError, os.error), reason:
2021 export_error_label = _("Electrum was unable to produce a private key-export.")
2022 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2024 except Exception as e:
2025 self.show_message(str(e))
2028 self.show_message(_("Private keys exported."))
2031 def do_export_privkeys(self, fileName, pklist, is_csv):
2032 with open(fileName, "w+") as f:
2034 transaction = csv.writer(f)
2035 transaction.writerow(["address", "private_key"])
2036 for addr, pk in pklist.items():
2037 transaction.writerow(["%34s"%addr,pk])
2040 f.write(json.dumps(pklist, indent = 4))
2043 def do_import_labels(self):
2044 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2045 if not labelsFile: return
2047 f = open(labelsFile, 'r')
2050 for key, value in json.loads(data).items():
2051 self.wallet.set_label(key, value)
2052 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2053 except (IOError, os.error), reason:
2054 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2057 def do_export_labels(self):
2058 labels = self.wallet.labels
2060 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2062 with open(fileName, 'w+') as f:
2063 json.dump(labels, f)
2064 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2065 except (IOError, os.error), reason:
2066 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2069 def export_history_dialog(self):
2072 d.setWindowTitle(_('Export History'))
2073 d.setMinimumSize(400, 200)
2074 vbox = QVBoxLayout(d)
2076 defaultname = os.path.expanduser('~/electrum-history.csv')
2077 select_msg = _('Select file to export your wallet transactions to')
2079 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2080 vbox.addLayout(hbox)
2084 h, b = ok_cancel_buttons2(d, _('Export'))
2089 filename = filename_e.text()
2094 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2095 except (IOError, os.error), reason:
2096 export_error_label = _("Electrum was unable to produce a transaction export.")
2097 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2100 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2103 def do_export_history(self, wallet, fileName, is_csv):
2104 history = wallet.get_tx_history()
2106 for item in history:
2107 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2109 if timestamp is not None:
2111 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2112 except [RuntimeError, TypeError, NameError] as reason:
2113 time_string = "unknown"
2116 time_string = "unknown"
2118 time_string = "pending"
2120 if value is not None:
2121 value_string = format_satoshis(value, True)
2126 fee_string = format_satoshis(fee, True)
2131 label, is_default_label = wallet.get_label(tx_hash)
2132 label = label.encode('utf-8')
2136 balance_string = format_satoshis(balance, False)
2138 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2140 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2142 with open(fileName, "w+") as f:
2144 transaction = csv.writer(f)
2145 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2147 transaction.writerow(line)
2150 f.write(json.dumps(lines, indent = 4))
2153 def sweep_key_dialog(self):
2155 d.setWindowTitle(_('Sweep private keys'))
2156 d.setMinimumSize(600, 300)
2158 vbox = QVBoxLayout(d)
2159 vbox.addWidget(QLabel(_("Enter private keys")))
2161 keys_e = QTextEdit()
2162 keys_e.setTabChangesFocus(True)
2163 vbox.addWidget(keys_e)
2165 h, address_e = address_field(self.wallet.addresses())
2169 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2170 vbox.addLayout(hbox)
2171 button.setEnabled(False)
2174 addr = str(address_e.text())
2175 if bitcoin.is_address(addr):
2179 pk = str(keys_e.toPlainText()).strip()
2180 if Wallet.is_private_key(pk):
2183 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2184 keys_e.textChanged.connect(f)
2185 address_e.textChanged.connect(f)
2189 fee = self.wallet.fee
2190 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2191 self.show_transaction(tx)
2195 def do_import_privkey(self, password):
2196 if not self.wallet.has_imported_keys():
2197 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2198 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2199 + _('Are you sure you understand what you are doing?'), 3, 4)
2202 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2205 text = str(text).split()
2210 addr = self.wallet.import_key(key, password)
2211 except Exception as e:
2217 addrlist.append(addr)
2219 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2221 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2222 self.update_receive_tab()
2223 self.update_history_tab()
2226 def settings_dialog(self):
2228 d.setWindowTitle(_('Electrum Settings'))
2230 vbox = QVBoxLayout()
2231 grid = QGridLayout()
2232 grid.setColumnStretch(0,1)
2234 nz_label = QLabel(_('Display zeros') + ':')
2235 grid.addWidget(nz_label, 0, 0)
2236 nz_e = AmountEdit(None,True)
2237 nz_e.setText("%d"% self.num_zeros)
2238 grid.addWidget(nz_e, 0, 1)
2239 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2240 grid.addWidget(HelpButton(msg), 0, 2)
2241 if not self.config.is_modifiable('num_zeros'):
2242 for w in [nz_e, nz_label]: w.setEnabled(False)
2244 lang_label=QLabel(_('Language') + ':')
2245 grid.addWidget(lang_label, 1, 0)
2246 lang_combo = QComboBox()
2247 from electrum.i18n import languages
2248 lang_combo.addItems(languages.values())
2250 index = languages.keys().index(self.config.get("language",''))
2253 lang_combo.setCurrentIndex(index)
2254 grid.addWidget(lang_combo, 1, 1)
2255 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2256 if not self.config.is_modifiable('language'):
2257 for w in [lang_combo, lang_label]: w.setEnabled(False)
2260 fee_label = QLabel(_('Transaction fee') + ':')
2261 grid.addWidget(fee_label, 2, 0)
2262 fee_e = BTCAmountEdit(self.get_decimal_point)
2263 fee_e.setAmount(self.wallet.fee)
2264 grid.addWidget(fee_e, 2, 1)
2265 msg = _('Fee per kilobyte of transaction.') + '\n' \
2266 + _('Recommended value') + ': ' + self.format_amount(10000) + ' ' + self.base_unit()
2267 grid.addWidget(HelpButton(msg), 2, 2)
2268 if not self.config.is_modifiable('fee_per_kb'):
2269 for w in [fee_e, fee_label]: w.setEnabled(False)
2271 units = ['BTC', 'mBTC']
2272 unit_label = QLabel(_('Base unit') + ':')
2273 grid.addWidget(unit_label, 3, 0)
2274 unit_combo = QComboBox()
2275 unit_combo.addItems(units)
2276 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2277 grid.addWidget(unit_combo, 3, 1)
2278 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2279 + '\n1BTC=1000mBTC.\n' \
2280 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2282 usechange_cb = QCheckBox(_('Use change addresses'))
2283 usechange_cb.setChecked(self.wallet.use_change)
2284 grid.addWidget(usechange_cb, 4, 0)
2285 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2286 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2288 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2289 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2290 grid.addWidget(block_ex_label, 5, 0)
2291 block_ex_combo = QComboBox()
2292 block_ex_combo.addItems(block_explorers)
2293 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2294 grid.addWidget(block_ex_combo, 5, 1)
2295 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2297 show_tx = self.config.get('show_before_broadcast', False)
2298 showtx_cb = QCheckBox(_('Show before broadcast'))
2299 showtx_cb.setChecked(show_tx)
2300 grid.addWidget(showtx_cb, 6, 0)
2301 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2303 vbox.addLayout(grid)
2305 vbox.addLayout(ok_cancel_buttons(d))
2309 if not d.exec_(): return
2311 fee = fee_e.get_amount()
2313 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2316 self.wallet.set_fee(fee)
2318 nz = unicode(nz_e.text())
2323 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2326 if self.num_zeros != nz:
2328 self.config.set_key('num_zeros', nz, True)
2329 self.update_history_tab()
2330 self.update_receive_tab()
2332 usechange_result = usechange_cb.isChecked()
2333 if self.wallet.use_change != usechange_result:
2334 self.wallet.use_change = usechange_result
2335 self.wallet.storage.put('use_change', self.wallet.use_change)
2337 if showtx_cb.isChecked() != show_tx:
2338 self.config.set_key('show_before_broadcast', not show_tx)
2340 unit_result = units[unit_combo.currentIndex()]
2341 if self.base_unit() != unit_result:
2342 self.decimal_point = 8 if unit_result == 'BTC' else 5
2343 self.config.set_key('decimal_point', self.decimal_point, True)
2344 self.update_history_tab()
2345 self.update_status()
2347 need_restart = False
2349 lang_request = languages.keys()[lang_combo.currentIndex()]
2350 if lang_request != self.config.get('language'):
2351 self.config.set_key("language", lang_request, True)
2354 be_result = block_explorers[block_ex_combo.currentIndex()]
2355 self.config.set_key('block_explorer', be_result, True)
2357 run_hook('close_settings_dialog')
2360 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2363 def run_network_dialog(self):
2364 if not self.network:
2366 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2368 def closeEvent(self, event):
2370 self.config.set_key("is_maximized", self.isMaximized())
2371 if not self.isMaximized():
2373 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2374 self.save_column_widths()
2375 self.config.set_key("console-history", self.console.history[-50:], True)
2376 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2380 def plugins_dialog(self):
2381 from electrum.plugins import plugins
2384 d.setWindowTitle(_('Electrum Plugins'))
2387 vbox = QVBoxLayout(d)
2390 scroll = QScrollArea()
2391 scroll.setEnabled(True)
2392 scroll.setWidgetResizable(True)
2393 scroll.setMinimumSize(400,250)
2394 vbox.addWidget(scroll)
2398 w.setMinimumHeight(len(plugins)*35)
2400 grid = QGridLayout()
2401 grid.setColumnStretch(0,1)
2404 def do_toggle(cb, p, w):
2407 if w: w.setEnabled(r)
2409 def mk_toggle(cb, p, w):
2410 return lambda: do_toggle(cb,p,w)
2412 for i, p in enumerate(plugins):
2414 cb = QCheckBox(p.fullname())
2415 cb.setDisabled(not p.is_available())
2416 cb.setChecked(p.is_enabled())
2417 grid.addWidget(cb, i, 0)
2418 if p.requires_settings():
2419 w = p.settings_widget(self)
2420 w.setEnabled( p.is_enabled() )
2421 grid.addWidget(w, i, 1)
2424 cb.clicked.connect(mk_toggle(cb,p,w))
2425 grid.addWidget(HelpButton(p.description()), i, 2)
2427 print_msg(_("Error: cannot display plugin"), p)
2428 traceback.print_exc(file=sys.stdout)
2429 grid.setRowStretch(i+1,1)
2431 vbox.addLayout(close_button(d))
2436 def show_account_details(self, k):
2437 account = self.wallet.accounts[k]
2440 d.setWindowTitle(_('Account Details'))
2443 vbox = QVBoxLayout(d)
2444 name = self.wallet.get_account_name(k)
2445 label = QLabel('Name: ' + name)
2446 vbox.addWidget(label)
2448 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2450 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2452 vbox.addWidget(QLabel(_('Master Public Key:')))
2455 text.setReadOnly(True)
2456 text.setMaximumHeight(170)
2457 vbox.addWidget(text)
2459 mpk_text = '\n'.join( account.get_master_pubkeys() )
2460 text.setText(mpk_text)
2462 vbox.addLayout(close_button(d))