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 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 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 run_hook('exchange_rate_button', grid)
699 self.send_button = EnterButton(_("Send"), self.do_send)
700 grid.addWidget(self.send_button, 6, 1)
702 b = EnterButton(_("Clear"), self.do_clear)
703 grid.addWidget(b, 6, 2)
705 self.payto_sig = QLabel('')
706 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
708 #QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
709 #QShortcut(QKeySequence("Down"), w, w.focusNextChild)
712 def entry_changed( is_fee ):
713 self.funds_error = False
715 if self.amount_e.is_shortcut:
716 self.amount_e.is_shortcut = False
717 sendable = self.get_sendable_balance()
718 # there is only one output because we are completely spending inputs
719 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, coins = self.get_coins())
720 fee = self.wallet.estimated_fee(inputs, 1)
722 self.amount_e.setText( self.format_amount(amount) )
723 self.fee_e.setText( self.format_amount( fee ) )
726 amount = self.amount_e.get_amount()
727 fee = self.fee_e.get_amount()
729 if not is_fee: fee = None
732 # assume that there will be 2 outputs (one for change)
733 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, coins = self.get_coins())
735 self.fee_e.setText( self.format_amount( fee ) )
738 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
742 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
743 self.funds_error = True
744 text = _( "Not enough funds" )
745 c, u = self.wallet.get_frozen_balance()
746 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
748 self.statusBar().showMessage(text)
749 self.amount_e.setPalette(palette)
750 self.fee_e.setPalette(palette)
752 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
753 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
755 run_hook('create_send_tab', grid)
758 def from_list_delete(self, item):
759 i = self.from_list.indexOfTopLevelItem(item)
761 self.redraw_from_list()
763 def from_list_menu(self, position):
764 item = self.from_list.itemAt(position)
766 menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
767 menu.exec_(self.from_list.viewport().mapToGlobal(position))
769 def set_pay_from(self, domain = None):
770 self.pay_from = [] if domain == [] else self.wallet.get_unspent_coins(domain)
771 self.redraw_from_list()
773 def redraw_from_list(self):
774 self.from_list.clear()
775 self.from_label.setHidden(len(self.pay_from) == 0)
776 self.from_list.setHidden(len(self.pay_from) == 0)
779 h = x.get('prevout_hash')
780 return h[0:8] + '...' + h[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
782 for item in self.pay_from:
783 self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
785 def update_completions(self):
787 for addr,label in self.wallet.labels.items():
788 if addr in self.wallet.addressbook:
789 l.append( label + ' <' + addr + '>')
791 run_hook('update_completions', l)
792 self.completions.setStringList(l)
796 return lambda s, *args: s.do_protect(func, args)
800 label = unicode( self.message_e.text() )
802 if self.gui_object.payment_request:
803 outputs = self.gui_object.payment_request.outputs
805 outputs = self.payto_e.get_outputs()
808 QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
811 for addr, x in outputs:
812 if addr is None or not bitcoin.is_address(addr):
813 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address'), _('OK'))
816 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
819 amount = sum(map(lambda x:x[1], outputs))
822 fee = self.fee_e.get_amount()
824 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
827 confirm_amount = self.config.get('confirm_amount', 100000000)
828 if amount >= confirm_amount:
829 o = '\n'.join(map(lambda x:x[0], outputs))
830 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
833 confirm_fee = self.config.get('confirm_fee', 100000)
834 if fee >= confirm_fee:
835 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()}):
838 self.send_tx(outputs, fee, label)
843 def send_tx(self, outputs, fee, label, password):
844 self.send_button.setDisabled(True)
846 # first, create an unsigned tx
847 coins = self.get_coins()
849 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
851 except Exception as e:
852 traceback.print_exc(file=sys.stdout)
853 self.show_message(str(e))
854 self.send_button.setDisabled(False)
857 # call hook to see if plugin needs gui interaction
858 run_hook('send_tx', tx)
864 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
865 self.wallet.sign_transaction(tx, keypairs, password)
866 return tx, fee, label
868 def sign_done(tx, fee, label):
870 self.show_message(tx.error)
871 self.send_button.setDisabled(False)
873 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
874 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
875 self.send_button.setDisabled(False)
878 self.wallet.set_label(tx.hash(), label)
880 if not tx.is_complete() or self.config.get('show_before_broadcast'):
881 self.show_transaction(tx)
883 self.send_button.setDisabled(False)
886 self.broadcast_transaction(tx)
888 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
889 self.waiting_dialog.start()
893 def broadcast_transaction(self, tx):
895 def broadcast_thread():
896 if self.gui_object.payment_request:
897 refund_address = self.wallet.addresses()[0]
898 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
899 self.gui_object.payment_request = None
901 status, msg = self.wallet.sendtx(tx)
904 def broadcast_done(status, msg):
906 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
909 QMessageBox.warning(self, _('Error'), msg, _('OK'))
910 self.send_button.setDisabled(False)
912 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
913 self.waiting_dialog.start()
917 def prepare_for_payment_request(self):
918 self.tabs.setCurrentIndex(1)
919 self.payto_e.is_pr = True
920 for e in [self.payto_e, self.amount_e, self.message_e]:
922 for h in [self.payto_help, self.amount_help, self.message_help]:
924 self.payto_e.setText(_("please wait..."))
927 def payment_request_ok(self):
928 pr = self.gui_object.payment_request
931 self.invoices[pr_id] = (pr.get_domain(), pr.get_amount())
932 self.wallet.storage.put('invoices', self.invoices)
933 self.update_invoices_tab()
935 self.payto_help.show()
936 self.payto_help.set_alt(lambda: self.show_pr_details(pr))
938 self.payto_e.setGreen()
939 self.payto_e.setText(pr.domain)
940 self.amount_e.setText(self.format_amount(pr.get_amount()))
941 self.message_e.setText(pr.memo)
943 def payment_request_error(self):
945 self.show_message(self.gui_object.payment_request.error)
946 self.gui_object.payment_request = None
948 def set_send(self, address, amount, label, message):
950 if label and self.wallet.labels.get(address) != label:
951 if self.question('Give label "%s" to address %s ?'%(label,address)):
952 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
953 self.wallet.addressbook.append(address)
954 self.wallet.set_label(address, label)
956 self.tabs.setCurrentIndex(1)
957 label = self.wallet.labels.get(address)
958 m_addr = label + ' <'+ address +'>' if label else address
959 self.payto_e.setText(m_addr)
961 self.message_e.setText(message)
963 self.amount_e.setText(amount)
967 self.payto_e.is_pr = False
968 self.payto_sig.setVisible(False)
969 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
973 for h in [self.payto_help, self.amount_help, self.message_help]:
976 self.payto_help.set_alt(None)
977 self.set_pay_from([])
982 def set_addrs_frozen(self,addrs,freeze):
984 if not addr: continue
985 if addr in self.wallet.frozen_addresses and not freeze:
986 self.wallet.unfreeze(addr)
987 elif addr not in self.wallet.frozen_addresses and freeze:
988 self.wallet.freeze(addr)
989 self.update_receive_tab()
993 def create_list_tab(self, headers):
994 "generic tab creation method"
995 l = MyTreeWidget(self)
996 l.setColumnCount( len(headers) )
997 l.setHeaderLabels( headers )
1000 vbox = QVBoxLayout()
1007 vbox.addWidget(buttons)
1012 def create_receive_tab(self):
1013 l, w = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1014 l.setContextMenuPolicy(Qt.CustomContextMenu)
1015 l.customContextMenuRequested.connect(self.create_receive_menu)
1016 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1017 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1018 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1019 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1020 self.receive_list = l
1026 def save_column_widths(self):
1027 self.column_widths["receive"] = []
1028 for i in range(self.receive_list.columnCount() -1):
1029 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1031 self.column_widths["history"] = []
1032 for i in range(self.history_list.columnCount() - 1):
1033 self.column_widths["history"].append(self.history_list.columnWidth(i))
1035 self.column_widths["contacts"] = []
1036 for i in range(self.contacts_list.columnCount() - 1):
1037 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1039 self.config.set_key("column_widths_2", self.column_widths, True)
1042 def create_contacts_tab(self):
1043 l, w = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1044 l.setContextMenuPolicy(Qt.CustomContextMenu)
1045 l.customContextMenuRequested.connect(self.create_contact_menu)
1046 for i,width in enumerate(self.column_widths['contacts']):
1047 l.setColumnWidth(i, width)
1049 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1050 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1051 self.contacts_list = l
1055 def create_invoices_tab(self):
1056 l, w = self.create_list_tab([_('Requestor'), _('Amount'), _('Status')])
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
1067 for item, value in invoices.items():
1068 domain, amount = value
1069 item = QTreeWidgetItem( [ domain, self.format_amount(amount), ""] )
1070 l.addTopLevelItem(item)
1072 l.setCurrentItem(l.topLevelItem(0))
1076 def delete_imported_key(self, addr):
1077 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1078 self.wallet.delete_imported_key(addr)
1079 self.update_receive_tab()
1080 self.update_history_tab()
1082 def edit_account_label(self, k):
1083 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1085 label = unicode(text)
1086 self.wallet.set_label(k,label)
1087 self.update_receive_tab()
1089 def account_set_expanded(self, item, k, b):
1091 self.accounts_expanded[k] = b
1093 def create_account_menu(self, position, k, item):
1095 if item.isExpanded():
1096 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1098 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1099 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1100 if self.wallet.seed_version > 4:
1101 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1102 if self.wallet.account_is_pending(k):
1103 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1104 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1106 def delete_pending_account(self, k):
1107 self.wallet.delete_pending_account(k)
1108 self.update_receive_tab()
1110 def create_receive_menu(self, position):
1111 # fixme: this function apparently has a side effect.
1112 # if it is not called the menu pops up several times
1113 #self.receive_list.selectedIndexes()
1115 selected = self.receive_list.selectedItems()
1116 multi_select = len(selected) > 1
1117 addrs = [unicode(item.text(0)) for item in selected]
1118 if not multi_select:
1119 item = self.receive_list.itemAt(position)
1123 if not is_valid(addr):
1124 k = str(item.data(0,32).toString())
1126 self.create_account_menu(position, k, item)
1128 item.setExpanded(not item.isExpanded())
1132 if not multi_select:
1133 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1134 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1135 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1136 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1137 if not self.wallet.is_watching_only():
1138 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1139 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1140 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1141 if self.wallet.is_imported(addr):
1142 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1144 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1145 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1146 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1147 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1149 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1150 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1152 run_hook('receive_menu', menu, addrs)
1153 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1156 def get_sendable_balance(self):
1157 return sum(map(lambda x:x['value'], self.get_coins()))
1160 def get_coins(self):
1162 return self.pay_from
1164 domain = self.wallet.get_account_addresses(self.current_account)
1165 for i in self.wallet.frozen_addresses:
1166 if i in domain: domain.remove(i)
1167 return self.wallet.get_unspent_coins(domain)
1170 def send_from_addresses(self, addrs):
1171 self.set_pay_from( addrs )
1172 self.tabs.setCurrentIndex(1)
1175 def payto(self, addr):
1177 label = self.wallet.labels.get(addr)
1178 m_addr = label + ' <' + addr + '>' if label else addr
1179 self.tabs.setCurrentIndex(1)
1180 self.payto_e.setText(m_addr)
1181 self.amount_e.setFocus()
1184 def delete_contact(self, x):
1185 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1186 self.wallet.delete_contact(x)
1187 self.wallet.set_label(x, None)
1188 self.update_history_tab()
1189 self.update_contacts_tab()
1190 self.update_completions()
1193 def create_contact_menu(self, position):
1194 item = self.contacts_list.itemAt(position)
1197 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1199 addr = unicode(item.text(0))
1200 label = unicode(item.text(1))
1201 is_editable = item.data(0,32).toBool()
1202 payto_addr = item.data(0,33).toString()
1203 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1204 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1205 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1207 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1208 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1210 run_hook('create_contact_menu', menu, item)
1211 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1213 def delete_invoice(self, item):
1214 self.invoices.pop(key)
1215 self.wallet.storage.put('invoices', self.invoices)
1216 self.update_invoices_tab()
1218 def show_invoice(self, key):
1219 from electrum.paymentrequest import PaymentRequest
1220 domain, value = self.invoices[key]
1221 pr = PaymentRequest(self.config)
1225 self.show_pr_details(pr)
1227 def show_pr_details(self, pr):
1228 msg = 'Domain: ' + pr.domain
1229 msg += '\nStatus: ' + pr.get_status()
1230 msg += '\nMemo: ' + pr.memo
1231 msg += '\nPayment URL: ' + pr.payment_url
1232 msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[0] + ' ' + self.format_amount(x[1])+ self.base_unit(), pr.get_outputs()))
1233 QMessageBox.information(self, 'Invoice', msg , 'OK')
1235 def create_invoice_menu(self, position):
1236 item = self.invoices_list.itemAt(position)
1239 k = self.invoices_list.indexOfTopLevelItem(item)
1240 key = self.invoices.keys()[k]
1242 menu.addAction(_("Details"), lambda: self.show_invoice(key))
1243 menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
1244 menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
1247 def update_receive_item(self, item):
1248 item.setFont(0, QFont(MONOSPACE_FONT))
1249 address = str(item.data(0,0).toString())
1250 label = self.wallet.labels.get(address,'')
1251 item.setData(1,0,label)
1252 item.setData(0,32, True) # is editable
1254 run_hook('update_receive_item', address, item)
1256 if not self.wallet.is_mine(address): return
1258 c, u = self.wallet.get_addr_balance(address)
1259 balance = self.format_amount(c + u)
1260 item.setData(2,0,balance)
1262 if address in self.wallet.frozen_addresses:
1263 item.setBackgroundColor(0, QColor('lightblue'))
1266 def update_receive_tab(self):
1267 l = self.receive_list
1268 # extend the syntax for consistency
1269 l.addChild = l.addTopLevelItem
1270 l.insertChild = l.insertTopLevelItem
1273 for i,width in enumerate(self.column_widths['receive']):
1274 l.setColumnWidth(i, width)
1276 accounts = self.wallet.get_accounts()
1277 if self.current_account is None:
1278 account_items = sorted(accounts.items())
1280 account_items = [(self.current_account, accounts.get(self.current_account))]
1283 for k, account in account_items:
1285 if len(accounts) > 1:
1286 name = self.wallet.get_account_name(k)
1287 c,u = self.wallet.get_account_balance(k)
1288 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1289 l.addTopLevelItem(account_item)
1290 account_item.setExpanded(self.accounts_expanded.get(k, True))
1291 account_item.setData(0, 32, k)
1295 sequences = [0,1] if account.has_change() else [0]
1296 for is_change in sequences:
1297 if len(sequences) > 1:
1298 name = _("Receiving") if not is_change else _("Change")
1299 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1300 account_item.addChild(seq_item)
1302 seq_item.setExpanded(True)
1304 seq_item = account_item
1306 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1312 for address in account.get_addresses(is_change):
1314 num, is_used = self.wallet.is_used(address)
1317 if gap > self.wallet.gap_limit:
1322 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1323 self.update_receive_item(item)
1325 item.setBackgroundColor(1, QColor('red'))
1329 seq_item.insertChild(0,used_item)
1331 used_item.addChild(item)
1333 seq_item.addChild(item)
1335 # we use column 1 because column 0 may be hidden
1336 l.setCurrentItem(l.topLevelItem(0),1)
1339 def update_contacts_tab(self):
1340 l = self.contacts_list
1343 for address in self.wallet.addressbook:
1344 label = self.wallet.labels.get(address,'')
1345 n = self.wallet.get_num_tx(address)
1346 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1347 item.setFont(0, QFont(MONOSPACE_FONT))
1348 # 32 = label can be edited (bool)
1349 item.setData(0,32, True)
1351 item.setData(0,33, address)
1352 l.addTopLevelItem(item)
1354 run_hook('update_contacts_tab', l)
1355 l.setCurrentItem(l.topLevelItem(0))
1359 def create_console_tab(self):
1360 from console import Console
1361 self.console = console = Console()
1365 def update_console(self):
1366 console = self.console
1367 console.history = self.config.get("console-history",[])
1368 console.history_index = len(console.history)
1370 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1371 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1373 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1375 def mkfunc(f, method):
1376 return lambda *args: apply( f, (method, args, self.password_dialog ))
1378 if m[0]=='_' or m in ['network','wallet']: continue
1379 methods[m] = mkfunc(c._run, m)
1381 console.updateNamespace(methods)
1384 def change_account(self,s):
1385 if s == _("All accounts"):
1386 self.current_account = None
1388 accounts = self.wallet.get_account_names()
1389 for k, v in accounts.items():
1391 self.current_account = k
1392 self.update_history_tab()
1393 self.update_status()
1394 self.update_receive_tab()
1396 def create_status_bar(self):
1399 sb.setFixedHeight(35)
1400 qtVersion = qVersion()
1402 self.balance_label = QLabel("")
1403 sb.addWidget(self.balance_label)
1405 from version_getter import UpdateLabel
1406 self.updatelabel = UpdateLabel(self.config, sb)
1408 self.account_selector = QComboBox()
1409 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1410 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1411 sb.addPermanentWidget(self.account_selector)
1413 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1414 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1416 self.lock_icon = QIcon()
1417 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1418 sb.addPermanentWidget( self.password_button )
1420 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1421 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1422 sb.addPermanentWidget( self.seed_button )
1423 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1424 sb.addPermanentWidget( self.status_button )
1426 run_hook('create_status_bar', (sb,))
1428 self.setStatusBar(sb)
1431 def update_lock_icon(self):
1432 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1433 self.password_button.setIcon( icon )
1436 def update_buttons_on_seed(self):
1437 if self.wallet.has_seed():
1438 self.seed_button.show()
1440 self.seed_button.hide()
1442 if not self.wallet.is_watching_only():
1443 self.password_button.show()
1444 self.send_button.setText(_("Send"))
1446 self.password_button.hide()
1447 self.send_button.setText(_("Create unsigned transaction"))
1450 def change_password_dialog(self):
1451 from password_dialog import PasswordDialog
1452 d = PasswordDialog(self.wallet, self)
1454 self.update_lock_icon()
1457 def new_contact_dialog(self):
1460 d.setWindowTitle(_("New Contact"))
1461 vbox = QVBoxLayout(d)
1462 vbox.addWidget(QLabel(_('New Contact')+':'))
1464 grid = QGridLayout()
1467 grid.addWidget(QLabel(_("Address")), 1, 0)
1468 grid.addWidget(line1, 1, 1)
1469 grid.addWidget(QLabel(_("Name")), 2, 0)
1470 grid.addWidget(line2, 2, 1)
1472 vbox.addLayout(grid)
1473 vbox.addLayout(ok_cancel_buttons(d))
1478 address = str(line1.text())
1479 label = unicode(line2.text())
1481 if not is_valid(address):
1482 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1485 self.wallet.add_contact(address)
1487 self.wallet.set_label(address, label)
1489 self.update_contacts_tab()
1490 self.update_history_tab()
1491 self.update_completions()
1492 self.tabs.setCurrentIndex(3)
1496 def new_account_dialog(self, password):
1498 dialog = QDialog(self)
1500 dialog.setWindowTitle(_("New Account"))
1502 vbox = QVBoxLayout()
1503 vbox.addWidget(QLabel(_('Account name')+':'))
1506 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1507 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1512 vbox.addLayout(ok_cancel_buttons(dialog))
1513 dialog.setLayout(vbox)
1517 name = str(e.text())
1520 self.wallet.create_pending_account(name, password)
1521 self.update_receive_tab()
1522 self.tabs.setCurrentIndex(2)
1527 def show_master_public_keys(self):
1529 dialog = QDialog(self)
1531 dialog.setWindowTitle(_("Master Public Keys"))
1533 main_layout = QGridLayout()
1534 mpk_dict = self.wallet.get_master_public_keys()
1536 for key, value in mpk_dict.items():
1537 main_layout.addWidget(QLabel(key), i, 0)
1538 mpk_text = QTextEdit()
1539 mpk_text.setReadOnly(True)
1540 mpk_text.setMaximumHeight(170)
1541 mpk_text.setText(value)
1542 main_layout.addWidget(mpk_text, i + 1, 0)
1545 vbox = QVBoxLayout()
1546 vbox.addLayout(main_layout)
1547 vbox.addLayout(close_button(dialog))
1549 dialog.setLayout(vbox)
1554 def show_seed_dialog(self, password):
1555 if not self.wallet.has_seed():
1556 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1560 mnemonic = self.wallet.get_mnemonic(password)
1562 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1564 from seed_dialog import SeedDialog
1565 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1570 def show_qrcode(self, data, title = _("QR code")):
1574 d.setWindowTitle(title)
1575 d.setMinimumSize(270, 300)
1576 vbox = QVBoxLayout()
1577 qrw = QRCodeWidget(data)
1578 vbox.addWidget(qrw, 1)
1579 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1580 hbox = QHBoxLayout()
1583 filename = os.path.join(self.config.path, "qrcode.bmp")
1586 bmp.save_qrcode(qrw.qr, filename)
1587 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1589 def copy_to_clipboard():
1590 bmp.save_qrcode(qrw.qr, filename)
1591 self.app.clipboard().setImage(QImage(filename))
1592 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1594 b = QPushButton(_("Copy"))
1596 b.clicked.connect(copy_to_clipboard)
1598 b = QPushButton(_("Save"))
1600 b.clicked.connect(print_qr)
1602 b = QPushButton(_("Close"))
1604 b.clicked.connect(d.accept)
1607 vbox.addLayout(hbox)
1612 def do_protect(self, func, args):
1613 if self.wallet.use_encryption:
1614 password = self.password_dialog()
1620 if args != (False,):
1621 args = (self,) + args + (password,)
1623 args = (self,password)
1627 def show_public_keys(self, address):
1628 if not address: return
1630 pubkey_list = self.wallet.get_public_keys(address)
1631 except Exception as e:
1632 traceback.print_exc(file=sys.stdout)
1633 self.show_message(str(e))
1637 d.setMinimumSize(600, 200)
1639 vbox = QVBoxLayout()
1640 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1641 vbox.addWidget( QLabel(_("Public key") + ':'))
1643 keys.setReadOnly(True)
1644 keys.setText('\n'.join(pubkey_list))
1645 vbox.addWidget(keys)
1646 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1647 vbox.addLayout(close_button(d))
1652 def show_private_key(self, address, password):
1653 if not address: return
1655 pk_list = self.wallet.get_private_key(address, password)
1656 except Exception as e:
1657 traceback.print_exc(file=sys.stdout)
1658 self.show_message(str(e))
1662 d.setMinimumSize(600, 200)
1664 vbox = QVBoxLayout()
1665 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1666 vbox.addWidget( QLabel(_("Private key") + ':'))
1668 keys.setReadOnly(True)
1669 keys.setText('\n'.join(pk_list))
1670 vbox.addWidget(keys)
1671 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1672 vbox.addLayout(close_button(d))
1678 def do_sign(self, address, message, signature, password):
1679 message = unicode(message.toPlainText())
1680 message = message.encode('utf-8')
1682 sig = self.wallet.sign_message(str(address.text()), message, password)
1683 signature.setText(sig)
1684 except Exception as e:
1685 self.show_message(str(e))
1687 def do_verify(self, address, message, signature):
1688 message = unicode(message.toPlainText())
1689 message = message.encode('utf-8')
1690 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1691 self.show_message(_("Signature verified"))
1693 self.show_message(_("Error: wrong signature"))
1696 def sign_verify_message(self, address=''):
1699 d.setWindowTitle(_('Sign/verify Message'))
1700 d.setMinimumSize(410, 290)
1702 layout = QGridLayout(d)
1704 message_e = QTextEdit()
1705 layout.addWidget(QLabel(_('Message')), 1, 0)
1706 layout.addWidget(message_e, 1, 1)
1707 layout.setRowStretch(2,3)
1709 address_e = QLineEdit()
1710 address_e.setText(address)
1711 layout.addWidget(QLabel(_('Address')), 2, 0)
1712 layout.addWidget(address_e, 2, 1)
1714 signature_e = QTextEdit()
1715 layout.addWidget(QLabel(_('Signature')), 3, 0)
1716 layout.addWidget(signature_e, 3, 1)
1717 layout.setRowStretch(3,1)
1719 hbox = QHBoxLayout()
1721 b = QPushButton(_("Sign"))
1722 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1725 b = QPushButton(_("Verify"))
1726 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1729 b = QPushButton(_("Close"))
1730 b.clicked.connect(d.accept)
1732 layout.addLayout(hbox, 4, 1)
1737 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1739 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1740 message_e.setText(decrypted)
1741 except Exception as e:
1742 self.show_message(str(e))
1745 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1746 message = unicode(message_e.toPlainText())
1747 message = message.encode('utf-8')
1749 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1750 encrypted_e.setText(encrypted)
1751 except Exception as e:
1752 self.show_message(str(e))
1756 def encrypt_message(self, address = ''):
1759 d.setWindowTitle(_('Encrypt/decrypt Message'))
1760 d.setMinimumSize(610, 490)
1762 layout = QGridLayout(d)
1764 message_e = QTextEdit()
1765 layout.addWidget(QLabel(_('Message')), 1, 0)
1766 layout.addWidget(message_e, 1, 1)
1767 layout.setRowStretch(2,3)
1769 pubkey_e = QLineEdit()
1771 pubkey = self.wallet.getpubkeys(address)[0]
1772 pubkey_e.setText(pubkey)
1773 layout.addWidget(QLabel(_('Public key')), 2, 0)
1774 layout.addWidget(pubkey_e, 2, 1)
1776 encrypted_e = QTextEdit()
1777 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1778 layout.addWidget(encrypted_e, 3, 1)
1779 layout.setRowStretch(3,1)
1781 hbox = QHBoxLayout()
1782 b = QPushButton(_("Encrypt"))
1783 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1786 b = QPushButton(_("Decrypt"))
1787 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1790 b = QPushButton(_("Close"))
1791 b.clicked.connect(d.accept)
1794 layout.addLayout(hbox, 4, 1)
1798 def question(self, msg):
1799 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1801 def show_message(self, msg):
1802 QMessageBox.information(self, _('Message'), msg, _('OK'))
1804 def password_dialog(self, msg=None):
1807 d.setWindowTitle(_("Enter Password"))
1812 vbox = QVBoxLayout()
1814 msg = _('Please enter your password')
1815 vbox.addWidget(QLabel(msg))
1817 grid = QGridLayout()
1819 grid.addWidget(QLabel(_('Password')), 1, 0)
1820 grid.addWidget(pw, 1, 1)
1821 vbox.addLayout(grid)
1823 vbox.addLayout(ok_cancel_buttons(d))
1826 run_hook('password_dialog', pw, grid, 1)
1827 if not d.exec_(): return
1828 return unicode(pw.text())
1837 def tx_from_text(self, txt):
1838 "json or raw hexadecimal"
1841 tx = Transaction(txt)
1847 tx_dict = json.loads(str(txt))
1848 assert "hex" in tx_dict.keys()
1849 tx = Transaction(tx_dict["hex"])
1850 if tx_dict.has_key("input_info"):
1851 input_info = json.loads(tx_dict['input_info'])
1852 tx.add_input_info(input_info)
1855 traceback.print_exc(file=sys.stdout)
1858 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1862 def read_tx_from_file(self):
1863 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1867 with open(fileName, "r") as f:
1868 file_content = f.read()
1869 except (ValueError, IOError, os.error), reason:
1870 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1872 return self.tx_from_text(file_content)
1876 def sign_raw_transaction(self, tx, input_info, password):
1877 self.wallet.signrawtransaction(tx, input_info, [], password)
1879 def do_process_from_text(self):
1880 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1883 tx = self.tx_from_text(text)
1885 self.show_transaction(tx)
1887 def do_process_from_file(self):
1888 tx = self.read_tx_from_file()
1890 self.show_transaction(tx)
1892 def do_process_from_txid(self):
1893 from electrum import transaction
1894 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1896 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1898 tx = transaction.Transaction(r)
1900 self.show_transaction(tx)
1902 self.show_message("unknown transaction")
1904 def do_process_from_csvReader(self, csvReader):
1909 for position, row in enumerate(csvReader):
1911 if not is_valid(address):
1912 errors.append((position, address))
1914 amount = Decimal(row[1])
1915 amount = int(100000000*amount)
1916 outputs.append((address, amount))
1917 except (ValueError, IOError, os.error), reason:
1918 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1922 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1923 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1927 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1928 except Exception as e:
1929 self.show_message(str(e))
1932 self.show_transaction(tx)
1934 def do_process_from_csv_file(self):
1935 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1939 with open(fileName, "r") as f:
1940 csvReader = csv.reader(f)
1941 self.do_process_from_csvReader(csvReader)
1942 except (ValueError, IOError, os.error), reason:
1943 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1946 def do_process_from_csv_text(self):
1947 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1948 + _("Format: address, amount. One output per line"), _("Load CSV"))
1951 f = StringIO.StringIO(text)
1952 csvReader = csv.reader(f)
1953 self.do_process_from_csvReader(csvReader)
1958 def export_privkeys_dialog(self, password):
1959 if self.wallet.is_watching_only():
1960 self.show_message(_("This is a watching-only wallet"))
1964 d.setWindowTitle(_('Private keys'))
1965 d.setMinimumSize(850, 300)
1966 vbox = QVBoxLayout(d)
1968 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1969 _("Exposing a single private key can compromise your entire wallet!"),
1970 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1971 vbox.addWidget(QLabel(msg))
1977 defaultname = 'electrum-private-keys.csv'
1978 select_msg = _('Select file to export your private keys to')
1979 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1980 vbox.addLayout(hbox)
1982 h, b = ok_cancel_buttons2(d, _('Export'))
1987 addresses = self.wallet.addresses(True)
1989 def privkeys_thread():
1990 for addr in addresses:
1994 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1995 d.emit(SIGNAL('computing_privkeys'))
1996 d.emit(SIGNAL('show_privkeys'))
1998 def show_privkeys():
1999 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
2003 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
2004 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
2005 threading.Thread(target=privkeys_thread).start()
2011 filename = filename_e.text()
2016 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
2017 except (IOError, os.error), reason:
2018 export_error_label = _("Electrum was unable to produce a private key-export.")
2019 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2021 except Exception as e:
2022 self.show_message(str(e))
2025 self.show_message(_("Private keys exported."))
2028 def do_export_privkeys(self, fileName, pklist, is_csv):
2029 with open(fileName, "w+") as f:
2031 transaction = csv.writer(f)
2032 transaction.writerow(["address", "private_key"])
2033 for addr, pk in pklist.items():
2034 transaction.writerow(["%34s"%addr,pk])
2037 f.write(json.dumps(pklist, indent = 4))
2040 def do_import_labels(self):
2041 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2042 if not labelsFile: return
2044 f = open(labelsFile, 'r')
2047 for key, value in json.loads(data).items():
2048 self.wallet.set_label(key, value)
2049 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2050 except (IOError, os.error), reason:
2051 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2054 def do_export_labels(self):
2055 labels = self.wallet.labels
2057 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2059 with open(fileName, 'w+') as f:
2060 json.dump(labels, f)
2061 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2062 except (IOError, os.error), reason:
2063 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2066 def export_history_dialog(self):
2069 d.setWindowTitle(_('Export History'))
2070 d.setMinimumSize(400, 200)
2071 vbox = QVBoxLayout(d)
2073 defaultname = os.path.expanduser('~/electrum-history.csv')
2074 select_msg = _('Select file to export your wallet transactions to')
2076 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2077 vbox.addLayout(hbox)
2081 h, b = ok_cancel_buttons2(d, _('Export'))
2086 filename = filename_e.text()
2091 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2092 except (IOError, os.error), reason:
2093 export_error_label = _("Electrum was unable to produce a transaction export.")
2094 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2097 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2100 def do_export_history(self, wallet, fileName, is_csv):
2101 history = wallet.get_tx_history()
2103 for item in history:
2104 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2106 if timestamp is not None:
2108 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2109 except [RuntimeError, TypeError, NameError] as reason:
2110 time_string = "unknown"
2113 time_string = "unknown"
2115 time_string = "pending"
2117 if value is not None:
2118 value_string = format_satoshis(value, True)
2123 fee_string = format_satoshis(fee, True)
2128 label, is_default_label = wallet.get_label(tx_hash)
2129 label = label.encode('utf-8')
2133 balance_string = format_satoshis(balance, False)
2135 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2137 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2139 with open(fileName, "w+") as f:
2141 transaction = csv.writer(f)
2142 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2144 transaction.writerow(line)
2147 f.write(json.dumps(lines, indent = 4))
2150 def sweep_key_dialog(self):
2152 d.setWindowTitle(_('Sweep private keys'))
2153 d.setMinimumSize(600, 300)
2155 vbox = QVBoxLayout(d)
2156 vbox.addWidget(QLabel(_("Enter private keys")))
2158 keys_e = QTextEdit()
2159 keys_e.setTabChangesFocus(True)
2160 vbox.addWidget(keys_e)
2162 h, address_e = address_field(self.wallet.addresses())
2166 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2167 vbox.addLayout(hbox)
2168 button.setEnabled(False)
2171 addr = str(address_e.text())
2172 if bitcoin.is_address(addr):
2176 pk = str(keys_e.toPlainText()).strip()
2177 if Wallet.is_private_key(pk):
2180 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2181 keys_e.textChanged.connect(f)
2182 address_e.textChanged.connect(f)
2186 fee = self.wallet.fee
2187 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2188 self.show_transaction(tx)
2192 def do_import_privkey(self, password):
2193 if not self.wallet.has_imported_keys():
2194 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2195 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2196 + _('Are you sure you understand what you are doing?'), 3, 4)
2199 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2202 text = str(text).split()
2207 addr = self.wallet.import_key(key, password)
2208 except Exception as e:
2214 addrlist.append(addr)
2216 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2218 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2219 self.update_receive_tab()
2220 self.update_history_tab()
2223 def settings_dialog(self):
2225 d.setWindowTitle(_('Electrum Settings'))
2227 vbox = QVBoxLayout()
2228 grid = QGridLayout()
2229 grid.setColumnStretch(0,1)
2231 nz_label = QLabel(_('Display zeros') + ':')
2232 grid.addWidget(nz_label, 0, 0)
2233 nz_e = AmountEdit(None,True)
2234 nz_e.setText("%d"% self.num_zeros)
2235 grid.addWidget(nz_e, 0, 1)
2236 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2237 grid.addWidget(HelpButton(msg), 0, 2)
2238 if not self.config.is_modifiable('num_zeros'):
2239 for w in [nz_e, nz_label]: w.setEnabled(False)
2241 lang_label=QLabel(_('Language') + ':')
2242 grid.addWidget(lang_label, 1, 0)
2243 lang_combo = QComboBox()
2244 from electrum.i18n import languages
2245 lang_combo.addItems(languages.values())
2247 index = languages.keys().index(self.config.get("language",''))
2250 lang_combo.setCurrentIndex(index)
2251 grid.addWidget(lang_combo, 1, 1)
2252 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2253 if not self.config.is_modifiable('language'):
2254 for w in [lang_combo, lang_label]: w.setEnabled(False)
2257 fee_label = QLabel(_('Transaction fee') + ':')
2258 grid.addWidget(fee_label, 2, 0)
2259 fee_e = BTCAmountEdit(self.get_decimal_point)
2260 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2261 grid.addWidget(fee_e, 2, 1)
2262 msg = _('Fee per kilobyte of transaction.') + ' ' \
2263 + _('Recommended value') + ': ' + self.format_amount(20000)
2264 grid.addWidget(HelpButton(msg), 2, 2)
2265 if not self.config.is_modifiable('fee_per_kb'):
2266 for w in [fee_e, fee_label]: w.setEnabled(False)
2268 units = ['BTC', 'mBTC']
2269 unit_label = QLabel(_('Base unit') + ':')
2270 grid.addWidget(unit_label, 3, 0)
2271 unit_combo = QComboBox()
2272 unit_combo.addItems(units)
2273 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2274 grid.addWidget(unit_combo, 3, 1)
2275 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2276 + '\n1BTC=1000mBTC.\n' \
2277 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2279 usechange_cb = QCheckBox(_('Use change addresses'))
2280 usechange_cb.setChecked(self.wallet.use_change)
2281 grid.addWidget(usechange_cb, 4, 0)
2282 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2283 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2285 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2286 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2287 grid.addWidget(block_ex_label, 5, 0)
2288 block_ex_combo = QComboBox()
2289 block_ex_combo.addItems(block_explorers)
2290 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2291 grid.addWidget(block_ex_combo, 5, 1)
2292 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2294 show_tx = self.config.get('show_before_broadcast', False)
2295 showtx_cb = QCheckBox(_('Show before broadcast'))
2296 showtx_cb.setChecked(show_tx)
2297 grid.addWidget(showtx_cb, 6, 0)
2298 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2300 vbox.addLayout(grid)
2302 vbox.addLayout(ok_cancel_buttons(d))
2306 if not d.exec_(): return
2309 fee = self.fee_e.get_amount()
2311 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2314 self.wallet.set_fee(fee)
2316 nz = unicode(nz_e.text())
2321 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2324 if self.num_zeros != nz:
2326 self.config.set_key('num_zeros', nz, True)
2327 self.update_history_tab()
2328 self.update_receive_tab()
2330 usechange_result = usechange_cb.isChecked()
2331 if self.wallet.use_change != usechange_result:
2332 self.wallet.use_change = usechange_result
2333 self.wallet.storage.put('use_change', self.wallet.use_change)
2335 if showtx_cb.isChecked() != show_tx:
2336 self.config.set_key('show_before_broadcast', not show_tx)
2338 unit_result = units[unit_combo.currentIndex()]
2339 if self.base_unit() != unit_result:
2340 self.decimal_point = 8 if unit_result == 'BTC' else 5
2341 self.config.set_key('decimal_point', self.decimal_point, True)
2342 self.update_history_tab()
2343 self.update_status()
2345 need_restart = False
2347 lang_request = languages.keys()[lang_combo.currentIndex()]
2348 if lang_request != self.config.get('language'):
2349 self.config.set_key("language", lang_request, True)
2352 be_result = block_explorers[block_ex_combo.currentIndex()]
2353 self.config.set_key('block_explorer', be_result, True)
2355 run_hook('close_settings_dialog')
2358 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2361 def run_network_dialog(self):
2362 if not self.network:
2364 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2366 def closeEvent(self, event):
2368 self.config.set_key("is_maximized", self.isMaximized())
2369 if not self.isMaximized():
2371 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2372 self.save_column_widths()
2373 self.config.set_key("console-history", self.console.history[-50:], True)
2374 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2378 def plugins_dialog(self):
2379 from electrum.plugins import plugins
2382 d.setWindowTitle(_('Electrum Plugins'))
2385 vbox = QVBoxLayout(d)
2388 scroll = QScrollArea()
2389 scroll.setEnabled(True)
2390 scroll.setWidgetResizable(True)
2391 scroll.setMinimumSize(400,250)
2392 vbox.addWidget(scroll)
2396 w.setMinimumHeight(len(plugins)*35)
2398 grid = QGridLayout()
2399 grid.setColumnStretch(0,1)
2402 def do_toggle(cb, p, w):
2405 if w: w.setEnabled(r)
2407 def mk_toggle(cb, p, w):
2408 return lambda: do_toggle(cb,p,w)
2410 for i, p in enumerate(plugins):
2412 cb = QCheckBox(p.fullname())
2413 cb.setDisabled(not p.is_available())
2414 cb.setChecked(p.is_enabled())
2415 grid.addWidget(cb, i, 0)
2416 if p.requires_settings():
2417 w = p.settings_widget(self)
2418 w.setEnabled( p.is_enabled() )
2419 grid.addWidget(w, i, 1)
2422 cb.clicked.connect(mk_toggle(cb,p,w))
2423 grid.addWidget(HelpButton(p.description()), i, 2)
2425 print_msg(_("Error: cannot display plugin"), p)
2426 traceback.print_exc(file=sys.stdout)
2427 grid.setRowStretch(i+1,1)
2429 vbox.addLayout(close_button(d))
2434 def show_account_details(self, k):
2435 account = self.wallet.accounts[k]
2438 d.setWindowTitle(_('Account Details'))
2441 vbox = QVBoxLayout(d)
2442 name = self.wallet.get_account_name(k)
2443 label = QLabel('Name: ' + name)
2444 vbox.addWidget(label)
2446 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2448 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2450 vbox.addWidget(QLabel(_('Master Public Key:')))
2453 text.setReadOnly(True)
2454 text.setMaximumHeight(170)
2455 vbox.addWidget(text)
2457 mpk_text = '\n'.join( account.get_master_pubkeys() )
2458 text.setText(mpk_text)
2460 vbox.addLayout(close_button(d))