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
44 from electrum import bmp, pyqrnative
46 from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit
47 from network_dialog import NetworkDialog
48 from qrcodewidget import QRCodeWidget
50 from decimal import Decimal
58 if platform.system() == 'Windows':
59 MONOSPACE_FONT = 'Lucida Console'
60 elif platform.system() == 'Darwin':
61 MONOSPACE_FONT = 'Monaco'
63 MONOSPACE_FONT = 'monospace'
67 # status of payment requests
70 PR_SENT = 2 # sent but not propagated
71 PR_PAID = 3 # send and propagated
72 PR_ERROR = 4 # could not parse
75 from electrum import ELECTRUM_VERSION
88 class StatusBarButton(QPushButton):
89 def __init__(self, icon, tooltip, func):
90 QPushButton.__init__(self, icon, '')
91 self.setToolTip(tooltip)
93 self.setMaximumWidth(25)
94 self.clicked.connect(func)
96 self.setIconSize(QSize(25,25))
98 def keyPressEvent(self, e):
99 if e.key() == QtCore.Qt.Key_Return:
111 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
113 class ElectrumWindow(QMainWindow):
117 def __init__(self, config, network, gui_object):
118 QMainWindow.__init__(self)
121 self.network = network
122 self.gui_object = gui_object
123 self.tray = gui_object.tray
124 self.go_lite = gui_object.go_lite
127 self.create_status_bar()
128 self.need_update = threading.Event()
130 self.decimal_point = config.get('decimal_point', 5)
131 self.num_zeros = int(config.get('num_zeros',0))
134 set_language(config.get('language'))
136 self.funds_error = False
137 self.completions = QStringListModel()
139 self.tabs = tabs = QTabWidget(self)
140 self.column_widths = self.config.get("column_widths_2", default_column_widths )
141 tabs.addTab(self.create_history_tab(), _('History') )
142 tabs.addTab(self.create_send_tab(), _('Send') )
143 tabs.addTab(self.create_receive_tab(), _('Receive') )
144 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
145 tabs.addTab(self.create_invoices_tab(), _('Invoices') )
146 tabs.addTab(self.create_console_tab(), _('Console') )
147 tabs.setMinimumSize(600, 400)
148 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
149 self.setCentralWidget(tabs)
151 g = self.config.get("winpos-qt",[100, 100, 840, 400])
152 self.setGeometry(g[0], g[1], g[2], g[3])
153 if self.config.get("is_maximized"):
156 self.setWindowIcon(QIcon(":icons/electrum.png"))
159 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
160 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
161 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
162 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
163 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
165 for i in range(tabs.count()):
166 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
168 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
169 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
170 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
171 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
172 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
174 self.history_list.setFocus(True)
178 self.network.register_callback('updated', lambda: self.need_update.set())
179 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
180 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
181 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
182 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
184 # set initial message
185 self.console.showMessage(self.network.banner)
190 def update_account_selector(self):
192 accounts = self.wallet.get_account_names()
193 self.account_selector.clear()
194 if len(accounts) > 1:
195 self.account_selector.addItems([_("All accounts")] + accounts.values())
196 self.account_selector.setCurrentIndex(0)
197 self.account_selector.show()
199 self.account_selector.hide()
202 def load_wallet(self, wallet):
206 self.update_wallet_format()
208 self.invoices = self.wallet.storage.get('invoices', {})
209 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
210 self.current_account = self.wallet.storage.get("current_account", None)
211 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
212 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
213 self.setWindowTitle( title )
215 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
216 self.notify_transactions()
217 self.update_account_selector()
219 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
220 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
221 self.password_menu.setEnabled(not self.wallet.is_watching_only())
222 self.seed_menu.setEnabled(self.wallet.has_seed())
223 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
224 self.import_menu.setEnabled(self.wallet.can_import())
226 self.update_lock_icon()
227 self.update_buttons_on_seed()
228 self.update_console()
230 run_hook('load_wallet', wallet)
233 def update_wallet_format(self):
234 # convert old-format imported keys
235 if self.wallet.imported_keys:
236 password = self.password_dialog(_("Please enter your password in order to update imported keys"))
238 self.wallet.convert_imported_keys(password)
240 self.show_message("error")
243 def open_wallet(self):
244 wallet_folder = self.wallet.storage.path
245 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
249 storage = WalletStorage({'wallet_path': filename})
250 if not storage.file_exists:
251 self.show_message("file not found "+ filename)
254 self.wallet.stop_threads()
257 wallet = Wallet(storage)
258 wallet.start_threads(self.network)
260 self.load_wallet(wallet)
264 def backup_wallet(self):
266 path = self.wallet.storage.path
267 wallet_folder = os.path.dirname(path)
268 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
272 new_path = os.path.join(wallet_folder, filename)
275 shutil.copy2(path, new_path)
276 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
277 except (IOError, os.error), reason:
278 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
281 def new_wallet(self):
284 wallet_folder = os.path.dirname(self.wallet.storage.path)
285 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
288 filename = os.path.join(wallet_folder, filename)
290 storage = WalletStorage({'wallet_path': filename})
291 if storage.file_exists:
292 QMessageBox.critical(None, "Error", _("File exists"))
295 wizard = installwizard.InstallWizard(self.config, self.network, storage)
296 wallet = wizard.run('new')
298 self.load_wallet(wallet)
302 def init_menubar(self):
305 file_menu = menubar.addMenu(_("&File"))
306 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
307 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
308 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
309 file_menu.addAction(_("&Quit"), self.close)
311 wallet_menu = menubar.addMenu(_("&Wallet"))
312 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
313 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
315 wallet_menu.addSeparator()
317 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
318 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
319 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
321 wallet_menu.addSeparator()
322 labels_menu = wallet_menu.addMenu(_("&Labels"))
323 labels_menu.addAction(_("&Import"), self.do_import_labels)
324 labels_menu.addAction(_("&Export"), self.do_export_labels)
326 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
327 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
328 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
329 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
330 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
332 tools_menu = menubar.addMenu(_("&Tools"))
334 # Settings / Preferences are all reserved keywords in OSX using this as work around
335 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
336 tools_menu.addAction(_("&Network"), self.run_network_dialog)
337 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
338 tools_menu.addSeparator()
339 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
340 tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
341 tools_menu.addSeparator()
343 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
344 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
345 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
347 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
348 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
349 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
350 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
351 self.raw_transaction_menu = raw_transaction_menu
353 help_menu = menubar.addMenu(_("&Help"))
354 help_menu.addAction(_("&About"), self.show_about)
355 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
356 help_menu.addSeparator()
357 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
358 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
360 self.setMenuBar(menubar)
362 def show_about(self):
363 QMessageBox.about(self, "Electrum",
364 _("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."))
366 def show_report_bug(self):
367 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
368 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
371 def notify_transactions(self):
372 if not self.network or not self.network.is_connected():
375 print_error("Notifying GUI")
376 if len(self.network.pending_transactions_for_notifications) > 0:
377 # Combine the transactions if there are more then three
378 tx_amount = len(self.network.pending_transactions_for_notifications)
381 for tx in self.network.pending_transactions_for_notifications:
382 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
386 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
387 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
389 self.network.pending_transactions_for_notifications = []
391 for tx in self.network.pending_transactions_for_notifications:
393 self.network.pending_transactions_for_notifications.remove(tx)
394 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
396 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
398 def notify(self, message):
399 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
403 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
404 def getOpenFileName(self, title, filter = ""):
405 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
406 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
407 if fileName and directory != os.path.dirname(fileName):
408 self.config.set_key('io_dir', os.path.dirname(fileName), True)
411 def getSaveFileName(self, title, filename, filter = ""):
412 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
413 path = os.path.join( directory, filename )
414 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
415 if fileName and directory != os.path.dirname(fileName):
416 self.config.set_key('io_dir', os.path.dirname(fileName), True)
420 QMainWindow.close(self)
421 run_hook('close_main_window')
423 def connect_slots(self, sender):
424 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
425 self.previous_payto_e=''
427 def timer_actions(self):
428 if self.need_update.is_set():
430 self.need_update.clear()
431 run_hook('timer_actions')
433 def format_amount(self, x, is_diff=False, whitespaces=False):
434 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
437 def get_decimal_point(self):
438 return self.decimal_point
442 assert self.decimal_point in [5,8]
443 return "BTC" if self.decimal_point == 8 else "mBTC"
446 def update_status(self):
447 if self.network is None or not self.network.is_running():
449 icon = QIcon(":icons/status_disconnected.png")
451 elif self.network.is_connected():
452 if not self.wallet.up_to_date:
453 text = _("Synchronizing...")
454 icon = QIcon(":icons/status_waiting.png")
455 elif self.network.server_lag > 1:
456 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
457 icon = QIcon(":icons/status_lagging.png")
459 c, u = self.wallet.get_account_balance(self.current_account)
460 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
461 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
463 # append fiat balance and price from exchange rate plugin
465 run_hook('get_fiat_status_text', c+u, r)
470 self.tray.setToolTip(text)
471 icon = QIcon(":icons/status_connected.png")
473 text = _("Not connected")
474 icon = QIcon(":icons/status_disconnected.png")
476 self.balance_label.setText(text)
477 self.status_button.setIcon( icon )
480 def update_wallet(self):
482 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
483 self.update_history_tab()
484 self.update_receive_tab()
485 self.update_contacts_tab()
486 self.update_completions()
487 self.update_invoices_tab()
490 def create_history_tab(self):
491 self.history_list = l = MyTreeWidget(self)
493 for i,width in enumerate(self.column_widths['history']):
494 l.setColumnWidth(i, width)
495 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
496 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
497 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
499 l.customContextMenuRequested.connect(self.create_history_menu)
503 def create_history_menu(self, position):
504 self.history_list.selectedIndexes()
505 item = self.history_list.currentItem()
506 be = self.config.get('block_explorer', 'Blockchain.info')
507 if be == 'Blockchain.info':
508 block_explorer = 'https://blockchain.info/tx/'
509 elif be == 'Blockr.io':
510 block_explorer = 'https://blockr.io/tx/info/'
511 elif be == 'Insight.is':
512 block_explorer = 'http://live.insight.is/tx/'
514 tx_hash = str(item.data(0, Qt.UserRole).toString())
515 if not tx_hash: return
517 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
518 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
519 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
520 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
521 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
524 def show_transaction(self, tx):
525 import transaction_dialog
526 d = transaction_dialog.TxDialog(tx, self)
529 def tx_label_clicked(self, item, column):
530 if column==2 and item.isSelected():
532 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
533 self.history_list.editItem( item, column )
534 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
537 def tx_label_changed(self, item, column):
541 tx_hash = str(item.data(0, Qt.UserRole).toString())
542 tx = self.wallet.transactions.get(tx_hash)
543 text = unicode( item.text(2) )
544 self.wallet.set_label(tx_hash, text)
546 item.setForeground(2, QBrush(QColor('black')))
548 text = self.wallet.get_default_label(tx_hash)
549 item.setText(2, text)
550 item.setForeground(2, QBrush(QColor('gray')))
554 def edit_label(self, is_recv):
555 l = self.receive_list if is_recv else self.contacts_list
556 item = l.currentItem()
557 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
558 l.editItem( item, 1 )
559 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
563 def address_label_clicked(self, item, column, l, column_addr, column_label):
564 if column == column_label and item.isSelected():
565 is_editable = item.data(0, 32).toBool()
568 addr = unicode( item.text(column_addr) )
569 label = unicode( item.text(column_label) )
570 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
571 l.editItem( item, column )
572 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
575 def address_label_changed(self, item, column, l, column_addr, column_label):
576 if column == column_label:
577 addr = unicode( item.text(column_addr) )
578 text = unicode( item.text(column_label) )
579 is_editable = item.data(0, 32).toBool()
583 changed = self.wallet.set_label(addr, text)
585 self.update_history_tab()
586 self.update_completions()
588 self.current_item_changed(item)
590 run_hook('item_changed', item, column)
593 def current_item_changed(self, a):
594 run_hook('current_item_changed', a)
598 def update_history_tab(self):
600 self.history_list.clear()
601 for item in self.wallet.get_tx_history(self.current_account):
602 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
603 time_str = _("unknown")
606 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
608 time_str = _("error")
611 time_str = 'unverified'
612 icon = QIcon(":icons/unconfirmed.png")
615 icon = QIcon(":icons/unconfirmed.png")
617 icon = QIcon(":icons/clock%d.png"%conf)
619 icon = QIcon(":icons/confirmed.png")
621 if value is not None:
622 v_str = self.format_amount(value, True, whitespaces=True)
626 balance_str = self.format_amount(balance, whitespaces=True)
629 label, is_default_label = self.wallet.get_label(tx_hash)
631 label = _('Pruned transaction outputs')
632 is_default_label = False
634 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
635 item.setFont(2, QFont(MONOSPACE_FONT))
636 item.setFont(3, QFont(MONOSPACE_FONT))
637 item.setFont(4, QFont(MONOSPACE_FONT))
639 item.setForeground(3, QBrush(QColor("#BC1E1E")))
641 item.setData(0, Qt.UserRole, tx_hash)
642 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
644 item.setForeground(2, QBrush(QColor('grey')))
646 item.setIcon(0, icon)
647 self.history_list.insertTopLevelItem(0,item)
650 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
651 run_hook('history_tab_update')
654 def create_send_tab(self):
657 self.send_grid = grid = QGridLayout(w)
659 grid.setColumnMinimumWidth(3,300)
660 grid.setColumnStretch(5,1)
661 grid.setRowStretch(8, 1)
663 from paytoedit import PayToEdit
664 self.amount_e = BTCAmountEdit(self.get_decimal_point)
665 self.payto_e = PayToEdit(self.amount_e)
666 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)'))
667 grid.addWidget(QLabel(_('Pay to')), 1, 0)
668 grid.addWidget(self.payto_e, 1, 1, 1, 3)
669 grid.addWidget(self.payto_help, 1, 4)
671 completer = QCompleter()
672 completer.setCaseSensitivity(False)
673 self.payto_e.setCompleter(completer)
674 completer.setModel(self.completions)
676 self.message_e = MyLineEdit()
677 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.'))
678 grid.addWidget(QLabel(_('Description')), 2, 0)
679 grid.addWidget(self.message_e, 2, 1, 1, 3)
680 grid.addWidget(self.message_help, 2, 4)
682 self.from_label = QLabel(_('From'))
683 grid.addWidget(self.from_label, 3, 0)
684 self.from_list = MyTreeWidget(self)
685 self.from_list.setColumnCount(2)
686 self.from_list.setColumnWidth(0, 350)
687 self.from_list.setColumnWidth(1, 50)
688 self.from_list.setHeaderHidden(True)
689 self.from_list.setMaximumHeight(80)
690 self.from_list.setContextMenuPolicy(Qt.CustomContextMenu)
691 self.from_list.customContextMenuRequested.connect(self.from_list_menu)
692 grid.addWidget(self.from_list, 3, 1, 1, 3)
693 self.set_pay_from([])
695 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
696 + _('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.') \
697 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
698 grid.addWidget(QLabel(_('Amount')), 4, 0)
699 grid.addWidget(self.amount_e, 4, 1, 1, 2)
700 grid.addWidget(self.amount_help, 4, 3)
702 self.fee_e = BTCAmountEdit(self.get_decimal_point)
703 grid.addWidget(QLabel(_('Fee')), 5, 0)
704 grid.addWidget(self.fee_e, 5, 1, 1, 2)
705 grid.addWidget(HelpButton(
706 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
707 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
708 + _('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)
710 self.send_button = EnterButton(_("Send"), self.do_send)
711 grid.addWidget(self.send_button, 6, 1)
713 b = EnterButton(_("Clear"), self.do_clear)
714 grid.addWidget(b, 6, 2)
716 self.payto_sig = QLabel('')
717 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
719 #QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
720 #QShortcut(QKeySequence("Down"), w, w.focusNextChild)
723 def entry_changed( is_fee ):
724 self.funds_error = False
726 if self.amount_e.is_shortcut:
727 self.amount_e.is_shortcut = False
728 sendable = self.get_sendable_balance()
729 # there is only one output because we are completely spending inputs
730 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, coins = self.get_coins())
731 fee = self.wallet.estimated_fee(inputs, 1)
733 self.amount_e.setAmount(amount)
734 self.fee_e.setAmount(fee)
737 amount = self.amount_e.get_amount()
738 fee = self.fee_e.get_amount()
740 if not is_fee: fee = None
743 # assume that there will be 2 outputs (one for change)
744 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, coins = self.get_coins())
746 self.fee_e.setAmount(fee)
749 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
753 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
754 self.funds_error = True
755 text = _( "Not enough funds" )
756 c, u = self.wallet.get_frozen_balance()
757 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
759 self.statusBar().showMessage(text)
760 self.amount_e.setPalette(palette)
761 self.fee_e.setPalette(palette)
763 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
764 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
766 run_hook('create_send_tab', grid)
769 def from_list_delete(self, item):
770 i = self.from_list.indexOfTopLevelItem(item)
772 self.redraw_from_list()
774 def from_list_menu(self, position):
775 item = self.from_list.itemAt(position)
777 menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
778 menu.exec_(self.from_list.viewport().mapToGlobal(position))
780 def set_pay_from(self, domain = None):
781 self.pay_from = [] if domain == [] else self.wallet.get_unspent_coins(domain)
782 self.redraw_from_list()
784 def redraw_from_list(self):
785 self.from_list.clear()
786 self.from_label.setHidden(len(self.pay_from) == 0)
787 self.from_list.setHidden(len(self.pay_from) == 0)
790 h = x.get('prevout_hash')
791 return h[0:8] + '...' + h[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
793 for item in self.pay_from:
794 self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
796 def update_completions(self):
798 for addr,label in self.wallet.labels.items():
799 if addr in self.wallet.addressbook:
800 l.append( label + ' <' + addr + '>')
802 run_hook('update_completions', l)
803 self.completions.setStringList(l)
807 return lambda s, *args: s.do_protect(func, args)
810 def read_send_tab(self):
812 if self.payment_request and self.payment_request.has_expired():
813 QMessageBox.warning(self, _('Error'), _('Payment request has expired'), _('OK'))
816 label = unicode( self.message_e.text() )
818 if self.payment_request:
819 outputs = self.payment_request.get_outputs()
821 outputs = self.payto_e.get_outputs()
824 QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
827 for addr, x in outputs:
828 if addr is None or not bitcoin.is_address(addr):
829 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address'), _('OK'))
832 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
835 amount = sum(map(lambda x:x[1], outputs))
837 fee = self.fee_e.get_amount()
839 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
842 confirm_amount = self.config.get('confirm_amount', 100000000)
843 if amount >= confirm_amount:
844 o = '\n'.join(map(lambda x:x[0], outputs))
845 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
848 confirm_fee = self.config.get('confirm_fee', 100000)
849 if fee >= confirm_fee:
850 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()}):
853 coins = self.get_coins()
854 return outputs, fee, label, coins
858 r = self.read_send_tab()
861 outputs, fee, label, coins = r
862 self.send_tx(outputs, fee, label, coins)
866 def send_tx(self, outputs, fee, label, coins, password):
867 self.send_button.setDisabled(True)
869 # first, create an unsigned tx
871 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
873 except Exception as e:
874 traceback.print_exc(file=sys.stdout)
875 self.show_message(str(e))
876 self.send_button.setDisabled(False)
879 # call hook to see if plugin needs gui interaction
880 run_hook('send_tx', tx)
886 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
887 self.wallet.sign_transaction(tx, keypairs, password)
888 return tx, fee, label
890 def sign_done(tx, fee, label):
892 self.show_message(tx.error)
893 self.send_button.setDisabled(False)
895 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
896 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
897 self.send_button.setDisabled(False)
900 self.wallet.set_label(tx.hash(), label)
902 if not tx.is_complete() or self.config.get('show_before_broadcast'):
903 self.show_transaction(tx)
905 self.send_button.setDisabled(False)
908 self.broadcast_transaction(tx)
910 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
911 self.waiting_dialog.start()
915 def broadcast_transaction(self, tx):
917 def broadcast_thread():
918 pr = self.payment_request
920 return self.wallet.sendtx(tx)
923 self.payment_request = None
924 return False, _("Payment request has expired")
926 status, msg = self.wallet.sendtx(tx)
930 self.invoices[pr.get_id()] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), PR_PAID, tx.hash())
931 self.wallet.storage.put('invoices', self.invoices)
932 self.update_invoices_tab()
933 self.payment_request = None
934 refund_address = self.wallet.addresses()[0]
935 ack_status, ack_msg = pr.send_ack(str(tx), refund_address)
941 def broadcast_done(status, msg):
943 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
946 QMessageBox.warning(self, _('Error'), msg, _('OK'))
947 self.send_button.setDisabled(False)
949 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
950 self.waiting_dialog.start()
954 def prepare_for_payment_request(self):
955 self.tabs.setCurrentIndex(1)
956 self.payto_e.is_pr = True
957 for e in [self.payto_e, self.amount_e, self.message_e]:
959 for h in [self.payto_help, self.amount_help, self.message_help]:
961 self.payto_e.setText(_("please wait..."))
964 def payment_request_ok(self):
965 pr = self.payment_request
967 if pr_id not in self.invoices:
968 self.invoices[pr_id] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), PR_UNPAID, None)
969 self.wallet.storage.put('invoices', self.invoices)
970 self.update_invoices_tab()
972 print_error('invoice already in list')
974 status = self.invoices[pr_id][3]
975 if status == PR_PAID:
977 self.show_message("invoice already paid")
978 self.payment_request = None
981 self.payto_help.show()
982 self.payto_help.set_alt(lambda: self.show_pr_details(pr))
984 if not pr.has_expired():
985 self.payto_e.setGreen()
987 self.payto_e.setExpired()
989 self.payto_e.setText(pr.domain)
990 self.amount_e.setText(self.format_amount(pr.get_amount()))
991 self.message_e.setText(pr.get_memo())
993 def payment_request_error(self):
995 self.show_message(self.payment_request.error)
996 self.payment_request = None
998 def pay_from_URI(self,URI):
1001 address, amount, label, message, request_url = util.parse_URI(URI)
1003 address, amount, label, message, request_url = util.parse_URI(URI)
1004 except Exception as e:
1005 QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URI:') + '\n' + str(e), _('OK'))
1008 self.tabs.setCurrentIndex(1)
1012 if self.wallet.labels.get(address) != label:
1013 if self.question(_('Save label "%s" for address %s ?'%(label,address))):
1014 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1015 self.wallet.addressbook.append(address)
1016 self.wallet.set_label(address, label)
1018 label = self.wallet.labels.get(address)
1020 self.payto_e.setText(label + ' <'+ address +'>' if label else address)
1022 self.message_e.setText(message)
1024 self.amount_e.setAmount(amount)
1027 from electrum import paymentrequest
1028 def payment_request():
1029 self.payment_request = paymentrequest.PaymentRequest(self.config)
1030 self.payment_request.read(request_url)
1031 if self.payment_request.verify():
1032 self.emit(SIGNAL('payment_request_ok'))
1034 self.emit(SIGNAL('payment_request_error'))
1036 self.pr_thread = threading.Thread(target=payment_request).start()
1037 self.prepare_for_payment_request()
1042 self.payto_e.is_pr = False
1043 self.payto_sig.setVisible(False)
1044 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1048 for h in [self.payto_help, self.amount_help, self.message_help]:
1051 self.payto_help.set_alt(None)
1052 self.set_pay_from([])
1053 self.update_status()
1057 def set_addrs_frozen(self,addrs,freeze):
1059 if not addr: continue
1060 if addr in self.wallet.frozen_addresses and not freeze:
1061 self.wallet.unfreeze(addr)
1062 elif addr not in self.wallet.frozen_addresses and freeze:
1063 self.wallet.freeze(addr)
1064 self.update_receive_tab()
1068 def create_list_tab(self, headers):
1069 "generic tab creation method"
1070 l = MyTreeWidget(self)
1071 l.setColumnCount( len(headers) )
1072 l.setHeaderLabels( headers )
1075 vbox = QVBoxLayout()
1082 vbox.addWidget(buttons)
1087 def create_receive_tab(self):
1088 l, w = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1089 for i,width in enumerate(self.column_widths['receive']):
1090 l.setColumnWidth(i, width)
1091 l.setContextMenuPolicy(Qt.CustomContextMenu)
1092 l.customContextMenuRequested.connect(self.create_receive_menu)
1093 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1094 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1095 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1096 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1097 self.receive_list = l
1103 def save_column_widths(self):
1104 self.column_widths["receive"] = []
1105 for i in range(self.receive_list.columnCount() -1):
1106 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1108 self.column_widths["history"] = []
1109 for i in range(self.history_list.columnCount() - 1):
1110 self.column_widths["history"].append(self.history_list.columnWidth(i))
1112 self.column_widths["contacts"] = []
1113 for i in range(self.contacts_list.columnCount() - 1):
1114 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1116 self.config.set_key("column_widths_2", self.column_widths, True)
1119 def create_contacts_tab(self):
1120 l, w = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1121 l.setContextMenuPolicy(Qt.CustomContextMenu)
1122 l.customContextMenuRequested.connect(self.create_contact_menu)
1123 for i,width in enumerate(self.column_widths['contacts']):
1124 l.setColumnWidth(i, width)
1125 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1126 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1127 self.contacts_list = l
1131 def create_invoices_tab(self):
1132 l, w = self.create_list_tab([_('Requestor'), _('Memo'),_('Amount'), _('Status')])
1134 h.setStretchLastSection(False)
1135 h.setResizeMode(1, QHeaderView.Stretch)
1136 l.setContextMenuPolicy(Qt.CustomContextMenu)
1137 l.customContextMenuRequested.connect(self.create_invoice_menu)
1138 self.invoices_list = l
1141 def update_invoices_tab(self):
1142 invoices = self.wallet.storage.get('invoices', {})
1143 l = self.invoices_list
1145 for key, value in invoices.items():
1147 domain, memo, amount, status, tx_hash = value
1151 item = QTreeWidgetItem( [ domain, memo, self.format_amount(amount), format_status(status)] )
1152 l.addTopLevelItem(item)
1154 l.setCurrentItem(l.topLevelItem(0))
1158 def delete_imported_key(self, addr):
1159 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1160 self.wallet.delete_imported_key(addr)
1161 self.update_receive_tab()
1162 self.update_history_tab()
1164 def edit_account_label(self, k):
1165 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1167 label = unicode(text)
1168 self.wallet.set_label(k,label)
1169 self.update_receive_tab()
1171 def account_set_expanded(self, item, k, b):
1173 self.accounts_expanded[k] = b
1175 def create_account_menu(self, position, k, item):
1177 if item.isExpanded():
1178 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1180 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1181 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1182 if self.wallet.seed_version > 4:
1183 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1184 if self.wallet.account_is_pending(k):
1185 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1186 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1188 def delete_pending_account(self, k):
1189 self.wallet.delete_pending_account(k)
1190 self.update_receive_tab()
1192 def create_receive_menu(self, position):
1193 # fixme: this function apparently has a side effect.
1194 # if it is not called the menu pops up several times
1195 #self.receive_list.selectedIndexes()
1197 selected = self.receive_list.selectedItems()
1198 multi_select = len(selected) > 1
1199 addrs = [unicode(item.text(0)) for item in selected]
1200 if not multi_select:
1201 item = self.receive_list.itemAt(position)
1205 if not is_valid(addr):
1206 k = str(item.data(0,32).toString())
1208 self.create_account_menu(position, k, item)
1210 item.setExpanded(not item.isExpanded())
1214 if not multi_select:
1215 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1216 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1217 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1218 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1219 if not self.wallet.is_watching_only():
1220 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1221 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1222 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1223 if self.wallet.is_imported(addr):
1224 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1226 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1227 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1228 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1229 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1231 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1232 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1234 run_hook('receive_menu', menu, addrs)
1235 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1238 def get_sendable_balance(self):
1239 return sum(map(lambda x:x['value'], self.get_coins()))
1242 def get_coins(self):
1244 return self.pay_from
1246 domain = self.wallet.get_account_addresses(self.current_account)
1247 for i in self.wallet.frozen_addresses:
1248 if i in domain: domain.remove(i)
1249 return self.wallet.get_unspent_coins(domain)
1252 def send_from_addresses(self, addrs):
1253 self.set_pay_from( addrs )
1254 self.tabs.setCurrentIndex(1)
1257 def payto(self, addr):
1259 label = self.wallet.labels.get(addr)
1260 m_addr = label + ' <' + addr + '>' if label else addr
1261 self.tabs.setCurrentIndex(1)
1262 self.payto_e.setText(m_addr)
1263 self.amount_e.setFocus()
1266 def delete_contact(self, x):
1267 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1268 self.wallet.delete_contact(x)
1269 self.wallet.set_label(x, None)
1270 self.update_history_tab()
1271 self.update_contacts_tab()
1272 self.update_completions()
1275 def create_contact_menu(self, position):
1276 item = self.contacts_list.itemAt(position)
1279 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1281 addr = unicode(item.text(0))
1282 label = unicode(item.text(1))
1283 is_editable = item.data(0,32).toBool()
1284 payto_addr = item.data(0,33).toString()
1285 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1286 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1287 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1289 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1290 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1292 run_hook('create_contact_menu', menu, item)
1293 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1295 def delete_invoice(self, key):
1296 self.invoices.pop(key)
1297 self.wallet.storage.put('invoices', self.invoices)
1298 self.update_invoices_tab()
1300 def show_invoice(self, key):
1301 from electrum.paymentrequest import PaymentRequest
1302 domain, memo, value, status, tx_hash = self.invoices[key]
1303 pr = PaymentRequest(self.config)
1307 self.show_pr_details(pr)
1309 def show_pr_details(self, pr):
1310 msg = 'Domain: ' + pr.domain
1311 msg += '\nStatus: ' + pr.get_status()
1312 msg += '\nMemo: ' + pr.get_memo()
1313 msg += '\nPayment URL: ' + pr.payment_url
1314 msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[0] + ' ' + self.format_amount(x[1])+ self.base_unit(), pr.get_outputs()))
1315 QMessageBox.information(self, 'Invoice', msg , 'OK')
1317 def do_pay_invoice(self, key):
1318 from electrum.paymentrequest import PaymentRequest
1319 domain, memo, value, status, tx_hash = self.invoices[key]
1320 pr = PaymentRequest(self.config)
1323 self.payment_request = pr
1324 self.prepare_for_payment_request()
1326 self.payment_request_ok()
1328 self.payment_request_error()
1331 def create_invoice_menu(self, position):
1332 item = self.invoices_list.itemAt(position)
1335 k = self.invoices_list.indexOfTopLevelItem(item)
1336 key = self.invoices.keys()[k]
1337 domain, memo, value, status, tx_hash = self.invoices[key]
1339 menu.addAction(_("Details"), lambda: self.show_invoice(key))
1340 if status == PR_UNPAID:
1341 menu.addAction(_("Pay Now"), lambda: self.do_pay_invoice(key))
1342 menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
1343 menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
1346 def update_receive_item(self, item):
1347 item.setFont(0, QFont(MONOSPACE_FONT))
1348 address = str(item.data(0,0).toString())
1349 label = self.wallet.labels.get(address,'')
1350 item.setData(1,0,label)
1351 item.setData(0,32, True) # is editable
1353 run_hook('update_receive_item', address, item)
1355 if not self.wallet.is_mine(address): return
1357 c, u = self.wallet.get_addr_balance(address)
1358 balance = self.format_amount(c + u)
1359 item.setData(2,0,balance)
1361 if address in self.wallet.frozen_addresses:
1362 item.setBackgroundColor(0, QColor('lightblue'))
1365 def update_receive_tab(self):
1366 l = self.receive_list
1367 # extend the syntax for consistency
1368 l.addChild = l.addTopLevelItem
1369 l.insertChild = l.insertTopLevelItem
1373 accounts = self.wallet.get_accounts()
1374 if self.current_account is None:
1375 account_items = sorted(accounts.items())
1377 account_items = [(self.current_account, accounts.get(self.current_account))]
1380 for k, account in account_items:
1382 if len(accounts) > 1:
1383 name = self.wallet.get_account_name(k)
1384 c,u = self.wallet.get_account_balance(k)
1385 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1386 l.addTopLevelItem(account_item)
1387 account_item.setExpanded(self.accounts_expanded.get(k, True))
1388 account_item.setData(0, 32, k)
1392 sequences = [0,1] if account.has_change() else [0]
1393 for is_change in sequences:
1394 if len(sequences) > 1:
1395 name = _("Receiving") if not is_change else _("Change")
1396 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1397 account_item.addChild(seq_item)
1399 seq_item.setExpanded(True)
1401 seq_item = account_item
1403 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1409 for address in account.get_addresses(is_change):
1411 num, is_used = self.wallet.is_used(address)
1414 if gap > self.wallet.gap_limit:
1419 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1420 self.update_receive_item(item)
1422 item.setBackgroundColor(1, QColor('red'))
1426 seq_item.insertChild(0,used_item)
1428 used_item.addChild(item)
1430 seq_item.addChild(item)
1432 # we use column 1 because column 0 may be hidden
1433 l.setCurrentItem(l.topLevelItem(0),1)
1436 def update_contacts_tab(self):
1437 l = self.contacts_list
1440 for address in self.wallet.addressbook:
1441 label = self.wallet.labels.get(address,'')
1442 n = self.wallet.get_num_tx(address)
1443 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1444 item.setFont(0, QFont(MONOSPACE_FONT))
1445 # 32 = label can be edited (bool)
1446 item.setData(0,32, True)
1448 item.setData(0,33, address)
1449 l.addTopLevelItem(item)
1451 run_hook('update_contacts_tab', l)
1452 l.setCurrentItem(l.topLevelItem(0))
1456 def create_console_tab(self):
1457 from console import Console
1458 self.console = console = Console()
1462 def update_console(self):
1463 console = self.console
1464 console.history = self.config.get("console-history",[])
1465 console.history_index = len(console.history)
1467 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1468 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1470 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1472 def mkfunc(f, method):
1473 return lambda *args: apply( f, (method, args, self.password_dialog ))
1475 if m[0]=='_' or m in ['network','wallet']: continue
1476 methods[m] = mkfunc(c._run, m)
1478 console.updateNamespace(methods)
1481 def change_account(self,s):
1482 if s == _("All accounts"):
1483 self.current_account = None
1485 accounts = self.wallet.get_account_names()
1486 for k, v in accounts.items():
1488 self.current_account = k
1489 self.update_history_tab()
1490 self.update_status()
1491 self.update_receive_tab()
1493 def create_status_bar(self):
1496 sb.setFixedHeight(35)
1497 qtVersion = qVersion()
1499 self.balance_label = QLabel("")
1500 sb.addWidget(self.balance_label)
1502 from version_getter import UpdateLabel
1503 self.updatelabel = UpdateLabel(self.config, sb)
1505 self.account_selector = QComboBox()
1506 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1507 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1508 sb.addPermanentWidget(self.account_selector)
1510 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1511 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1513 self.lock_icon = QIcon()
1514 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1515 sb.addPermanentWidget( self.password_button )
1517 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1518 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1519 sb.addPermanentWidget( self.seed_button )
1520 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1521 sb.addPermanentWidget( self.status_button )
1523 run_hook('create_status_bar', (sb,))
1525 self.setStatusBar(sb)
1528 def update_lock_icon(self):
1529 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1530 self.password_button.setIcon( icon )
1533 def update_buttons_on_seed(self):
1534 if self.wallet.has_seed():
1535 self.seed_button.show()
1537 self.seed_button.hide()
1539 if not self.wallet.is_watching_only():
1540 self.password_button.show()
1541 self.send_button.setText(_("Send"))
1543 self.password_button.hide()
1544 self.send_button.setText(_("Create unsigned transaction"))
1547 def change_password_dialog(self):
1548 from password_dialog import PasswordDialog
1549 d = PasswordDialog(self.wallet, self)
1551 self.update_lock_icon()
1554 def new_contact_dialog(self):
1557 d.setWindowTitle(_("New Contact"))
1558 vbox = QVBoxLayout(d)
1559 vbox.addWidget(QLabel(_('New Contact')+':'))
1561 grid = QGridLayout()
1564 grid.addWidget(QLabel(_("Address")), 1, 0)
1565 grid.addWidget(line1, 1, 1)
1566 grid.addWidget(QLabel(_("Name")), 2, 0)
1567 grid.addWidget(line2, 2, 1)
1569 vbox.addLayout(grid)
1570 vbox.addLayout(ok_cancel_buttons(d))
1575 address = str(line1.text())
1576 label = unicode(line2.text())
1578 if not is_valid(address):
1579 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1582 self.wallet.add_contact(address)
1584 self.wallet.set_label(address, label)
1586 self.update_contacts_tab()
1587 self.update_history_tab()
1588 self.update_completions()
1589 self.tabs.setCurrentIndex(3)
1593 def new_account_dialog(self, password):
1595 dialog = QDialog(self)
1597 dialog.setWindowTitle(_("New Account"))
1599 vbox = QVBoxLayout()
1600 vbox.addWidget(QLabel(_('Account name')+':'))
1603 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1604 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1609 vbox.addLayout(ok_cancel_buttons(dialog))
1610 dialog.setLayout(vbox)
1614 name = str(e.text())
1617 self.wallet.create_pending_account(name, password)
1618 self.update_receive_tab()
1619 self.tabs.setCurrentIndex(2)
1624 def show_master_public_keys(self):
1626 dialog = QDialog(self)
1628 dialog.setWindowTitle(_("Master Public Keys"))
1630 main_layout = QGridLayout()
1631 mpk_dict = self.wallet.get_master_public_keys()
1633 for key, value in mpk_dict.items():
1634 main_layout.addWidget(QLabel(key), i, 0)
1635 mpk_text = QTextEdit()
1636 mpk_text.setReadOnly(True)
1637 mpk_text.setMaximumHeight(170)
1638 mpk_text.setText(value)
1639 main_layout.addWidget(mpk_text, i + 1, 0)
1642 vbox = QVBoxLayout()
1643 vbox.addLayout(main_layout)
1644 vbox.addLayout(close_button(dialog))
1646 dialog.setLayout(vbox)
1651 def show_seed_dialog(self, password):
1652 if not self.wallet.has_seed():
1653 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1657 mnemonic = self.wallet.get_mnemonic(password)
1659 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1661 from seed_dialog import SeedDialog
1662 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1667 def show_qrcode(self, data, title = _("QR code")):
1671 d.setWindowTitle(title)
1672 d.setMinimumSize(270, 300)
1673 vbox = QVBoxLayout()
1674 qrw = QRCodeWidget(data)
1675 vbox.addWidget(qrw, 1)
1676 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1677 hbox = QHBoxLayout()
1680 filename = os.path.join(self.config.path, "qrcode.bmp")
1683 bmp.save_qrcode(qrw.qr, filename)
1684 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1686 def copy_to_clipboard():
1687 bmp.save_qrcode(qrw.qr, filename)
1688 self.app.clipboard().setImage(QImage(filename))
1689 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1691 b = QPushButton(_("Copy"))
1693 b.clicked.connect(copy_to_clipboard)
1695 b = QPushButton(_("Save"))
1697 b.clicked.connect(print_qr)
1699 b = QPushButton(_("Close"))
1701 b.clicked.connect(d.accept)
1704 vbox.addLayout(hbox)
1709 def do_protect(self, func, args):
1710 if self.wallet.use_encryption:
1711 password = self.password_dialog()
1717 if args != (False,):
1718 args = (self,) + args + (password,)
1720 args = (self,password)
1724 def show_public_keys(self, address):
1725 if not address: return
1727 pubkey_list = self.wallet.get_public_keys(address)
1728 except Exception as e:
1729 traceback.print_exc(file=sys.stdout)
1730 self.show_message(str(e))
1734 d.setMinimumSize(600, 200)
1736 vbox = QVBoxLayout()
1737 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1738 vbox.addWidget( QLabel(_("Public key") + ':'))
1740 keys.setReadOnly(True)
1741 keys.setText('\n'.join(pubkey_list))
1742 vbox.addWidget(keys)
1743 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1744 vbox.addLayout(close_button(d))
1749 def show_private_key(self, address, password):
1750 if not address: return
1752 pk_list = self.wallet.get_private_key(address, password)
1753 except Exception as e:
1754 traceback.print_exc(file=sys.stdout)
1755 self.show_message(str(e))
1759 d.setMinimumSize(600, 200)
1761 vbox = QVBoxLayout()
1762 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1763 vbox.addWidget( QLabel(_("Private key") + ':'))
1765 keys.setReadOnly(True)
1766 keys.setText('\n'.join(pk_list))
1767 vbox.addWidget(keys)
1768 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1769 vbox.addLayout(close_button(d))
1775 def do_sign(self, address, message, signature, password):
1776 message = unicode(message.toPlainText())
1777 message = message.encode('utf-8')
1779 sig = self.wallet.sign_message(str(address.text()), message, password)
1780 signature.setText(sig)
1781 except Exception as e:
1782 self.show_message(str(e))
1784 def do_verify(self, address, message, signature):
1785 message = unicode(message.toPlainText())
1786 message = message.encode('utf-8')
1787 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1788 self.show_message(_("Signature verified"))
1790 self.show_message(_("Error: wrong signature"))
1793 def sign_verify_message(self, address=''):
1796 d.setWindowTitle(_('Sign/verify Message'))
1797 d.setMinimumSize(410, 290)
1799 layout = QGridLayout(d)
1801 message_e = QTextEdit()
1802 layout.addWidget(QLabel(_('Message')), 1, 0)
1803 layout.addWidget(message_e, 1, 1)
1804 layout.setRowStretch(2,3)
1806 address_e = QLineEdit()
1807 address_e.setText(address)
1808 layout.addWidget(QLabel(_('Address')), 2, 0)
1809 layout.addWidget(address_e, 2, 1)
1811 signature_e = QTextEdit()
1812 layout.addWidget(QLabel(_('Signature')), 3, 0)
1813 layout.addWidget(signature_e, 3, 1)
1814 layout.setRowStretch(3,1)
1816 hbox = QHBoxLayout()
1818 b = QPushButton(_("Sign"))
1819 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1822 b = QPushButton(_("Verify"))
1823 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1826 b = QPushButton(_("Close"))
1827 b.clicked.connect(d.accept)
1829 layout.addLayout(hbox, 4, 1)
1834 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1836 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1837 message_e.setText(decrypted)
1838 except Exception as e:
1839 self.show_message(str(e))
1842 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1843 message = unicode(message_e.toPlainText())
1844 message = message.encode('utf-8')
1846 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1847 encrypted_e.setText(encrypted)
1848 except Exception as e:
1849 self.show_message(str(e))
1853 def encrypt_message(self, address = ''):
1856 d.setWindowTitle(_('Encrypt/decrypt Message'))
1857 d.setMinimumSize(610, 490)
1859 layout = QGridLayout(d)
1861 message_e = QTextEdit()
1862 layout.addWidget(QLabel(_('Message')), 1, 0)
1863 layout.addWidget(message_e, 1, 1)
1864 layout.setRowStretch(2,3)
1866 pubkey_e = QLineEdit()
1868 pubkey = self.wallet.getpubkeys(address)[0]
1869 pubkey_e.setText(pubkey)
1870 layout.addWidget(QLabel(_('Public key')), 2, 0)
1871 layout.addWidget(pubkey_e, 2, 1)
1873 encrypted_e = QTextEdit()
1874 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1875 layout.addWidget(encrypted_e, 3, 1)
1876 layout.setRowStretch(3,1)
1878 hbox = QHBoxLayout()
1879 b = QPushButton(_("Encrypt"))
1880 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1883 b = QPushButton(_("Decrypt"))
1884 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1887 b = QPushButton(_("Close"))
1888 b.clicked.connect(d.accept)
1891 layout.addLayout(hbox, 4, 1)
1895 def question(self, msg):
1896 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1898 def show_message(self, msg):
1899 QMessageBox.information(self, _('Message'), msg, _('OK'))
1901 def password_dialog(self, msg=None):
1904 d.setWindowTitle(_("Enter Password"))
1909 vbox = QVBoxLayout()
1911 msg = _('Please enter your password')
1912 vbox.addWidget(QLabel(msg))
1914 grid = QGridLayout()
1916 grid.addWidget(QLabel(_('Password')), 1, 0)
1917 grid.addWidget(pw, 1, 1)
1918 vbox.addLayout(grid)
1920 vbox.addLayout(ok_cancel_buttons(d))
1923 run_hook('password_dialog', pw, grid, 1)
1924 if not d.exec_(): return
1925 return unicode(pw.text())
1934 def tx_from_text(self, txt):
1935 "json or raw hexadecimal"
1938 tx = Transaction(txt)
1944 tx_dict = json.loads(str(txt))
1945 assert "hex" in tx_dict.keys()
1946 tx = Transaction(tx_dict["hex"])
1947 if tx_dict.has_key("input_info"):
1948 input_info = json.loads(tx_dict['input_info'])
1949 tx.add_input_info(input_info)
1952 traceback.print_exc(file=sys.stdout)
1955 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1959 def read_tx_from_file(self):
1960 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1964 with open(fileName, "r") as f:
1965 file_content = f.read()
1966 except (ValueError, IOError, os.error), reason:
1967 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1969 return self.tx_from_text(file_content)
1973 def sign_raw_transaction(self, tx, input_info, password):
1974 self.wallet.signrawtransaction(tx, input_info, [], password)
1976 def do_process_from_text(self):
1977 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1980 tx = self.tx_from_text(text)
1982 self.show_transaction(tx)
1984 def do_process_from_file(self):
1985 tx = self.read_tx_from_file()
1987 self.show_transaction(tx)
1989 def do_process_from_txid(self):
1990 from electrum import transaction
1991 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1993 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1995 tx = transaction.Transaction(r)
1997 self.show_transaction(tx)
1999 self.show_message("unknown transaction")
2001 def do_process_from_csvReader(self, csvReader):
2006 for position, row in enumerate(csvReader):
2008 if not is_valid(address):
2009 errors.append((position, address))
2011 amount = Decimal(row[1])
2012 amount = int(100000000*amount)
2013 outputs.append((address, amount))
2014 except (ValueError, IOError, os.error), reason:
2015 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2019 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
2020 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
2024 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
2025 except Exception as e:
2026 self.show_message(str(e))
2029 self.show_transaction(tx)
2031 def do_process_from_csv_file(self):
2032 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
2036 with open(fileName, "r") as f:
2037 csvReader = csv.reader(f)
2038 self.do_process_from_csvReader(csvReader)
2039 except (ValueError, IOError, os.error), reason:
2040 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2043 def do_process_from_csv_text(self):
2044 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
2045 + _("Format: address, amount. One output per line"), _("Load CSV"))
2048 f = StringIO.StringIO(text)
2049 csvReader = csv.reader(f)
2050 self.do_process_from_csvReader(csvReader)
2055 def export_privkeys_dialog(self, password):
2056 if self.wallet.is_watching_only():
2057 self.show_message(_("This is a watching-only wallet"))
2061 d.setWindowTitle(_('Private keys'))
2062 d.setMinimumSize(850, 300)
2063 vbox = QVBoxLayout(d)
2065 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
2066 _("Exposing a single private key can compromise your entire wallet!"),
2067 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
2068 vbox.addWidget(QLabel(msg))
2074 defaultname = 'electrum-private-keys.csv'
2075 select_msg = _('Select file to export your private keys to')
2076 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2077 vbox.addLayout(hbox)
2079 h, b = ok_cancel_buttons2(d, _('Export'))
2084 addresses = self.wallet.addresses(True)
2086 def privkeys_thread():
2087 for addr in addresses:
2091 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
2092 d.emit(SIGNAL('computing_privkeys'))
2093 d.emit(SIGNAL('show_privkeys'))
2095 def show_privkeys():
2096 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
2100 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
2101 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
2102 threading.Thread(target=privkeys_thread).start()
2108 filename = filename_e.text()
2113 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
2114 except (IOError, os.error), reason:
2115 export_error_label = _("Electrum was unable to produce a private key-export.")
2116 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2118 except Exception as e:
2119 self.show_message(str(e))
2122 self.show_message(_("Private keys exported."))
2125 def do_export_privkeys(self, fileName, pklist, is_csv):
2126 with open(fileName, "w+") as f:
2128 transaction = csv.writer(f)
2129 transaction.writerow(["address", "private_key"])
2130 for addr, pk in pklist.items():
2131 transaction.writerow(["%34s"%addr,pk])
2134 f.write(json.dumps(pklist, indent = 4))
2137 def do_import_labels(self):
2138 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2139 if not labelsFile: return
2141 f = open(labelsFile, 'r')
2144 for key, value in json.loads(data).items():
2145 self.wallet.set_label(key, value)
2146 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2147 except (IOError, os.error), reason:
2148 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2151 def do_export_labels(self):
2152 labels = self.wallet.labels
2154 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2156 with open(fileName, 'w+') as f:
2157 json.dump(labels, f)
2158 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2159 except (IOError, os.error), reason:
2160 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2163 def export_history_dialog(self):
2166 d.setWindowTitle(_('Export History'))
2167 d.setMinimumSize(400, 200)
2168 vbox = QVBoxLayout(d)
2170 defaultname = os.path.expanduser('~/electrum-history.csv')
2171 select_msg = _('Select file to export your wallet transactions to')
2173 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2174 vbox.addLayout(hbox)
2178 h, b = ok_cancel_buttons2(d, _('Export'))
2183 filename = filename_e.text()
2188 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2189 except (IOError, os.error), reason:
2190 export_error_label = _("Electrum was unable to produce a transaction export.")
2191 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2194 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2197 def do_export_history(self, wallet, fileName, is_csv):
2198 history = wallet.get_tx_history()
2200 for item in history:
2201 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2203 if timestamp is not None:
2205 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2206 except [RuntimeError, TypeError, NameError] as reason:
2207 time_string = "unknown"
2210 time_string = "unknown"
2212 time_string = "pending"
2214 if value is not None:
2215 value_string = format_satoshis(value, True)
2220 fee_string = format_satoshis(fee, True)
2225 label, is_default_label = wallet.get_label(tx_hash)
2226 label = label.encode('utf-8')
2230 balance_string = format_satoshis(balance, False)
2232 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2234 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2236 with open(fileName, "w+") as f:
2238 transaction = csv.writer(f)
2239 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2241 transaction.writerow(line)
2244 f.write(json.dumps(lines, indent = 4))
2247 def sweep_key_dialog(self):
2249 d.setWindowTitle(_('Sweep private keys'))
2250 d.setMinimumSize(600, 300)
2252 vbox = QVBoxLayout(d)
2253 vbox.addWidget(QLabel(_("Enter private keys")))
2255 keys_e = QTextEdit()
2256 keys_e.setTabChangesFocus(True)
2257 vbox.addWidget(keys_e)
2259 h, address_e = address_field(self.wallet.addresses())
2263 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2264 vbox.addLayout(hbox)
2265 button.setEnabled(False)
2268 addr = str(address_e.text())
2269 if bitcoin.is_address(addr):
2273 pk = str(keys_e.toPlainText()).strip()
2274 if Wallet.is_private_key(pk):
2277 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2278 keys_e.textChanged.connect(f)
2279 address_e.textChanged.connect(f)
2283 fee = self.wallet.fee
2284 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2285 self.show_transaction(tx)
2289 def do_import_privkey(self, password):
2290 if not self.wallet.has_imported_keys():
2291 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2292 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2293 + _('Are you sure you understand what you are doing?'), 3, 4)
2296 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2299 text = str(text).split()
2304 addr = self.wallet.import_key(key, password)
2305 except Exception as e:
2311 addrlist.append(addr)
2313 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2315 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2316 self.update_receive_tab()
2317 self.update_history_tab()
2320 def settings_dialog(self):
2322 d.setWindowTitle(_('Electrum Settings'))
2324 vbox = QVBoxLayout()
2325 grid = QGridLayout()
2326 grid.setColumnStretch(0,1)
2328 nz_label = QLabel(_('Display zeros') + ':')
2329 grid.addWidget(nz_label, 0, 0)
2330 nz_e = AmountEdit(None,True)
2331 nz_e.setText("%d"% self.num_zeros)
2332 grid.addWidget(nz_e, 0, 1)
2333 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2334 grid.addWidget(HelpButton(msg), 0, 2)
2335 if not self.config.is_modifiable('num_zeros'):
2336 for w in [nz_e, nz_label]: w.setEnabled(False)
2338 lang_label=QLabel(_('Language') + ':')
2339 grid.addWidget(lang_label, 1, 0)
2340 lang_combo = QComboBox()
2341 from electrum.i18n import languages
2342 lang_combo.addItems(languages.values())
2344 index = languages.keys().index(self.config.get("language",''))
2347 lang_combo.setCurrentIndex(index)
2348 grid.addWidget(lang_combo, 1, 1)
2349 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2350 if not self.config.is_modifiable('language'):
2351 for w in [lang_combo, lang_label]: w.setEnabled(False)
2354 fee_label = QLabel(_('Transaction fee') + ':')
2355 grid.addWidget(fee_label, 2, 0)
2356 fee_e = BTCAmountEdit(self.get_decimal_point)
2357 fee_e.setAmount(self.wallet.fee)
2358 grid.addWidget(fee_e, 2, 1)
2359 msg = _('Fee per kilobyte of transaction.') + '\n' \
2360 + _('Recommended value') + ': ' + self.format_amount(10000) + ' ' + self.base_unit()
2361 grid.addWidget(HelpButton(msg), 2, 2)
2362 if not self.config.is_modifiable('fee_per_kb'):
2363 for w in [fee_e, fee_label]: w.setEnabled(False)
2365 units = ['BTC', 'mBTC']
2366 unit_label = QLabel(_('Base unit') + ':')
2367 grid.addWidget(unit_label, 3, 0)
2368 unit_combo = QComboBox()
2369 unit_combo.addItems(units)
2370 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2371 grid.addWidget(unit_combo, 3, 1)
2372 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2373 + '\n1BTC=1000mBTC.\n' \
2374 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2376 usechange_cb = QCheckBox(_('Use change addresses'))
2377 usechange_cb.setChecked(self.wallet.use_change)
2378 grid.addWidget(usechange_cb, 4, 0)
2379 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2380 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2382 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2383 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2384 grid.addWidget(block_ex_label, 5, 0)
2385 block_ex_combo = QComboBox()
2386 block_ex_combo.addItems(block_explorers)
2387 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2388 grid.addWidget(block_ex_combo, 5, 1)
2389 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2391 show_tx = self.config.get('show_before_broadcast', False)
2392 showtx_cb = QCheckBox(_('Show before broadcast'))
2393 showtx_cb.setChecked(show_tx)
2394 grid.addWidget(showtx_cb, 6, 0)
2395 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2397 vbox.addLayout(grid)
2399 vbox.addLayout(ok_cancel_buttons(d))
2403 if not d.exec_(): return
2405 fee = fee_e.get_amount()
2407 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2410 self.wallet.set_fee(fee)
2412 nz = unicode(nz_e.text())
2417 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2420 if self.num_zeros != nz:
2422 self.config.set_key('num_zeros', nz, True)
2423 self.update_history_tab()
2424 self.update_receive_tab()
2426 usechange_result = usechange_cb.isChecked()
2427 if self.wallet.use_change != usechange_result:
2428 self.wallet.use_change = usechange_result
2429 self.wallet.storage.put('use_change', self.wallet.use_change)
2431 if showtx_cb.isChecked() != show_tx:
2432 self.config.set_key('show_before_broadcast', not show_tx)
2434 unit_result = units[unit_combo.currentIndex()]
2435 if self.base_unit() != unit_result:
2436 self.decimal_point = 8 if unit_result == 'BTC' else 5
2437 self.config.set_key('decimal_point', self.decimal_point, True)
2438 self.update_history_tab()
2439 self.update_status()
2441 need_restart = False
2443 lang_request = languages.keys()[lang_combo.currentIndex()]
2444 if lang_request != self.config.get('language'):
2445 self.config.set_key("language", lang_request, True)
2448 be_result = block_explorers[block_ex_combo.currentIndex()]
2449 self.config.set_key('block_explorer', be_result, True)
2451 run_hook('close_settings_dialog')
2454 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2457 def run_network_dialog(self):
2458 if not self.network:
2460 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2462 def closeEvent(self, event):
2464 self.config.set_key("is_maximized", self.isMaximized())
2465 if not self.isMaximized():
2467 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2468 self.save_column_widths()
2469 self.config.set_key("console-history", self.console.history[-50:], True)
2470 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2474 def plugins_dialog(self):
2475 from electrum.plugins import plugins
2478 d.setWindowTitle(_('Electrum Plugins'))
2481 vbox = QVBoxLayout(d)
2484 scroll = QScrollArea()
2485 scroll.setEnabled(True)
2486 scroll.setWidgetResizable(True)
2487 scroll.setMinimumSize(400,250)
2488 vbox.addWidget(scroll)
2492 w.setMinimumHeight(len(plugins)*35)
2494 grid = QGridLayout()
2495 grid.setColumnStretch(0,1)
2498 def do_toggle(cb, p, w):
2501 if w: w.setEnabled(r)
2503 def mk_toggle(cb, p, w):
2504 return lambda: do_toggle(cb,p,w)
2506 for i, p in enumerate(plugins):
2508 cb = QCheckBox(p.fullname())
2509 cb.setDisabled(not p.is_available())
2510 cb.setChecked(p.is_enabled())
2511 grid.addWidget(cb, i, 0)
2512 if p.requires_settings():
2513 w = p.settings_widget(self)
2514 w.setEnabled( p.is_enabled() )
2515 grid.addWidget(w, i, 1)
2518 cb.clicked.connect(mk_toggle(cb,p,w))
2519 grid.addWidget(HelpButton(p.description()), i, 2)
2521 print_msg(_("Error: cannot display plugin"), p)
2522 traceback.print_exc(file=sys.stdout)
2523 grid.setRowStretch(i+1,1)
2525 vbox.addLayout(close_button(d))
2530 def show_account_details(self, k):
2531 account = self.wallet.accounts[k]
2534 d.setWindowTitle(_('Account Details'))
2537 vbox = QVBoxLayout(d)
2538 name = self.wallet.get_account_name(k)
2539 label = QLabel('Name: ' + name)
2540 vbox.addWidget(label)
2542 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2544 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2546 vbox.addWidget(QLabel(_('Master Public Key:')))
2549 text.setReadOnly(True)
2550 text.setMaximumHeight(170)
2551 vbox.addWidget(text)
2553 mpk_text = '\n'.join( account.get_master_pubkeys() )
2554 text.setText(mpk_text)
2556 vbox.addLayout(close_button(d))