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):
811 label = unicode( self.message_e.text() )
813 if self.payment_request:
814 outputs = self.payment_request.get_outputs()
816 outputs = self.payto_e.get_outputs()
819 QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
822 for addr, x in outputs:
823 if addr is None or not bitcoin.is_address(addr):
824 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address'), _('OK'))
827 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
830 amount = sum(map(lambda x:x[1], outputs))
832 fee = self.fee_e.get_amount()
834 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
837 confirm_amount = self.config.get('confirm_amount', 100000000)
838 if amount >= confirm_amount:
839 o = '\n'.join(map(lambda x:x[0], outputs))
840 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
843 confirm_fee = self.config.get('confirm_fee', 100000)
844 if fee >= confirm_fee:
845 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()}):
848 coins = self.get_coins()
849 return outputs, fee, label, coins
853 r = self.read_send_tab()
856 outputs, fee, label, coins = r
857 self.send_tx(outputs, fee, label, coins)
861 def send_tx(self, outputs, fee, label, coins, password):
862 self.send_button.setDisabled(True)
864 # first, create an unsigned tx
866 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
868 except Exception as e:
869 traceback.print_exc(file=sys.stdout)
870 self.show_message(str(e))
871 self.send_button.setDisabled(False)
874 # call hook to see if plugin needs gui interaction
875 run_hook('send_tx', tx)
881 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
882 self.wallet.sign_transaction(tx, keypairs, password)
883 return tx, fee, label
885 def sign_done(tx, fee, label):
887 self.show_message(tx.error)
888 self.send_button.setDisabled(False)
890 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
891 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
892 self.send_button.setDisabled(False)
895 self.wallet.set_label(tx.hash(), label)
897 if not tx.is_complete() or self.config.get('show_before_broadcast'):
898 self.show_transaction(tx)
900 self.send_button.setDisabled(False)
903 self.broadcast_transaction(tx)
905 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
906 self.waiting_dialog.start()
910 def broadcast_transaction(self, tx):
912 def broadcast_thread():
913 pr = self.payment_request
915 return self.wallet.sendtx(tx)
918 self.payment_request = None
919 return False, _("Payment request has expired")
921 status, msg = self.wallet.sendtx(tx)
925 self.invoices[pr.get_id()] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), PR_PAID, tx.hash())
926 self.wallet.storage.put('invoices', self.invoices)
927 self.update_invoices_tab()
928 self.payment_request = None
929 refund_address = self.wallet.addresses()[0]
930 ack_status, ack_msg = pr.send_ack(str(tx), refund_address)
936 def broadcast_done(status, msg):
938 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
941 QMessageBox.warning(self, _('Error'), msg, _('OK'))
942 self.send_button.setDisabled(False)
944 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
945 self.waiting_dialog.start()
949 def prepare_for_payment_request(self):
950 self.tabs.setCurrentIndex(1)
951 self.payto_e.is_pr = True
952 for e in [self.payto_e, self.amount_e, self.message_e]:
954 for h in [self.payto_help, self.amount_help, self.message_help]:
956 self.payto_e.setText(_("please wait..."))
959 def payment_request_ok(self):
960 pr = self.payment_request
962 if pr_id not in self.invoices:
963 self.invoices[pr_id] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), PR_UNPAID, None)
964 self.wallet.storage.put('invoices', self.invoices)
965 self.update_invoices_tab()
967 print_error('invoice already in list')
969 status = self.invoices[pr_id][3]
970 if status == PR_PAID:
972 self.show_message("invoice already paid")
973 self.payment_request = None
976 self.payto_help.show()
977 self.payto_help.set_alt(lambda: self.show_pr_details(pr))
979 self.payto_e.setGreen()
980 self.payto_e.setText(pr.domain)
981 self.amount_e.setText(self.format_amount(pr.get_amount()))
982 self.message_e.setText(pr.get_memo())
984 def payment_request_error(self):
986 self.show_message(self.payment_request.error)
987 self.payment_request = None
989 def pay_from_URI(self,URI):
992 address, amount, label, message, request_url = util.parse_URI(URI)
994 address, amount, label, message, request_url = util.parse_URI(URI)
995 except Exception as e:
996 QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URI:') + '\n' + str(e), _('OK'))
999 self.tabs.setCurrentIndex(1)
1003 if self.wallet.labels.get(address) != label:
1004 if self.question(_('Save label "%s" for address %s ?'%(label,address))):
1005 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1006 self.wallet.addressbook.append(address)
1007 self.wallet.set_label(address, label)
1009 label = self.wallet.labels.get(address)
1011 self.payto_e.setText(label + ' <'+ address +'>' if label else address)
1013 self.message_e.setText(message)
1015 self.amount_e.setAmount(amount)
1018 from electrum import paymentrequest
1019 def payment_request():
1020 self.payment_request = paymentrequest.PaymentRequest(self.config)
1021 self.payment_request.read(request_url)
1022 if self.payment_request.verify():
1023 self.emit(SIGNAL('payment_request_ok'))
1025 self.emit(SIGNAL('payment_request_error'))
1027 self.pr_thread = threading.Thread(target=payment_request).start()
1028 self.prepare_for_payment_request()
1033 self.payto_e.is_pr = False
1034 self.payto_sig.setVisible(False)
1035 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1039 for h in [self.payto_help, self.amount_help, self.message_help]:
1042 self.payto_help.set_alt(None)
1043 self.set_pay_from([])
1044 self.update_status()
1048 def set_addrs_frozen(self,addrs,freeze):
1050 if not addr: continue
1051 if addr in self.wallet.frozen_addresses and not freeze:
1052 self.wallet.unfreeze(addr)
1053 elif addr not in self.wallet.frozen_addresses and freeze:
1054 self.wallet.freeze(addr)
1055 self.update_receive_tab()
1059 def create_list_tab(self, headers):
1060 "generic tab creation method"
1061 l = MyTreeWidget(self)
1062 l.setColumnCount( len(headers) )
1063 l.setHeaderLabels( headers )
1066 vbox = QVBoxLayout()
1073 vbox.addWidget(buttons)
1078 def create_receive_tab(self):
1079 l, w = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1080 for i,width in enumerate(self.column_widths['receive']):
1081 l.setColumnWidth(i, width)
1082 l.setContextMenuPolicy(Qt.CustomContextMenu)
1083 l.customContextMenuRequested.connect(self.create_receive_menu)
1084 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1085 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1086 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1087 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1088 self.receive_list = l
1094 def save_column_widths(self):
1095 self.column_widths["receive"] = []
1096 for i in range(self.receive_list.columnCount() -1):
1097 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1099 self.column_widths["history"] = []
1100 for i in range(self.history_list.columnCount() - 1):
1101 self.column_widths["history"].append(self.history_list.columnWidth(i))
1103 self.column_widths["contacts"] = []
1104 for i in range(self.contacts_list.columnCount() - 1):
1105 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1107 self.config.set_key("column_widths_2", self.column_widths, True)
1110 def create_contacts_tab(self):
1111 l, w = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1112 l.setContextMenuPolicy(Qt.CustomContextMenu)
1113 l.customContextMenuRequested.connect(self.create_contact_menu)
1114 for i,width in enumerate(self.column_widths['contacts']):
1115 l.setColumnWidth(i, width)
1116 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1117 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1118 self.contacts_list = l
1122 def create_invoices_tab(self):
1123 l, w = self.create_list_tab([_('Requestor'), _('Memo'),_('Amount'), _('Status')])
1125 h.setStretchLastSection(False)
1126 h.setResizeMode(1, QHeaderView.Stretch)
1127 l.setContextMenuPolicy(Qt.CustomContextMenu)
1128 l.customContextMenuRequested.connect(self.create_invoice_menu)
1129 self.invoices_list = l
1132 def update_invoices_tab(self):
1133 invoices = self.wallet.storage.get('invoices', {})
1134 l = self.invoices_list
1136 for key, value in invoices.items():
1138 domain, memo, amount, status, tx_hash = value
1142 item = QTreeWidgetItem( [ domain, memo, self.format_amount(amount), format_status(status)] )
1143 l.addTopLevelItem(item)
1145 l.setCurrentItem(l.topLevelItem(0))
1149 def delete_imported_key(self, addr):
1150 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1151 self.wallet.delete_imported_key(addr)
1152 self.update_receive_tab()
1153 self.update_history_tab()
1155 def edit_account_label(self, k):
1156 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1158 label = unicode(text)
1159 self.wallet.set_label(k,label)
1160 self.update_receive_tab()
1162 def account_set_expanded(self, item, k, b):
1164 self.accounts_expanded[k] = b
1166 def create_account_menu(self, position, k, item):
1168 if item.isExpanded():
1169 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1171 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1172 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1173 if self.wallet.seed_version > 4:
1174 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1175 if self.wallet.account_is_pending(k):
1176 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1177 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1179 def delete_pending_account(self, k):
1180 self.wallet.delete_pending_account(k)
1181 self.update_receive_tab()
1183 def create_receive_menu(self, position):
1184 # fixme: this function apparently has a side effect.
1185 # if it is not called the menu pops up several times
1186 #self.receive_list.selectedIndexes()
1188 selected = self.receive_list.selectedItems()
1189 multi_select = len(selected) > 1
1190 addrs = [unicode(item.text(0)) for item in selected]
1191 if not multi_select:
1192 item = self.receive_list.itemAt(position)
1196 if not is_valid(addr):
1197 k = str(item.data(0,32).toString())
1199 self.create_account_menu(position, k, item)
1201 item.setExpanded(not item.isExpanded())
1205 if not multi_select:
1206 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1207 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1208 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1209 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1210 if not self.wallet.is_watching_only():
1211 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1212 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1213 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1214 if self.wallet.is_imported(addr):
1215 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1217 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1218 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1219 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1220 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1222 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1223 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1225 run_hook('receive_menu', menu, addrs)
1226 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1229 def get_sendable_balance(self):
1230 return sum(map(lambda x:x['value'], self.get_coins()))
1233 def get_coins(self):
1235 return self.pay_from
1237 domain = self.wallet.get_account_addresses(self.current_account)
1238 for i in self.wallet.frozen_addresses:
1239 if i in domain: domain.remove(i)
1240 return self.wallet.get_unspent_coins(domain)
1243 def send_from_addresses(self, addrs):
1244 self.set_pay_from( addrs )
1245 self.tabs.setCurrentIndex(1)
1248 def payto(self, addr):
1250 label = self.wallet.labels.get(addr)
1251 m_addr = label + ' <' + addr + '>' if label else addr
1252 self.tabs.setCurrentIndex(1)
1253 self.payto_e.setText(m_addr)
1254 self.amount_e.setFocus()
1257 def delete_contact(self, x):
1258 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1259 self.wallet.delete_contact(x)
1260 self.wallet.set_label(x, None)
1261 self.update_history_tab()
1262 self.update_contacts_tab()
1263 self.update_completions()
1266 def create_contact_menu(self, position):
1267 item = self.contacts_list.itemAt(position)
1270 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1272 addr = unicode(item.text(0))
1273 label = unicode(item.text(1))
1274 is_editable = item.data(0,32).toBool()
1275 payto_addr = item.data(0,33).toString()
1276 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1277 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1278 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1280 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1281 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1283 run_hook('create_contact_menu', menu, item)
1284 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1286 def delete_invoice(self, key):
1287 self.invoices.pop(key)
1288 self.wallet.storage.put('invoices', self.invoices)
1289 self.update_invoices_tab()
1291 def show_invoice(self, key):
1292 from electrum.paymentrequest import PaymentRequest
1293 domain, memo, value, status, tx_hash = self.invoices[key]
1294 pr = PaymentRequest(self.config)
1298 self.show_pr_details(pr)
1300 def show_pr_details(self, pr):
1301 msg = 'Domain: ' + pr.domain
1302 msg += '\nStatus: ' + pr.get_status()
1303 msg += '\nMemo: ' + pr.get_memo()
1304 msg += '\nPayment URL: ' + pr.payment_url
1305 msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[0] + ' ' + self.format_amount(x[1])+ self.base_unit(), pr.get_outputs()))
1306 QMessageBox.information(self, 'Invoice', msg , 'OK')
1308 def do_pay_invoice(self, key):
1309 from electrum.paymentrequest import PaymentRequest
1310 domain, memo, value, status, tx_hash = self.invoices[key]
1311 pr = PaymentRequest(self.config)
1314 self.payment_request = pr
1315 self.prepare_for_payment_request()
1317 self.payment_request_ok()
1319 self.payment_request_error()
1322 def create_invoice_menu(self, position):
1323 item = self.invoices_list.itemAt(position)
1326 k = self.invoices_list.indexOfTopLevelItem(item)
1327 key = self.invoices.keys()[k]
1328 domain, memo, value, status, tx_hash = self.invoices[key]
1330 menu.addAction(_("Details"), lambda: self.show_invoice(key))
1331 if status == PR_UNPAID:
1332 menu.addAction(_("Pay Now"), lambda: self.do_pay_invoice(key))
1333 menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
1334 menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
1337 def update_receive_item(self, item):
1338 item.setFont(0, QFont(MONOSPACE_FONT))
1339 address = str(item.data(0,0).toString())
1340 label = self.wallet.labels.get(address,'')
1341 item.setData(1,0,label)
1342 item.setData(0,32, True) # is editable
1344 run_hook('update_receive_item', address, item)
1346 if not self.wallet.is_mine(address): return
1348 c, u = self.wallet.get_addr_balance(address)
1349 balance = self.format_amount(c + u)
1350 item.setData(2,0,balance)
1352 if address in self.wallet.frozen_addresses:
1353 item.setBackgroundColor(0, QColor('lightblue'))
1356 def update_receive_tab(self):
1357 l = self.receive_list
1358 # extend the syntax for consistency
1359 l.addChild = l.addTopLevelItem
1360 l.insertChild = l.insertTopLevelItem
1364 accounts = self.wallet.get_accounts()
1365 if self.current_account is None:
1366 account_items = sorted(accounts.items())
1368 account_items = [(self.current_account, accounts.get(self.current_account))]
1371 for k, account in account_items:
1373 if len(accounts) > 1:
1374 name = self.wallet.get_account_name(k)
1375 c,u = self.wallet.get_account_balance(k)
1376 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1377 l.addTopLevelItem(account_item)
1378 account_item.setExpanded(self.accounts_expanded.get(k, True))
1379 account_item.setData(0, 32, k)
1383 sequences = [0,1] if account.has_change() else [0]
1384 for is_change in sequences:
1385 if len(sequences) > 1:
1386 name = _("Receiving") if not is_change else _("Change")
1387 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1388 account_item.addChild(seq_item)
1390 seq_item.setExpanded(True)
1392 seq_item = account_item
1394 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1400 for address in account.get_addresses(is_change):
1402 num, is_used = self.wallet.is_used(address)
1405 if gap > self.wallet.gap_limit:
1410 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1411 self.update_receive_item(item)
1413 item.setBackgroundColor(1, QColor('red'))
1417 seq_item.insertChild(0,used_item)
1419 used_item.addChild(item)
1421 seq_item.addChild(item)
1423 # we use column 1 because column 0 may be hidden
1424 l.setCurrentItem(l.topLevelItem(0),1)
1427 def update_contacts_tab(self):
1428 l = self.contacts_list
1431 for address in self.wallet.addressbook:
1432 label = self.wallet.labels.get(address,'')
1433 n = self.wallet.get_num_tx(address)
1434 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1435 item.setFont(0, QFont(MONOSPACE_FONT))
1436 # 32 = label can be edited (bool)
1437 item.setData(0,32, True)
1439 item.setData(0,33, address)
1440 l.addTopLevelItem(item)
1442 run_hook('update_contacts_tab', l)
1443 l.setCurrentItem(l.topLevelItem(0))
1447 def create_console_tab(self):
1448 from console import Console
1449 self.console = console = Console()
1453 def update_console(self):
1454 console = self.console
1455 console.history = self.config.get("console-history",[])
1456 console.history_index = len(console.history)
1458 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1459 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1461 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1463 def mkfunc(f, method):
1464 return lambda *args: apply( f, (method, args, self.password_dialog ))
1466 if m[0]=='_' or m in ['network','wallet']: continue
1467 methods[m] = mkfunc(c._run, m)
1469 console.updateNamespace(methods)
1472 def change_account(self,s):
1473 if s == _("All accounts"):
1474 self.current_account = None
1476 accounts = self.wallet.get_account_names()
1477 for k, v in accounts.items():
1479 self.current_account = k
1480 self.update_history_tab()
1481 self.update_status()
1482 self.update_receive_tab()
1484 def create_status_bar(self):
1487 sb.setFixedHeight(35)
1488 qtVersion = qVersion()
1490 self.balance_label = QLabel("")
1491 sb.addWidget(self.balance_label)
1493 from version_getter import UpdateLabel
1494 self.updatelabel = UpdateLabel(self.config, sb)
1496 self.account_selector = QComboBox()
1497 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1498 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1499 sb.addPermanentWidget(self.account_selector)
1501 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1502 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1504 self.lock_icon = QIcon()
1505 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1506 sb.addPermanentWidget( self.password_button )
1508 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1509 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1510 sb.addPermanentWidget( self.seed_button )
1511 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1512 sb.addPermanentWidget( self.status_button )
1514 run_hook('create_status_bar', (sb,))
1516 self.setStatusBar(sb)
1519 def update_lock_icon(self):
1520 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1521 self.password_button.setIcon( icon )
1524 def update_buttons_on_seed(self):
1525 if self.wallet.has_seed():
1526 self.seed_button.show()
1528 self.seed_button.hide()
1530 if not self.wallet.is_watching_only():
1531 self.password_button.show()
1532 self.send_button.setText(_("Send"))
1534 self.password_button.hide()
1535 self.send_button.setText(_("Create unsigned transaction"))
1538 def change_password_dialog(self):
1539 from password_dialog import PasswordDialog
1540 d = PasswordDialog(self.wallet, self)
1542 self.update_lock_icon()
1545 def new_contact_dialog(self):
1548 d.setWindowTitle(_("New Contact"))
1549 vbox = QVBoxLayout(d)
1550 vbox.addWidget(QLabel(_('New Contact')+':'))
1552 grid = QGridLayout()
1555 grid.addWidget(QLabel(_("Address")), 1, 0)
1556 grid.addWidget(line1, 1, 1)
1557 grid.addWidget(QLabel(_("Name")), 2, 0)
1558 grid.addWidget(line2, 2, 1)
1560 vbox.addLayout(grid)
1561 vbox.addLayout(ok_cancel_buttons(d))
1566 address = str(line1.text())
1567 label = unicode(line2.text())
1569 if not is_valid(address):
1570 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1573 self.wallet.add_contact(address)
1575 self.wallet.set_label(address, label)
1577 self.update_contacts_tab()
1578 self.update_history_tab()
1579 self.update_completions()
1580 self.tabs.setCurrentIndex(3)
1584 def new_account_dialog(self, password):
1586 dialog = QDialog(self)
1588 dialog.setWindowTitle(_("New Account"))
1590 vbox = QVBoxLayout()
1591 vbox.addWidget(QLabel(_('Account name')+':'))
1594 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1595 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1600 vbox.addLayout(ok_cancel_buttons(dialog))
1601 dialog.setLayout(vbox)
1605 name = str(e.text())
1608 self.wallet.create_pending_account(name, password)
1609 self.update_receive_tab()
1610 self.tabs.setCurrentIndex(2)
1615 def show_master_public_keys(self):
1617 dialog = QDialog(self)
1619 dialog.setWindowTitle(_("Master Public Keys"))
1621 main_layout = QGridLayout()
1622 mpk_dict = self.wallet.get_master_public_keys()
1624 for key, value in mpk_dict.items():
1625 main_layout.addWidget(QLabel(key), i, 0)
1626 mpk_text = QTextEdit()
1627 mpk_text.setReadOnly(True)
1628 mpk_text.setMaximumHeight(170)
1629 mpk_text.setText(value)
1630 main_layout.addWidget(mpk_text, i + 1, 0)
1633 vbox = QVBoxLayout()
1634 vbox.addLayout(main_layout)
1635 vbox.addLayout(close_button(dialog))
1637 dialog.setLayout(vbox)
1642 def show_seed_dialog(self, password):
1643 if not self.wallet.has_seed():
1644 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1648 mnemonic = self.wallet.get_mnemonic(password)
1650 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1652 from seed_dialog import SeedDialog
1653 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1658 def show_qrcode(self, data, title = _("QR code")):
1662 d.setWindowTitle(title)
1663 d.setMinimumSize(270, 300)
1664 vbox = QVBoxLayout()
1665 qrw = QRCodeWidget(data)
1666 vbox.addWidget(qrw, 1)
1667 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1668 hbox = QHBoxLayout()
1671 filename = os.path.join(self.config.path, "qrcode.bmp")
1674 bmp.save_qrcode(qrw.qr, filename)
1675 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1677 def copy_to_clipboard():
1678 bmp.save_qrcode(qrw.qr, filename)
1679 self.app.clipboard().setImage(QImage(filename))
1680 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1682 b = QPushButton(_("Copy"))
1684 b.clicked.connect(copy_to_clipboard)
1686 b = QPushButton(_("Save"))
1688 b.clicked.connect(print_qr)
1690 b = QPushButton(_("Close"))
1692 b.clicked.connect(d.accept)
1695 vbox.addLayout(hbox)
1700 def do_protect(self, func, args):
1701 if self.wallet.use_encryption:
1702 password = self.password_dialog()
1708 if args != (False,):
1709 args = (self,) + args + (password,)
1711 args = (self,password)
1715 def show_public_keys(self, address):
1716 if not address: return
1718 pubkey_list = self.wallet.get_public_keys(address)
1719 except Exception as e:
1720 traceback.print_exc(file=sys.stdout)
1721 self.show_message(str(e))
1725 d.setMinimumSize(600, 200)
1727 vbox = QVBoxLayout()
1728 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1729 vbox.addWidget( QLabel(_("Public key") + ':'))
1731 keys.setReadOnly(True)
1732 keys.setText('\n'.join(pubkey_list))
1733 vbox.addWidget(keys)
1734 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1735 vbox.addLayout(close_button(d))
1740 def show_private_key(self, address, password):
1741 if not address: return
1743 pk_list = self.wallet.get_private_key(address, password)
1744 except Exception as e:
1745 traceback.print_exc(file=sys.stdout)
1746 self.show_message(str(e))
1750 d.setMinimumSize(600, 200)
1752 vbox = QVBoxLayout()
1753 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1754 vbox.addWidget( QLabel(_("Private key") + ':'))
1756 keys.setReadOnly(True)
1757 keys.setText('\n'.join(pk_list))
1758 vbox.addWidget(keys)
1759 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1760 vbox.addLayout(close_button(d))
1766 def do_sign(self, address, message, signature, password):
1767 message = unicode(message.toPlainText())
1768 message = message.encode('utf-8')
1770 sig = self.wallet.sign_message(str(address.text()), message, password)
1771 signature.setText(sig)
1772 except Exception as e:
1773 self.show_message(str(e))
1775 def do_verify(self, address, message, signature):
1776 message = unicode(message.toPlainText())
1777 message = message.encode('utf-8')
1778 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1779 self.show_message(_("Signature verified"))
1781 self.show_message(_("Error: wrong signature"))
1784 def sign_verify_message(self, address=''):
1787 d.setWindowTitle(_('Sign/verify Message'))
1788 d.setMinimumSize(410, 290)
1790 layout = QGridLayout(d)
1792 message_e = QTextEdit()
1793 layout.addWidget(QLabel(_('Message')), 1, 0)
1794 layout.addWidget(message_e, 1, 1)
1795 layout.setRowStretch(2,3)
1797 address_e = QLineEdit()
1798 address_e.setText(address)
1799 layout.addWidget(QLabel(_('Address')), 2, 0)
1800 layout.addWidget(address_e, 2, 1)
1802 signature_e = QTextEdit()
1803 layout.addWidget(QLabel(_('Signature')), 3, 0)
1804 layout.addWidget(signature_e, 3, 1)
1805 layout.setRowStretch(3,1)
1807 hbox = QHBoxLayout()
1809 b = QPushButton(_("Sign"))
1810 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1813 b = QPushButton(_("Verify"))
1814 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1817 b = QPushButton(_("Close"))
1818 b.clicked.connect(d.accept)
1820 layout.addLayout(hbox, 4, 1)
1825 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1827 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1828 message_e.setText(decrypted)
1829 except Exception as e:
1830 self.show_message(str(e))
1833 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1834 message = unicode(message_e.toPlainText())
1835 message = message.encode('utf-8')
1837 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1838 encrypted_e.setText(encrypted)
1839 except Exception as e:
1840 self.show_message(str(e))
1844 def encrypt_message(self, address = ''):
1847 d.setWindowTitle(_('Encrypt/decrypt Message'))
1848 d.setMinimumSize(610, 490)
1850 layout = QGridLayout(d)
1852 message_e = QTextEdit()
1853 layout.addWidget(QLabel(_('Message')), 1, 0)
1854 layout.addWidget(message_e, 1, 1)
1855 layout.setRowStretch(2,3)
1857 pubkey_e = QLineEdit()
1859 pubkey = self.wallet.getpubkeys(address)[0]
1860 pubkey_e.setText(pubkey)
1861 layout.addWidget(QLabel(_('Public key')), 2, 0)
1862 layout.addWidget(pubkey_e, 2, 1)
1864 encrypted_e = QTextEdit()
1865 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1866 layout.addWidget(encrypted_e, 3, 1)
1867 layout.setRowStretch(3,1)
1869 hbox = QHBoxLayout()
1870 b = QPushButton(_("Encrypt"))
1871 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1874 b = QPushButton(_("Decrypt"))
1875 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1878 b = QPushButton(_("Close"))
1879 b.clicked.connect(d.accept)
1882 layout.addLayout(hbox, 4, 1)
1886 def question(self, msg):
1887 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1889 def show_message(self, msg):
1890 QMessageBox.information(self, _('Message'), msg, _('OK'))
1892 def password_dialog(self, msg=None):
1895 d.setWindowTitle(_("Enter Password"))
1900 vbox = QVBoxLayout()
1902 msg = _('Please enter your password')
1903 vbox.addWidget(QLabel(msg))
1905 grid = QGridLayout()
1907 grid.addWidget(QLabel(_('Password')), 1, 0)
1908 grid.addWidget(pw, 1, 1)
1909 vbox.addLayout(grid)
1911 vbox.addLayout(ok_cancel_buttons(d))
1914 run_hook('password_dialog', pw, grid, 1)
1915 if not d.exec_(): return
1916 return unicode(pw.text())
1925 def tx_from_text(self, txt):
1926 "json or raw hexadecimal"
1929 tx = Transaction(txt)
1935 tx_dict = json.loads(str(txt))
1936 assert "hex" in tx_dict.keys()
1937 tx = Transaction(tx_dict["hex"])
1938 if tx_dict.has_key("input_info"):
1939 input_info = json.loads(tx_dict['input_info'])
1940 tx.add_input_info(input_info)
1943 traceback.print_exc(file=sys.stdout)
1946 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1950 def read_tx_from_file(self):
1951 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1955 with open(fileName, "r") as f:
1956 file_content = f.read()
1957 except (ValueError, IOError, os.error), reason:
1958 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1960 return self.tx_from_text(file_content)
1964 def sign_raw_transaction(self, tx, input_info, password):
1965 self.wallet.signrawtransaction(tx, input_info, [], password)
1967 def do_process_from_text(self):
1968 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1971 tx = self.tx_from_text(text)
1973 self.show_transaction(tx)
1975 def do_process_from_file(self):
1976 tx = self.read_tx_from_file()
1978 self.show_transaction(tx)
1980 def do_process_from_txid(self):
1981 from electrum import transaction
1982 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1984 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1986 tx = transaction.Transaction(r)
1988 self.show_transaction(tx)
1990 self.show_message("unknown transaction")
1992 def do_process_from_csvReader(self, csvReader):
1997 for position, row in enumerate(csvReader):
1999 if not is_valid(address):
2000 errors.append((position, address))
2002 amount = Decimal(row[1])
2003 amount = int(100000000*amount)
2004 outputs.append((address, amount))
2005 except (ValueError, IOError, os.error), reason:
2006 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2010 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
2011 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
2015 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
2016 except Exception as e:
2017 self.show_message(str(e))
2020 self.show_transaction(tx)
2022 def do_process_from_csv_file(self):
2023 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
2027 with open(fileName, "r") as f:
2028 csvReader = csv.reader(f)
2029 self.do_process_from_csvReader(csvReader)
2030 except (ValueError, IOError, os.error), reason:
2031 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2034 def do_process_from_csv_text(self):
2035 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
2036 + _("Format: address, amount. One output per line"), _("Load CSV"))
2039 f = StringIO.StringIO(text)
2040 csvReader = csv.reader(f)
2041 self.do_process_from_csvReader(csvReader)
2046 def export_privkeys_dialog(self, password):
2047 if self.wallet.is_watching_only():
2048 self.show_message(_("This is a watching-only wallet"))
2052 d.setWindowTitle(_('Private keys'))
2053 d.setMinimumSize(850, 300)
2054 vbox = QVBoxLayout(d)
2056 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
2057 _("Exposing a single private key can compromise your entire wallet!"),
2058 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
2059 vbox.addWidget(QLabel(msg))
2065 defaultname = 'electrum-private-keys.csv'
2066 select_msg = _('Select file to export your private keys to')
2067 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2068 vbox.addLayout(hbox)
2070 h, b = ok_cancel_buttons2(d, _('Export'))
2075 addresses = self.wallet.addresses(True)
2077 def privkeys_thread():
2078 for addr in addresses:
2082 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
2083 d.emit(SIGNAL('computing_privkeys'))
2084 d.emit(SIGNAL('show_privkeys'))
2086 def show_privkeys():
2087 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
2091 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
2092 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
2093 threading.Thread(target=privkeys_thread).start()
2099 filename = filename_e.text()
2104 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
2105 except (IOError, os.error), reason:
2106 export_error_label = _("Electrum was unable to produce a private key-export.")
2107 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2109 except Exception as e:
2110 self.show_message(str(e))
2113 self.show_message(_("Private keys exported."))
2116 def do_export_privkeys(self, fileName, pklist, is_csv):
2117 with open(fileName, "w+") as f:
2119 transaction = csv.writer(f)
2120 transaction.writerow(["address", "private_key"])
2121 for addr, pk in pklist.items():
2122 transaction.writerow(["%34s"%addr,pk])
2125 f.write(json.dumps(pklist, indent = 4))
2128 def do_import_labels(self):
2129 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2130 if not labelsFile: return
2132 f = open(labelsFile, 'r')
2135 for key, value in json.loads(data).items():
2136 self.wallet.set_label(key, value)
2137 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2138 except (IOError, os.error), reason:
2139 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2142 def do_export_labels(self):
2143 labels = self.wallet.labels
2145 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2147 with open(fileName, 'w+') as f:
2148 json.dump(labels, f)
2149 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2150 except (IOError, os.error), reason:
2151 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2154 def export_history_dialog(self):
2157 d.setWindowTitle(_('Export History'))
2158 d.setMinimumSize(400, 200)
2159 vbox = QVBoxLayout(d)
2161 defaultname = os.path.expanduser('~/electrum-history.csv')
2162 select_msg = _('Select file to export your wallet transactions to')
2164 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2165 vbox.addLayout(hbox)
2169 h, b = ok_cancel_buttons2(d, _('Export'))
2174 filename = filename_e.text()
2179 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2180 except (IOError, os.error), reason:
2181 export_error_label = _("Electrum was unable to produce a transaction export.")
2182 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2185 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2188 def do_export_history(self, wallet, fileName, is_csv):
2189 history = wallet.get_tx_history()
2191 for item in history:
2192 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2194 if timestamp is not None:
2196 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2197 except [RuntimeError, TypeError, NameError] as reason:
2198 time_string = "unknown"
2201 time_string = "unknown"
2203 time_string = "pending"
2205 if value is not None:
2206 value_string = format_satoshis(value, True)
2211 fee_string = format_satoshis(fee, True)
2216 label, is_default_label = wallet.get_label(tx_hash)
2217 label = label.encode('utf-8')
2221 balance_string = format_satoshis(balance, False)
2223 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2225 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2227 with open(fileName, "w+") as f:
2229 transaction = csv.writer(f)
2230 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2232 transaction.writerow(line)
2235 f.write(json.dumps(lines, indent = 4))
2238 def sweep_key_dialog(self):
2240 d.setWindowTitle(_('Sweep private keys'))
2241 d.setMinimumSize(600, 300)
2243 vbox = QVBoxLayout(d)
2244 vbox.addWidget(QLabel(_("Enter private keys")))
2246 keys_e = QTextEdit()
2247 keys_e.setTabChangesFocus(True)
2248 vbox.addWidget(keys_e)
2250 h, address_e = address_field(self.wallet.addresses())
2254 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2255 vbox.addLayout(hbox)
2256 button.setEnabled(False)
2259 addr = str(address_e.text())
2260 if bitcoin.is_address(addr):
2264 pk = str(keys_e.toPlainText()).strip()
2265 if Wallet.is_private_key(pk):
2268 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2269 keys_e.textChanged.connect(f)
2270 address_e.textChanged.connect(f)
2274 fee = self.wallet.fee
2275 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2276 self.show_transaction(tx)
2280 def do_import_privkey(self, password):
2281 if not self.wallet.has_imported_keys():
2282 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2283 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2284 + _('Are you sure you understand what you are doing?'), 3, 4)
2287 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2290 text = str(text).split()
2295 addr = self.wallet.import_key(key, password)
2296 except Exception as e:
2302 addrlist.append(addr)
2304 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2306 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2307 self.update_receive_tab()
2308 self.update_history_tab()
2311 def settings_dialog(self):
2313 d.setWindowTitle(_('Electrum Settings'))
2315 vbox = QVBoxLayout()
2316 grid = QGridLayout()
2317 grid.setColumnStretch(0,1)
2319 nz_label = QLabel(_('Display zeros') + ':')
2320 grid.addWidget(nz_label, 0, 0)
2321 nz_e = AmountEdit(None,True)
2322 nz_e.setText("%d"% self.num_zeros)
2323 grid.addWidget(nz_e, 0, 1)
2324 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2325 grid.addWidget(HelpButton(msg), 0, 2)
2326 if not self.config.is_modifiable('num_zeros'):
2327 for w in [nz_e, nz_label]: w.setEnabled(False)
2329 lang_label=QLabel(_('Language') + ':')
2330 grid.addWidget(lang_label, 1, 0)
2331 lang_combo = QComboBox()
2332 from electrum.i18n import languages
2333 lang_combo.addItems(languages.values())
2335 index = languages.keys().index(self.config.get("language",''))
2338 lang_combo.setCurrentIndex(index)
2339 grid.addWidget(lang_combo, 1, 1)
2340 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2341 if not self.config.is_modifiable('language'):
2342 for w in [lang_combo, lang_label]: w.setEnabled(False)
2345 fee_label = QLabel(_('Transaction fee') + ':')
2346 grid.addWidget(fee_label, 2, 0)
2347 fee_e = BTCAmountEdit(self.get_decimal_point)
2348 fee_e.setAmount(self.wallet.fee)
2349 grid.addWidget(fee_e, 2, 1)
2350 msg = _('Fee per kilobyte of transaction.') + '\n' \
2351 + _('Recommended value') + ': ' + self.format_amount(10000) + ' ' + self.base_unit()
2352 grid.addWidget(HelpButton(msg), 2, 2)
2353 if not self.config.is_modifiable('fee_per_kb'):
2354 for w in [fee_e, fee_label]: w.setEnabled(False)
2356 units = ['BTC', 'mBTC']
2357 unit_label = QLabel(_('Base unit') + ':')
2358 grid.addWidget(unit_label, 3, 0)
2359 unit_combo = QComboBox()
2360 unit_combo.addItems(units)
2361 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2362 grid.addWidget(unit_combo, 3, 1)
2363 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2364 + '\n1BTC=1000mBTC.\n' \
2365 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2367 usechange_cb = QCheckBox(_('Use change addresses'))
2368 usechange_cb.setChecked(self.wallet.use_change)
2369 grid.addWidget(usechange_cb, 4, 0)
2370 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2371 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2373 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2374 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2375 grid.addWidget(block_ex_label, 5, 0)
2376 block_ex_combo = QComboBox()
2377 block_ex_combo.addItems(block_explorers)
2378 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2379 grid.addWidget(block_ex_combo, 5, 1)
2380 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2382 show_tx = self.config.get('show_before_broadcast', False)
2383 showtx_cb = QCheckBox(_('Show before broadcast'))
2384 showtx_cb.setChecked(show_tx)
2385 grid.addWidget(showtx_cb, 6, 0)
2386 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2388 vbox.addLayout(grid)
2390 vbox.addLayout(ok_cancel_buttons(d))
2394 if not d.exec_(): return
2396 fee = fee_e.get_amount()
2398 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2401 self.wallet.set_fee(fee)
2403 nz = unicode(nz_e.text())
2408 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2411 if self.num_zeros != nz:
2413 self.config.set_key('num_zeros', nz, True)
2414 self.update_history_tab()
2415 self.update_receive_tab()
2417 usechange_result = usechange_cb.isChecked()
2418 if self.wallet.use_change != usechange_result:
2419 self.wallet.use_change = usechange_result
2420 self.wallet.storage.put('use_change', self.wallet.use_change)
2422 if showtx_cb.isChecked() != show_tx:
2423 self.config.set_key('show_before_broadcast', not show_tx)
2425 unit_result = units[unit_combo.currentIndex()]
2426 if self.base_unit() != unit_result:
2427 self.decimal_point = 8 if unit_result == 'BTC' else 5
2428 self.config.set_key('decimal_point', self.decimal_point, True)
2429 self.update_history_tab()
2430 self.update_status()
2432 need_restart = False
2434 lang_request = languages.keys()[lang_combo.currentIndex()]
2435 if lang_request != self.config.get('language'):
2436 self.config.set_key("language", lang_request, True)
2439 be_result = block_explorers[block_ex_combo.currentIndex()]
2440 self.config.set_key('block_explorer', be_result, True)
2442 run_hook('close_settings_dialog')
2445 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2448 def run_network_dialog(self):
2449 if not self.network:
2451 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2453 def closeEvent(self, event):
2455 self.config.set_key("is_maximized", self.isMaximized())
2456 if not self.isMaximized():
2458 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2459 self.save_column_widths()
2460 self.config.set_key("console-history", self.console.history[-50:], True)
2461 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2465 def plugins_dialog(self):
2466 from electrum.plugins import plugins
2469 d.setWindowTitle(_('Electrum Plugins'))
2472 vbox = QVBoxLayout(d)
2475 scroll = QScrollArea()
2476 scroll.setEnabled(True)
2477 scroll.setWidgetResizable(True)
2478 scroll.setMinimumSize(400,250)
2479 vbox.addWidget(scroll)
2483 w.setMinimumHeight(len(plugins)*35)
2485 grid = QGridLayout()
2486 grid.setColumnStretch(0,1)
2489 def do_toggle(cb, p, w):
2492 if w: w.setEnabled(r)
2494 def mk_toggle(cb, p, w):
2495 return lambda: do_toggle(cb,p,w)
2497 for i, p in enumerate(plugins):
2499 cb = QCheckBox(p.fullname())
2500 cb.setDisabled(not p.is_available())
2501 cb.setChecked(p.is_enabled())
2502 grid.addWidget(cb, i, 0)
2503 if p.requires_settings():
2504 w = p.settings_widget(self)
2505 w.setEnabled( p.is_enabled() )
2506 grid.addWidget(w, i, 1)
2509 cb.clicked.connect(mk_toggle(cb,p,w))
2510 grid.addWidget(HelpButton(p.description()), i, 2)
2512 print_msg(_("Error: cannot display plugin"), p)
2513 traceback.print_exc(file=sys.stdout)
2514 grid.setRowStretch(i+1,1)
2516 vbox.addLayout(close_button(d))
2521 def show_account_details(self, k):
2522 account = self.wallet.accounts[k]
2525 d.setWindowTitle(_('Account Details'))
2528 vbox = QVBoxLayout(d)
2529 name = self.wallet.get_account_name(k)
2530 label = QLabel('Name: ' + name)
2531 vbox.addWidget(label)
2533 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2535 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2537 vbox.addWidget(QLabel(_('Master Public Key:')))
2540 text.setReadOnly(True)
2541 text.setMaximumHeight(170)
2542 vbox.addWidget(text)
2544 mpk_text = '\n'.join( account.get_master_pubkeys() )
2545 text.setText(mpk_text)
2547 vbox.addLayout(close_button(d))