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.paymentrequest import PR_UNPAID, PR_PAID
47 from electrum import bmp, pyqrnative
49 from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit
50 from network_dialog import NetworkDialog
51 from qrcodewidget import QRCodeWidget
53 from decimal import Decimal
61 if platform.system() == 'Windows':
62 MONOSPACE_FONT = 'Lucida Console'
63 elif platform.system() == 'Darwin':
64 MONOSPACE_FONT = 'Monaco'
66 MONOSPACE_FONT = 'monospace'
68 from electrum import ELECTRUM_VERSION
81 class StatusBarButton(QPushButton):
82 def __init__(self, icon, tooltip, func):
83 QPushButton.__init__(self, icon, '')
84 self.setToolTip(tooltip)
86 self.setMaximumWidth(25)
87 self.clicked.connect(func)
89 self.setIconSize(QSize(25,25))
91 def keyPressEvent(self, e):
92 if e.key() == QtCore.Qt.Key_Return:
104 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
106 class ElectrumWindow(QMainWindow):
110 def __init__(self, config, network, gui_object):
111 QMainWindow.__init__(self)
114 self.network = network
115 self.gui_object = gui_object
116 self.tray = gui_object.tray
117 self.go_lite = gui_object.go_lite
120 self.create_status_bar()
121 self.need_update = threading.Event()
123 self.decimal_point = config.get('decimal_point', 5)
124 self.num_zeros = int(config.get('num_zeros',0))
127 set_language(config.get('language'))
129 self.funds_error = False
130 self.completions = QStringListModel()
132 self.tabs = tabs = QTabWidget(self)
133 self.column_widths = self.config.get("column_widths_2", default_column_widths )
134 tabs.addTab(self.create_history_tab(), _('History') )
135 tabs.addTab(self.create_send_tab(), _('Send') )
136 tabs.addTab(self.create_receive_tab(), _('Receive') )
137 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
138 tabs.addTab(self.create_invoices_tab(), _('Invoices') )
139 tabs.addTab(self.create_console_tab(), _('Console') )
140 tabs.setMinimumSize(600, 400)
141 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
142 self.setCentralWidget(tabs)
144 g = self.config.get("winpos-qt",[100, 100, 840, 400])
145 self.setGeometry(g[0], g[1], g[2], g[3])
146 if self.config.get("is_maximized"):
149 self.setWindowIcon(QIcon(":icons/electrum.png"))
152 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
153 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
154 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
155 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
156 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
158 for i in range(tabs.count()):
159 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
161 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
162 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
163 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
164 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
165 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
167 self.history_list.setFocus(True)
171 self.network.register_callback('updated', lambda: self.need_update.set())
172 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
173 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
174 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
175 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
177 # set initial message
178 self.console.showMessage(self.network.banner)
183 def update_account_selector(self):
185 accounts = self.wallet.get_account_names()
186 self.account_selector.clear()
187 if len(accounts) > 1:
188 self.account_selector.addItems([_("All accounts")] + accounts.values())
189 self.account_selector.setCurrentIndex(0)
190 self.account_selector.show()
192 self.account_selector.hide()
195 def load_wallet(self, wallet):
199 self.update_wallet_format()
201 self.invoices = self.wallet.storage.get('invoices', {})
202 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
203 self.current_account = self.wallet.storage.get("current_account", None)
204 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
205 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
206 self.setWindowTitle( title )
208 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
209 self.notify_transactions()
210 self.update_account_selector()
212 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
213 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
214 self.password_menu.setEnabled(not self.wallet.is_watching_only())
215 self.seed_menu.setEnabled(self.wallet.has_seed())
216 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
217 self.import_menu.setEnabled(self.wallet.can_import())
219 self.update_lock_icon()
220 self.update_buttons_on_seed()
221 self.update_console()
223 run_hook('load_wallet', wallet)
226 def update_wallet_format(self):
227 # convert old-format imported keys
228 if self.wallet.imported_keys:
229 password = self.password_dialog(_("Please enter your password in order to update imported keys"))
231 self.wallet.convert_imported_keys(password)
233 self.show_message("error")
236 def open_wallet(self):
237 wallet_folder = self.wallet.storage.path
238 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
242 storage = WalletStorage({'wallet_path': filename})
243 if not storage.file_exists:
244 self.show_message("file not found "+ filename)
247 self.wallet.stop_threads()
250 wallet = Wallet(storage)
251 wallet.start_threads(self.network)
253 self.load_wallet(wallet)
257 def backup_wallet(self):
259 path = self.wallet.storage.path
260 wallet_folder = os.path.dirname(path)
261 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
265 new_path = os.path.join(wallet_folder, filename)
268 shutil.copy2(path, new_path)
269 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
270 except (IOError, os.error), reason:
271 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
274 def new_wallet(self):
277 wallet_folder = os.path.dirname(self.wallet.storage.path)
278 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
281 filename = os.path.join(wallet_folder, filename)
283 storage = WalletStorage({'wallet_path': filename})
284 if storage.file_exists:
285 QMessageBox.critical(None, "Error", _("File exists"))
288 wizard = installwizard.InstallWizard(self.config, self.network, storage)
289 wallet = wizard.run('new')
291 self.load_wallet(wallet)
295 def init_menubar(self):
298 file_menu = menubar.addMenu(_("&File"))
299 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
300 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
301 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
302 file_menu.addAction(_("&Quit"), self.close)
304 wallet_menu = menubar.addMenu(_("&Wallet"))
305 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
306 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
308 wallet_menu.addSeparator()
310 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
311 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
312 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
314 wallet_menu.addSeparator()
315 labels_menu = wallet_menu.addMenu(_("&Labels"))
316 labels_menu.addAction(_("&Import"), self.do_import_labels)
317 labels_menu.addAction(_("&Export"), self.do_export_labels)
319 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
320 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
321 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
322 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
323 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
325 tools_menu = menubar.addMenu(_("&Tools"))
327 # Settings / Preferences are all reserved keywords in OSX using this as work around
328 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
329 tools_menu.addAction(_("&Network"), self.run_network_dialog)
330 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
331 tools_menu.addSeparator()
332 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
333 tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
334 tools_menu.addSeparator()
336 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
337 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
338 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
340 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
341 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
342 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
343 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
344 self.raw_transaction_menu = raw_transaction_menu
346 help_menu = menubar.addMenu(_("&Help"))
347 help_menu.addAction(_("&About"), self.show_about)
348 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
349 help_menu.addSeparator()
350 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
351 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
353 self.setMenuBar(menubar)
355 def show_about(self):
356 QMessageBox.about(self, "Electrum",
357 _("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."))
359 def show_report_bug(self):
360 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
361 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
364 def notify_transactions(self):
365 if not self.network or not self.network.is_connected():
368 print_error("Notifying GUI")
369 if len(self.network.pending_transactions_for_notifications) > 0:
370 # Combine the transactions if there are more then three
371 tx_amount = len(self.network.pending_transactions_for_notifications)
374 for tx in self.network.pending_transactions_for_notifications:
375 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
379 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
380 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
382 self.network.pending_transactions_for_notifications = []
384 for tx in self.network.pending_transactions_for_notifications:
386 self.network.pending_transactions_for_notifications.remove(tx)
387 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
389 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
391 def notify(self, message):
392 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
396 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
397 def getOpenFileName(self, title, filter = ""):
398 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
399 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
400 if fileName and directory != os.path.dirname(fileName):
401 self.config.set_key('io_dir', os.path.dirname(fileName), True)
404 def getSaveFileName(self, title, filename, filter = ""):
405 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
406 path = os.path.join( directory, filename )
407 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
408 if fileName and directory != os.path.dirname(fileName):
409 self.config.set_key('io_dir', os.path.dirname(fileName), True)
413 QMainWindow.close(self)
414 run_hook('close_main_window')
416 def connect_slots(self, sender):
417 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
418 self.previous_payto_e=''
420 def timer_actions(self):
421 if self.need_update.is_set():
423 self.need_update.clear()
424 run_hook('timer_actions')
426 def format_amount(self, x, is_diff=False, whitespaces=False):
427 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
430 def get_decimal_point(self):
431 return self.decimal_point
435 assert self.decimal_point in [5,8]
436 return "BTC" if self.decimal_point == 8 else "mBTC"
439 def update_status(self):
440 if self.network is None or not self.network.is_running():
442 icon = QIcon(":icons/status_disconnected.png")
444 elif self.network.is_connected():
445 if not self.wallet.up_to_date:
446 text = _("Synchronizing...")
447 icon = QIcon(":icons/status_waiting.png")
448 elif self.network.server_lag > 1:
449 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
450 icon = QIcon(":icons/status_lagging.png")
452 c, u = self.wallet.get_account_balance(self.current_account)
453 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
454 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
456 # append fiat balance and price from exchange rate plugin
458 run_hook('get_fiat_status_text', c+u, r)
463 self.tray.setToolTip(text)
464 icon = QIcon(":icons/status_connected.png")
466 text = _("Not connected")
467 icon = QIcon(":icons/status_disconnected.png")
469 self.balance_label.setText(text)
470 self.status_button.setIcon( icon )
473 def update_wallet(self):
475 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
476 self.update_history_tab()
477 self.update_receive_tab()
478 self.update_contacts_tab()
479 self.update_completions()
480 self.update_invoices_tab()
483 def create_history_tab(self):
484 self.history_list = l = MyTreeWidget(self)
486 for i,width in enumerate(self.column_widths['history']):
487 l.setColumnWidth(i, width)
488 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
489 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
490 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
492 l.customContextMenuRequested.connect(self.create_history_menu)
496 def create_history_menu(self, position):
497 self.history_list.selectedIndexes()
498 item = self.history_list.currentItem()
499 be = self.config.get('block_explorer', 'Blockchain.info')
500 if be == 'Blockchain.info':
501 block_explorer = 'https://blockchain.info/tx/'
502 elif be == 'Blockr.io':
503 block_explorer = 'https://blockr.io/tx/info/'
504 elif be == 'Insight.is':
505 block_explorer = 'http://live.insight.is/tx/'
507 tx_hash = str(item.data(0, Qt.UserRole).toString())
508 if not tx_hash: return
510 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
511 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
512 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
513 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
514 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
517 def show_transaction(self, tx):
518 import transaction_dialog
519 d = transaction_dialog.TxDialog(tx, self)
522 def tx_label_clicked(self, item, column):
523 if column==2 and item.isSelected():
525 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
526 self.history_list.editItem( item, column )
527 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
530 def tx_label_changed(self, item, column):
534 tx_hash = str(item.data(0, Qt.UserRole).toString())
535 tx = self.wallet.transactions.get(tx_hash)
536 text = unicode( item.text(2) )
537 self.wallet.set_label(tx_hash, text)
539 item.setForeground(2, QBrush(QColor('black')))
541 text = self.wallet.get_default_label(tx_hash)
542 item.setText(2, text)
543 item.setForeground(2, QBrush(QColor('gray')))
547 def edit_label(self, is_recv):
548 l = self.receive_list if is_recv else self.contacts_list
549 item = l.currentItem()
550 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
551 l.editItem( item, 1 )
552 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
556 def address_label_clicked(self, item, column, l, column_addr, column_label):
557 if column == column_label and item.isSelected():
558 is_editable = item.data(0, 32).toBool()
561 addr = unicode( item.text(column_addr) )
562 label = unicode( item.text(column_label) )
563 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
564 l.editItem( item, column )
565 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
568 def address_label_changed(self, item, column, l, column_addr, column_label):
569 if column == column_label:
570 addr = unicode( item.text(column_addr) )
571 text = unicode( item.text(column_label) )
572 is_editable = item.data(0, 32).toBool()
576 changed = self.wallet.set_label(addr, text)
578 self.update_history_tab()
579 self.update_completions()
581 self.current_item_changed(item)
583 run_hook('item_changed', item, column)
586 def current_item_changed(self, a):
587 run_hook('current_item_changed', a)
591 def update_history_tab(self):
593 self.history_list.clear()
594 for item in self.wallet.get_tx_history(self.current_account):
595 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
596 time_str = _("unknown")
599 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
601 time_str = _("error")
604 time_str = 'unverified'
605 icon = QIcon(":icons/unconfirmed.png")
608 icon = QIcon(":icons/unconfirmed.png")
610 icon = QIcon(":icons/clock%d.png"%conf)
612 icon = QIcon(":icons/confirmed.png")
614 if value is not None:
615 v_str = self.format_amount(value, True, whitespaces=True)
619 balance_str = self.format_amount(balance, whitespaces=True)
622 label, is_default_label = self.wallet.get_label(tx_hash)
624 label = _('Pruned transaction outputs')
625 is_default_label = False
627 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
628 item.setFont(2, QFont(MONOSPACE_FONT))
629 item.setFont(3, QFont(MONOSPACE_FONT))
630 item.setFont(4, QFont(MONOSPACE_FONT))
632 item.setForeground(3, QBrush(QColor("#BC1E1E")))
634 item.setData(0, Qt.UserRole, tx_hash)
635 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
637 item.setForeground(2, QBrush(QColor('grey')))
639 item.setIcon(0, icon)
640 self.history_list.insertTopLevelItem(0,item)
643 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
644 run_hook('history_tab_update')
647 def create_send_tab(self):
650 self.send_grid = grid = QGridLayout(w)
652 grid.setColumnMinimumWidth(3,300)
653 grid.setColumnStretch(5,1)
654 grid.setRowStretch(8, 1)
656 from paytoedit import PayToEdit
657 self.amount_e = BTCAmountEdit(self.get_decimal_point)
658 self.payto_e = PayToEdit(self.amount_e)
659 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)'))
660 grid.addWidget(QLabel(_('Pay to')), 1, 0)
661 grid.addWidget(self.payto_e, 1, 1, 1, 3)
662 grid.addWidget(self.payto_help, 1, 4)
664 completer = QCompleter()
665 completer.setCaseSensitivity(False)
666 self.payto_e.setCompleter(completer)
667 completer.setModel(self.completions)
669 self.message_e = MyLineEdit()
670 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.'))
671 grid.addWidget(QLabel(_('Description')), 2, 0)
672 grid.addWidget(self.message_e, 2, 1, 1, 3)
673 grid.addWidget(self.message_help, 2, 4)
675 self.from_label = QLabel(_('From'))
676 grid.addWidget(self.from_label, 3, 0)
677 self.from_list = MyTreeWidget(self)
678 self.from_list.setColumnCount(2)
679 self.from_list.setColumnWidth(0, 350)
680 self.from_list.setColumnWidth(1, 50)
681 self.from_list.setHeaderHidden(True)
682 self.from_list.setMaximumHeight(80)
683 self.from_list.setContextMenuPolicy(Qt.CustomContextMenu)
684 self.from_list.customContextMenuRequested.connect(self.from_list_menu)
685 grid.addWidget(self.from_list, 3, 1, 1, 3)
686 self.set_pay_from([])
688 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
689 + _('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.') \
690 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
691 grid.addWidget(QLabel(_('Amount')), 4, 0)
692 grid.addWidget(self.amount_e, 4, 1, 1, 2)
693 grid.addWidget(self.amount_help, 4, 3)
695 self.fee_e = BTCAmountEdit(self.get_decimal_point)
696 grid.addWidget(QLabel(_('Fee')), 5, 0)
697 grid.addWidget(self.fee_e, 5, 1, 1, 2)
698 grid.addWidget(HelpButton(
699 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
700 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
701 + _('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)
703 self.send_button = EnterButton(_("Send"), self.do_send)
704 grid.addWidget(self.send_button, 6, 1)
706 b = EnterButton(_("Clear"), self.do_clear)
707 grid.addWidget(b, 6, 2)
709 self.payto_sig = QLabel('')
710 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
712 #QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
713 #QShortcut(QKeySequence("Down"), w, w.focusNextChild)
716 def entry_changed( is_fee ):
717 self.funds_error = False
719 if self.amount_e.is_shortcut:
720 self.amount_e.is_shortcut = False
721 sendable = self.get_sendable_balance()
722 # there is only one output because we are completely spending inputs
723 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, coins = self.get_coins())
724 fee = self.wallet.estimated_fee(inputs, 1)
726 self.amount_e.setAmount(amount)
727 self.fee_e.setAmount(fee)
730 amount = self.amount_e.get_amount()
731 fee = self.fee_e.get_amount()
733 if not is_fee: fee = None
736 # assume that there will be 2 outputs (one for change)
737 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, coins = self.get_coins())
739 self.fee_e.setAmount(fee)
742 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
746 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
747 self.funds_error = True
748 text = _( "Not enough funds" )
749 c, u = self.wallet.get_frozen_balance()
750 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
752 self.statusBar().showMessage(text)
753 self.amount_e.setPalette(palette)
754 self.fee_e.setPalette(palette)
756 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
757 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
759 run_hook('create_send_tab', grid)
762 def from_list_delete(self, item):
763 i = self.from_list.indexOfTopLevelItem(item)
765 self.redraw_from_list()
767 def from_list_menu(self, position):
768 item = self.from_list.itemAt(position)
770 menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
771 menu.exec_(self.from_list.viewport().mapToGlobal(position))
773 def set_pay_from(self, domain = None):
774 self.pay_from = [] if domain == [] else self.wallet.get_unspent_coins(domain)
775 self.redraw_from_list()
777 def redraw_from_list(self):
778 self.from_list.clear()
779 self.from_label.setHidden(len(self.pay_from) == 0)
780 self.from_list.setHidden(len(self.pay_from) == 0)
783 h = x.get('prevout_hash')
784 return h[0:8] + '...' + h[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
786 for item in self.pay_from:
787 self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
789 def update_completions(self):
791 for addr,label in self.wallet.labels.items():
792 if addr in self.wallet.addressbook:
793 l.append( label + ' <' + addr + '>')
795 run_hook('update_completions', l)
796 self.completions.setStringList(l)
800 return lambda s, *args: s.do_protect(func, args)
803 def read_send_tab(self):
804 label = unicode( self.message_e.text() )
806 if self.payment_request:
807 outputs = self.payment_request.get_outputs()
809 outputs = self.payto_e.get_outputs()
812 QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
815 for addr, x in outputs:
816 if addr is None or not bitcoin.is_address(addr):
817 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address'), _('OK'))
820 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
823 amount = sum(map(lambda x:x[1], outputs))
825 fee = self.fee_e.get_amount()
827 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
830 confirm_amount = self.config.get('confirm_amount', 100000000)
831 if amount >= confirm_amount:
832 o = '\n'.join(map(lambda x:x[0], outputs))
833 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
836 confirm_fee = self.config.get('confirm_fee', 100000)
837 if fee >= confirm_fee:
838 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()}):
841 coins = self.get_coins()
842 return outputs, fee, label, coins
846 r = self.read_send_tab()
849 outputs, fee, label, coins = r
850 self.send_tx(outputs, fee, label, coins)
854 def send_tx(self, outputs, fee, label, coins, password):
855 self.send_button.setDisabled(True)
857 # first, create an unsigned tx
859 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
861 except Exception as e:
862 traceback.print_exc(file=sys.stdout)
863 self.show_message(str(e))
864 self.send_button.setDisabled(False)
867 # call hook to see if plugin needs gui interaction
868 run_hook('send_tx', tx)
874 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
875 self.wallet.sign_transaction(tx, keypairs, password)
876 return tx, fee, label
878 def sign_done(tx, fee, label):
880 self.show_message(tx.error)
881 self.send_button.setDisabled(False)
883 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
884 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
885 self.send_button.setDisabled(False)
888 self.wallet.set_label(tx.hash(), label)
890 if not tx.is_complete() or self.config.get('show_before_broadcast'):
891 self.show_transaction(tx)
893 self.send_button.setDisabled(False)
896 self.broadcast_transaction(tx)
898 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
899 self.waiting_dialog.start()
903 def broadcast_transaction(self, tx):
905 def broadcast_thread():
906 pr = self.payment_request
908 return self.wallet.sendtx(tx)
911 self.payment_request = None
912 return False, _("Payment request has expired")
914 status, msg = self.wallet.sendtx(tx)
918 self.invoices[pr.get_id()] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), PR_PAID, tx.hash())
919 self.wallet.storage.put('invoices', self.invoices)
920 self.update_invoices_tab()
921 self.payment_request = None
922 refund_address = self.wallet.addresses()[0]
923 ack_status, ack_msg = pr.send_ack(str(tx), refund_address)
929 def broadcast_done(status, msg):
931 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
934 QMessageBox.warning(self, _('Error'), msg, _('OK'))
935 self.send_button.setDisabled(False)
937 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
938 self.waiting_dialog.start()
942 def prepare_for_payment_request(self):
943 self.tabs.setCurrentIndex(1)
944 self.payto_e.is_pr = True
945 for e in [self.payto_e, self.amount_e, self.message_e]:
947 for h in [self.payto_help, self.amount_help, self.message_help]:
949 self.payto_e.setText(_("please wait..."))
952 def payment_request_ok(self):
953 pr = self.payment_request
955 if pr_id not in self.invoices:
956 self.invoices[pr_id] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), PR_UNPAID, None)
957 self.wallet.storage.put('invoices', self.invoices)
958 self.update_invoices_tab()
960 print_error('invoice already in list')
962 status = self.invoices[pr_id][3]
963 if status == PR_PAID:
965 self.show_message("invoice already paid")
966 self.payment_request = None
969 self.payto_help.show()
970 self.payto_help.set_alt(lambda: self.show_pr_details(pr))
972 self.payto_e.setGreen()
973 self.payto_e.setText(pr.domain)
974 self.amount_e.setText(self.format_amount(pr.get_amount()))
975 self.message_e.setText(pr.get_memo())
977 def payment_request_error(self):
979 self.show_message(self.payment_request.error)
980 self.payment_request = None
982 def pay_from_URI(self,URI):
985 address, amount, label, message, request_url = util.parse_URI(URI)
987 address, amount, label, message, request_url = util.parse_URI(URI)
988 except Exception as e:
989 QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URI:') + '\n' + str(e), _('OK'))
992 self.tabs.setCurrentIndex(1)
996 if self.wallet.labels.get(address) != label:
997 if self.question(_('Save label "%s" for address %s ?'%(label,address))):
998 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
999 self.wallet.addressbook.append(address)
1000 self.wallet.set_label(address, label)
1002 label = self.wallet.labels.get(address)
1004 self.payto_e.setText(label + ' <'+ address +'>' if label else address)
1006 self.message_e.setText(message)
1008 self.amount_e.setAmount(amount)
1011 from electrum import paymentrequest
1012 def payment_request():
1013 self.payment_request = paymentrequest.PaymentRequest(self.config)
1014 self.payment_request.read(request_url)
1015 if self.payment_request.verify():
1016 self.emit(SIGNAL('payment_request_ok'))
1018 self.emit(SIGNAL('payment_request_error'))
1020 self.pr_thread = threading.Thread(target=payment_request).start()
1021 self.prepare_for_payment_request()
1026 self.payto_e.is_pr = False
1027 self.payto_sig.setVisible(False)
1028 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1032 for h in [self.payto_help, self.amount_help, self.message_help]:
1035 self.payto_help.set_alt(None)
1036 self.set_pay_from([])
1037 self.update_status()
1041 def set_addrs_frozen(self,addrs,freeze):
1043 if not addr: continue
1044 if addr in self.wallet.frozen_addresses and not freeze:
1045 self.wallet.unfreeze(addr)
1046 elif addr not in self.wallet.frozen_addresses and freeze:
1047 self.wallet.freeze(addr)
1048 self.update_receive_tab()
1052 def create_list_tab(self, headers):
1053 "generic tab creation method"
1054 l = MyTreeWidget(self)
1055 l.setColumnCount( len(headers) )
1056 l.setHeaderLabels( headers )
1059 vbox = QVBoxLayout()
1066 vbox.addWidget(buttons)
1071 def create_receive_tab(self):
1072 l, w = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1073 for i,width in enumerate(self.column_widths['receive']):
1074 l.setColumnWidth(i, width)
1075 l.setContextMenuPolicy(Qt.CustomContextMenu)
1076 l.customContextMenuRequested.connect(self.create_receive_menu)
1077 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1078 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1079 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1080 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1081 self.receive_list = l
1087 def save_column_widths(self):
1088 self.column_widths["receive"] = []
1089 for i in range(self.receive_list.columnCount() -1):
1090 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1092 self.column_widths["history"] = []
1093 for i in range(self.history_list.columnCount() - 1):
1094 self.column_widths["history"].append(self.history_list.columnWidth(i))
1096 self.column_widths["contacts"] = []
1097 for i in range(self.contacts_list.columnCount() - 1):
1098 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1100 self.config.set_key("column_widths_2", self.column_widths, True)
1103 def create_contacts_tab(self):
1104 l, w = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1105 l.setContextMenuPolicy(Qt.CustomContextMenu)
1106 l.customContextMenuRequested.connect(self.create_contact_menu)
1107 for i,width in enumerate(self.column_widths['contacts']):
1108 l.setColumnWidth(i, width)
1109 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1110 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1111 self.contacts_list = l
1115 def create_invoices_tab(self):
1116 l, w = self.create_list_tab([_('Requestor'), _('Memo'),_('Amount'), _('Status')])
1118 h.setStretchLastSection(False)
1119 h.setResizeMode(1, QHeaderView.Stretch)
1120 l.setContextMenuPolicy(Qt.CustomContextMenu)
1121 l.customContextMenuRequested.connect(self.create_invoice_menu)
1122 self.invoices_list = l
1125 def update_invoices_tab(self):
1126 invoices = self.wallet.storage.get('invoices', {})
1127 l = self.invoices_list
1129 for key, value in invoices.items():
1131 domain, memo, amount, status, tx_hash = value
1135 item = QTreeWidgetItem( [ domain, memo, self.format_amount(amount), format_status(status)] )
1136 l.addTopLevelItem(item)
1138 l.setCurrentItem(l.topLevelItem(0))
1142 def delete_imported_key(self, addr):
1143 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1144 self.wallet.delete_imported_key(addr)
1145 self.update_receive_tab()
1146 self.update_history_tab()
1148 def edit_account_label(self, k):
1149 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1151 label = unicode(text)
1152 self.wallet.set_label(k,label)
1153 self.update_receive_tab()
1155 def account_set_expanded(self, item, k, b):
1157 self.accounts_expanded[k] = b
1159 def create_account_menu(self, position, k, item):
1161 if item.isExpanded():
1162 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1164 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1165 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1166 if self.wallet.seed_version > 4:
1167 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1168 if self.wallet.account_is_pending(k):
1169 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1170 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1172 def delete_pending_account(self, k):
1173 self.wallet.delete_pending_account(k)
1174 self.update_receive_tab()
1176 def create_receive_menu(self, position):
1177 # fixme: this function apparently has a side effect.
1178 # if it is not called the menu pops up several times
1179 #self.receive_list.selectedIndexes()
1181 selected = self.receive_list.selectedItems()
1182 multi_select = len(selected) > 1
1183 addrs = [unicode(item.text(0)) for item in selected]
1184 if not multi_select:
1185 item = self.receive_list.itemAt(position)
1189 if not is_valid(addr):
1190 k = str(item.data(0,32).toString())
1192 self.create_account_menu(position, k, item)
1194 item.setExpanded(not item.isExpanded())
1198 if not multi_select:
1199 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1200 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1201 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1202 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1203 if not self.wallet.is_watching_only():
1204 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1205 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1206 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1207 if self.wallet.is_imported(addr):
1208 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1210 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1211 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1212 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1213 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1215 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1216 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1218 run_hook('receive_menu', menu, addrs)
1219 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1222 def get_sendable_balance(self):
1223 return sum(map(lambda x:x['value'], self.get_coins()))
1226 def get_coins(self):
1228 return self.pay_from
1230 domain = self.wallet.get_account_addresses(self.current_account)
1231 for i in self.wallet.frozen_addresses:
1232 if i in domain: domain.remove(i)
1233 return self.wallet.get_unspent_coins(domain)
1236 def send_from_addresses(self, addrs):
1237 self.set_pay_from( addrs )
1238 self.tabs.setCurrentIndex(1)
1241 def payto(self, addr):
1243 label = self.wallet.labels.get(addr)
1244 m_addr = label + ' <' + addr + '>' if label else addr
1245 self.tabs.setCurrentIndex(1)
1246 self.payto_e.setText(m_addr)
1247 self.amount_e.setFocus()
1250 def delete_contact(self, x):
1251 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1252 self.wallet.delete_contact(x)
1253 self.wallet.set_label(x, None)
1254 self.update_history_tab()
1255 self.update_contacts_tab()
1256 self.update_completions()
1259 def create_contact_menu(self, position):
1260 item = self.contacts_list.itemAt(position)
1263 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1265 addr = unicode(item.text(0))
1266 label = unicode(item.text(1))
1267 is_editable = item.data(0,32).toBool()
1268 payto_addr = item.data(0,33).toString()
1269 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1270 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1271 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1273 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1274 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1276 run_hook('create_contact_menu', menu, item)
1277 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1279 def delete_invoice(self, key):
1280 self.invoices.pop(key)
1281 self.wallet.storage.put('invoices', self.invoices)
1282 self.update_invoices_tab()
1284 def show_invoice(self, key):
1285 from electrum.paymentrequest import PaymentRequest
1286 domain, memo, value, status, tx_hash = self.invoices[key]
1287 pr = PaymentRequest(self.config)
1291 self.show_pr_details(pr)
1293 def show_pr_details(self, pr):
1294 msg = 'Domain: ' + pr.domain
1295 msg += '\nStatus: ' + pr.get_status()
1296 msg += '\nMemo: ' + pr.get_memo()
1297 msg += '\nPayment URL: ' + pr.payment_url
1298 msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[0] + ' ' + self.format_amount(x[1])+ self.base_unit(), pr.get_outputs()))
1299 QMessageBox.information(self, 'Invoice', msg , 'OK')
1301 def do_pay_invoice(self, key):
1302 from electrum.paymentrequest import PaymentRequest
1303 domain, memo, value, status, tx_hash = self.invoices[key]
1304 pr = PaymentRequest(self.config)
1307 self.payment_request = pr
1308 self.prepare_for_payment_request()
1310 self.payment_request_ok()
1312 self.payment_request_error()
1315 def create_invoice_menu(self, position):
1316 item = self.invoices_list.itemAt(position)
1319 k = self.invoices_list.indexOfTopLevelItem(item)
1320 key = self.invoices.keys()[k]
1321 domain, memo, value, status, tx_hash = self.invoices[key]
1323 menu.addAction(_("Details"), lambda: self.show_invoice(key))
1324 if status == PR_UNPAID:
1325 menu.addAction(_("Pay Now"), lambda: self.do_pay_invoice(key))
1326 menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
1327 menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
1330 def update_receive_item(self, item):
1331 item.setFont(0, QFont(MONOSPACE_FONT))
1332 address = str(item.data(0,0).toString())
1333 label = self.wallet.labels.get(address,'')
1334 item.setData(1,0,label)
1335 item.setData(0,32, True) # is editable
1337 run_hook('update_receive_item', address, item)
1339 if not self.wallet.is_mine(address): return
1341 c, u = self.wallet.get_addr_balance(address)
1342 balance = self.format_amount(c + u)
1343 item.setData(2,0,balance)
1345 if address in self.wallet.frozen_addresses:
1346 item.setBackgroundColor(0, QColor('lightblue'))
1349 def update_receive_tab(self):
1350 l = self.receive_list
1351 # extend the syntax for consistency
1352 l.addChild = l.addTopLevelItem
1353 l.insertChild = l.insertTopLevelItem
1357 accounts = self.wallet.get_accounts()
1358 if self.current_account is None:
1359 account_items = sorted(accounts.items())
1361 account_items = [(self.current_account, accounts.get(self.current_account))]
1364 for k, account in account_items:
1366 if len(accounts) > 1:
1367 name = self.wallet.get_account_name(k)
1368 c,u = self.wallet.get_account_balance(k)
1369 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1370 l.addTopLevelItem(account_item)
1371 account_item.setExpanded(self.accounts_expanded.get(k, True))
1372 account_item.setData(0, 32, k)
1376 sequences = [0,1] if account.has_change() else [0]
1377 for is_change in sequences:
1378 if len(sequences) > 1:
1379 name = _("Receiving") if not is_change else _("Change")
1380 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1381 account_item.addChild(seq_item)
1383 seq_item.setExpanded(True)
1385 seq_item = account_item
1387 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1393 for address in account.get_addresses(is_change):
1395 num, is_used = self.wallet.is_used(address)
1398 if gap > self.wallet.gap_limit:
1403 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1404 self.update_receive_item(item)
1406 item.setBackgroundColor(1, QColor('red'))
1410 seq_item.insertChild(0,used_item)
1412 used_item.addChild(item)
1414 seq_item.addChild(item)
1416 # we use column 1 because column 0 may be hidden
1417 l.setCurrentItem(l.topLevelItem(0),1)
1420 def update_contacts_tab(self):
1421 l = self.contacts_list
1424 for address in self.wallet.addressbook:
1425 label = self.wallet.labels.get(address,'')
1426 n = self.wallet.get_num_tx(address)
1427 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1428 item.setFont(0, QFont(MONOSPACE_FONT))
1429 # 32 = label can be edited (bool)
1430 item.setData(0,32, True)
1432 item.setData(0,33, address)
1433 l.addTopLevelItem(item)
1435 run_hook('update_contacts_tab', l)
1436 l.setCurrentItem(l.topLevelItem(0))
1440 def create_console_tab(self):
1441 from console import Console
1442 self.console = console = Console()
1446 def update_console(self):
1447 console = self.console
1448 console.history = self.config.get("console-history",[])
1449 console.history_index = len(console.history)
1451 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1452 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1454 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1456 def mkfunc(f, method):
1457 return lambda *args: apply( f, (method, args, self.password_dialog ))
1459 if m[0]=='_' or m in ['network','wallet']: continue
1460 methods[m] = mkfunc(c._run, m)
1462 console.updateNamespace(methods)
1465 def change_account(self,s):
1466 if s == _("All accounts"):
1467 self.current_account = None
1469 accounts = self.wallet.get_account_names()
1470 for k, v in accounts.items():
1472 self.current_account = k
1473 self.update_history_tab()
1474 self.update_status()
1475 self.update_receive_tab()
1477 def create_status_bar(self):
1480 sb.setFixedHeight(35)
1481 qtVersion = qVersion()
1483 self.balance_label = QLabel("")
1484 sb.addWidget(self.balance_label)
1486 from version_getter import UpdateLabel
1487 self.updatelabel = UpdateLabel(self.config, sb)
1489 self.account_selector = QComboBox()
1490 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1491 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1492 sb.addPermanentWidget(self.account_selector)
1494 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1495 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1497 self.lock_icon = QIcon()
1498 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1499 sb.addPermanentWidget( self.password_button )
1501 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1502 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1503 sb.addPermanentWidget( self.seed_button )
1504 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1505 sb.addPermanentWidget( self.status_button )
1507 run_hook('create_status_bar', (sb,))
1509 self.setStatusBar(sb)
1512 def update_lock_icon(self):
1513 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1514 self.password_button.setIcon( icon )
1517 def update_buttons_on_seed(self):
1518 if self.wallet.has_seed():
1519 self.seed_button.show()
1521 self.seed_button.hide()
1523 if not self.wallet.is_watching_only():
1524 self.password_button.show()
1525 self.send_button.setText(_("Send"))
1527 self.password_button.hide()
1528 self.send_button.setText(_("Create unsigned transaction"))
1531 def change_password_dialog(self):
1532 from password_dialog import PasswordDialog
1533 d = PasswordDialog(self.wallet, self)
1535 self.update_lock_icon()
1538 def new_contact_dialog(self):
1541 d.setWindowTitle(_("New Contact"))
1542 vbox = QVBoxLayout(d)
1543 vbox.addWidget(QLabel(_('New Contact')+':'))
1545 grid = QGridLayout()
1548 grid.addWidget(QLabel(_("Address")), 1, 0)
1549 grid.addWidget(line1, 1, 1)
1550 grid.addWidget(QLabel(_("Name")), 2, 0)
1551 grid.addWidget(line2, 2, 1)
1553 vbox.addLayout(grid)
1554 vbox.addLayout(ok_cancel_buttons(d))
1559 address = str(line1.text())
1560 label = unicode(line2.text())
1562 if not is_valid(address):
1563 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1566 self.wallet.add_contact(address)
1568 self.wallet.set_label(address, label)
1570 self.update_contacts_tab()
1571 self.update_history_tab()
1572 self.update_completions()
1573 self.tabs.setCurrentIndex(3)
1577 def new_account_dialog(self, password):
1579 dialog = QDialog(self)
1581 dialog.setWindowTitle(_("New Account"))
1583 vbox = QVBoxLayout()
1584 vbox.addWidget(QLabel(_('Account name')+':'))
1587 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1588 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1593 vbox.addLayout(ok_cancel_buttons(dialog))
1594 dialog.setLayout(vbox)
1598 name = str(e.text())
1601 self.wallet.create_pending_account(name, password)
1602 self.update_receive_tab()
1603 self.tabs.setCurrentIndex(2)
1608 def show_master_public_keys(self):
1610 dialog = QDialog(self)
1612 dialog.setWindowTitle(_("Master Public Keys"))
1614 main_layout = QGridLayout()
1615 mpk_dict = self.wallet.get_master_public_keys()
1617 for key, value in mpk_dict.items():
1618 main_layout.addWidget(QLabel(key), i, 0)
1619 mpk_text = QTextEdit()
1620 mpk_text.setReadOnly(True)
1621 mpk_text.setMaximumHeight(170)
1622 mpk_text.setText(value)
1623 main_layout.addWidget(mpk_text, i + 1, 0)
1626 vbox = QVBoxLayout()
1627 vbox.addLayout(main_layout)
1628 vbox.addLayout(close_button(dialog))
1630 dialog.setLayout(vbox)
1635 def show_seed_dialog(self, password):
1636 if not self.wallet.has_seed():
1637 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1641 mnemonic = self.wallet.get_mnemonic(password)
1643 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1645 from seed_dialog import SeedDialog
1646 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1651 def show_qrcode(self, data, title = _("QR code")):
1655 d.setWindowTitle(title)
1656 d.setMinimumSize(270, 300)
1657 vbox = QVBoxLayout()
1658 qrw = QRCodeWidget(data)
1659 vbox.addWidget(qrw, 1)
1660 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1661 hbox = QHBoxLayout()
1664 filename = os.path.join(self.config.path, "qrcode.bmp")
1667 bmp.save_qrcode(qrw.qr, filename)
1668 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1670 def copy_to_clipboard():
1671 bmp.save_qrcode(qrw.qr, filename)
1672 self.app.clipboard().setImage(QImage(filename))
1673 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1675 b = QPushButton(_("Copy"))
1677 b.clicked.connect(copy_to_clipboard)
1679 b = QPushButton(_("Save"))
1681 b.clicked.connect(print_qr)
1683 b = QPushButton(_("Close"))
1685 b.clicked.connect(d.accept)
1688 vbox.addLayout(hbox)
1693 def do_protect(self, func, args):
1694 if self.wallet.use_encryption:
1695 password = self.password_dialog()
1701 if args != (False,):
1702 args = (self,) + args + (password,)
1704 args = (self,password)
1708 def show_public_keys(self, address):
1709 if not address: return
1711 pubkey_list = self.wallet.get_public_keys(address)
1712 except Exception as e:
1713 traceback.print_exc(file=sys.stdout)
1714 self.show_message(str(e))
1718 d.setMinimumSize(600, 200)
1720 vbox = QVBoxLayout()
1721 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1722 vbox.addWidget( QLabel(_("Public key") + ':'))
1724 keys.setReadOnly(True)
1725 keys.setText('\n'.join(pubkey_list))
1726 vbox.addWidget(keys)
1727 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1728 vbox.addLayout(close_button(d))
1733 def show_private_key(self, address, password):
1734 if not address: return
1736 pk_list = self.wallet.get_private_key(address, password)
1737 except Exception as e:
1738 traceback.print_exc(file=sys.stdout)
1739 self.show_message(str(e))
1743 d.setMinimumSize(600, 200)
1745 vbox = QVBoxLayout()
1746 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1747 vbox.addWidget( QLabel(_("Private key") + ':'))
1749 keys.setReadOnly(True)
1750 keys.setText('\n'.join(pk_list))
1751 vbox.addWidget(keys)
1752 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1753 vbox.addLayout(close_button(d))
1759 def do_sign(self, address, message, signature, password):
1760 message = unicode(message.toPlainText())
1761 message = message.encode('utf-8')
1763 sig = self.wallet.sign_message(str(address.text()), message, password)
1764 signature.setText(sig)
1765 except Exception as e:
1766 self.show_message(str(e))
1768 def do_verify(self, address, message, signature):
1769 message = unicode(message.toPlainText())
1770 message = message.encode('utf-8')
1771 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1772 self.show_message(_("Signature verified"))
1774 self.show_message(_("Error: wrong signature"))
1777 def sign_verify_message(self, address=''):
1780 d.setWindowTitle(_('Sign/verify Message'))
1781 d.setMinimumSize(410, 290)
1783 layout = QGridLayout(d)
1785 message_e = QTextEdit()
1786 layout.addWidget(QLabel(_('Message')), 1, 0)
1787 layout.addWidget(message_e, 1, 1)
1788 layout.setRowStretch(2,3)
1790 address_e = QLineEdit()
1791 address_e.setText(address)
1792 layout.addWidget(QLabel(_('Address')), 2, 0)
1793 layout.addWidget(address_e, 2, 1)
1795 signature_e = QTextEdit()
1796 layout.addWidget(QLabel(_('Signature')), 3, 0)
1797 layout.addWidget(signature_e, 3, 1)
1798 layout.setRowStretch(3,1)
1800 hbox = QHBoxLayout()
1802 b = QPushButton(_("Sign"))
1803 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1806 b = QPushButton(_("Verify"))
1807 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1810 b = QPushButton(_("Close"))
1811 b.clicked.connect(d.accept)
1813 layout.addLayout(hbox, 4, 1)
1818 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1820 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1821 message_e.setText(decrypted)
1822 except Exception as e:
1823 self.show_message(str(e))
1826 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1827 message = unicode(message_e.toPlainText())
1828 message = message.encode('utf-8')
1830 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1831 encrypted_e.setText(encrypted)
1832 except Exception as e:
1833 self.show_message(str(e))
1837 def encrypt_message(self, address = ''):
1840 d.setWindowTitle(_('Encrypt/decrypt Message'))
1841 d.setMinimumSize(610, 490)
1843 layout = QGridLayout(d)
1845 message_e = QTextEdit()
1846 layout.addWidget(QLabel(_('Message')), 1, 0)
1847 layout.addWidget(message_e, 1, 1)
1848 layout.setRowStretch(2,3)
1850 pubkey_e = QLineEdit()
1852 pubkey = self.wallet.getpubkeys(address)[0]
1853 pubkey_e.setText(pubkey)
1854 layout.addWidget(QLabel(_('Public key')), 2, 0)
1855 layout.addWidget(pubkey_e, 2, 1)
1857 encrypted_e = QTextEdit()
1858 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1859 layout.addWidget(encrypted_e, 3, 1)
1860 layout.setRowStretch(3,1)
1862 hbox = QHBoxLayout()
1863 b = QPushButton(_("Encrypt"))
1864 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1867 b = QPushButton(_("Decrypt"))
1868 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1871 b = QPushButton(_("Close"))
1872 b.clicked.connect(d.accept)
1875 layout.addLayout(hbox, 4, 1)
1879 def question(self, msg):
1880 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1882 def show_message(self, msg):
1883 QMessageBox.information(self, _('Message'), msg, _('OK'))
1885 def password_dialog(self, msg=None):
1888 d.setWindowTitle(_("Enter Password"))
1893 vbox = QVBoxLayout()
1895 msg = _('Please enter your password')
1896 vbox.addWidget(QLabel(msg))
1898 grid = QGridLayout()
1900 grid.addWidget(QLabel(_('Password')), 1, 0)
1901 grid.addWidget(pw, 1, 1)
1902 vbox.addLayout(grid)
1904 vbox.addLayout(ok_cancel_buttons(d))
1907 run_hook('password_dialog', pw, grid, 1)
1908 if not d.exec_(): return
1909 return unicode(pw.text())
1918 def tx_from_text(self, txt):
1919 "json or raw hexadecimal"
1922 tx = Transaction(txt)
1928 tx_dict = json.loads(str(txt))
1929 assert "hex" in tx_dict.keys()
1930 tx = Transaction(tx_dict["hex"])
1931 if tx_dict.has_key("input_info"):
1932 input_info = json.loads(tx_dict['input_info'])
1933 tx.add_input_info(input_info)
1936 traceback.print_exc(file=sys.stdout)
1939 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1943 def read_tx_from_file(self):
1944 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1948 with open(fileName, "r") as f:
1949 file_content = f.read()
1950 except (ValueError, IOError, os.error), reason:
1951 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1953 return self.tx_from_text(file_content)
1957 def sign_raw_transaction(self, tx, input_info, password):
1958 self.wallet.signrawtransaction(tx, input_info, [], password)
1960 def do_process_from_text(self):
1961 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1964 tx = self.tx_from_text(text)
1966 self.show_transaction(tx)
1968 def do_process_from_file(self):
1969 tx = self.read_tx_from_file()
1971 self.show_transaction(tx)
1973 def do_process_from_txid(self):
1974 from electrum import transaction
1975 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1977 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1979 tx = transaction.Transaction(r)
1981 self.show_transaction(tx)
1983 self.show_message("unknown transaction")
1985 def do_process_from_csvReader(self, csvReader):
1990 for position, row in enumerate(csvReader):
1992 if not is_valid(address):
1993 errors.append((position, address))
1995 amount = Decimal(row[1])
1996 amount = int(100000000*amount)
1997 outputs.append((address, amount))
1998 except (ValueError, IOError, os.error), reason:
1999 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2003 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
2004 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
2008 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
2009 except Exception as e:
2010 self.show_message(str(e))
2013 self.show_transaction(tx)
2015 def do_process_from_csv_file(self):
2016 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
2020 with open(fileName, "r") as f:
2021 csvReader = csv.reader(f)
2022 self.do_process_from_csvReader(csvReader)
2023 except (ValueError, IOError, os.error), reason:
2024 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2027 def do_process_from_csv_text(self):
2028 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
2029 + _("Format: address, amount. One output per line"), _("Load CSV"))
2032 f = StringIO.StringIO(text)
2033 csvReader = csv.reader(f)
2034 self.do_process_from_csvReader(csvReader)
2039 def export_privkeys_dialog(self, password):
2040 if self.wallet.is_watching_only():
2041 self.show_message(_("This is a watching-only wallet"))
2045 d.setWindowTitle(_('Private keys'))
2046 d.setMinimumSize(850, 300)
2047 vbox = QVBoxLayout(d)
2049 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
2050 _("Exposing a single private key can compromise your entire wallet!"),
2051 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
2052 vbox.addWidget(QLabel(msg))
2058 defaultname = 'electrum-private-keys.csv'
2059 select_msg = _('Select file to export your private keys to')
2060 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2061 vbox.addLayout(hbox)
2063 h, b = ok_cancel_buttons2(d, _('Export'))
2068 addresses = self.wallet.addresses(True)
2070 def privkeys_thread():
2071 for addr in addresses:
2075 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
2076 d.emit(SIGNAL('computing_privkeys'))
2077 d.emit(SIGNAL('show_privkeys'))
2079 def show_privkeys():
2080 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
2084 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
2085 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
2086 threading.Thread(target=privkeys_thread).start()
2092 filename = filename_e.text()
2097 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
2098 except (IOError, os.error), reason:
2099 export_error_label = _("Electrum was unable to produce a private key-export.")
2100 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2102 except Exception as e:
2103 self.show_message(str(e))
2106 self.show_message(_("Private keys exported."))
2109 def do_export_privkeys(self, fileName, pklist, is_csv):
2110 with open(fileName, "w+") as f:
2112 transaction = csv.writer(f)
2113 transaction.writerow(["address", "private_key"])
2114 for addr, pk in pklist.items():
2115 transaction.writerow(["%34s"%addr,pk])
2118 f.write(json.dumps(pklist, indent = 4))
2121 def do_import_labels(self):
2122 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2123 if not labelsFile: return
2125 f = open(labelsFile, 'r')
2128 for key, value in json.loads(data).items():
2129 self.wallet.set_label(key, value)
2130 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2131 except (IOError, os.error), reason:
2132 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2135 def do_export_labels(self):
2136 labels = self.wallet.labels
2138 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2140 with open(fileName, 'w+') as f:
2141 json.dump(labels, f)
2142 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2143 except (IOError, os.error), reason:
2144 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2147 def export_history_dialog(self):
2150 d.setWindowTitle(_('Export History'))
2151 d.setMinimumSize(400, 200)
2152 vbox = QVBoxLayout(d)
2154 defaultname = os.path.expanduser('~/electrum-history.csv')
2155 select_msg = _('Select file to export your wallet transactions to')
2157 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2158 vbox.addLayout(hbox)
2162 h, b = ok_cancel_buttons2(d, _('Export'))
2167 filename = filename_e.text()
2172 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2173 except (IOError, os.error), reason:
2174 export_error_label = _("Electrum was unable to produce a transaction export.")
2175 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2178 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2181 def do_export_history(self, wallet, fileName, is_csv):
2182 history = wallet.get_tx_history()
2184 for item in history:
2185 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2187 if timestamp is not None:
2189 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2190 except [RuntimeError, TypeError, NameError] as reason:
2191 time_string = "unknown"
2194 time_string = "unknown"
2196 time_string = "pending"
2198 if value is not None:
2199 value_string = format_satoshis(value, True)
2204 fee_string = format_satoshis(fee, True)
2209 label, is_default_label = wallet.get_label(tx_hash)
2210 label = label.encode('utf-8')
2214 balance_string = format_satoshis(balance, False)
2216 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2218 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2220 with open(fileName, "w+") as f:
2222 transaction = csv.writer(f)
2223 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2225 transaction.writerow(line)
2228 f.write(json.dumps(lines, indent = 4))
2231 def sweep_key_dialog(self):
2233 d.setWindowTitle(_('Sweep private keys'))
2234 d.setMinimumSize(600, 300)
2236 vbox = QVBoxLayout(d)
2237 vbox.addWidget(QLabel(_("Enter private keys")))
2239 keys_e = QTextEdit()
2240 keys_e.setTabChangesFocus(True)
2241 vbox.addWidget(keys_e)
2243 h, address_e = address_field(self.wallet.addresses())
2247 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2248 vbox.addLayout(hbox)
2249 button.setEnabled(False)
2252 addr = str(address_e.text())
2253 if bitcoin.is_address(addr):
2257 pk = str(keys_e.toPlainText()).strip()
2258 if Wallet.is_private_key(pk):
2261 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2262 keys_e.textChanged.connect(f)
2263 address_e.textChanged.connect(f)
2267 fee = self.wallet.fee
2268 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2269 self.show_transaction(tx)
2273 def do_import_privkey(self, password):
2274 if not self.wallet.has_imported_keys():
2275 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2276 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2277 + _('Are you sure you understand what you are doing?'), 3, 4)
2280 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2283 text = str(text).split()
2288 addr = self.wallet.import_key(key, password)
2289 except Exception as e:
2295 addrlist.append(addr)
2297 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2299 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2300 self.update_receive_tab()
2301 self.update_history_tab()
2304 def settings_dialog(self):
2306 d.setWindowTitle(_('Electrum Settings'))
2308 vbox = QVBoxLayout()
2309 grid = QGridLayout()
2310 grid.setColumnStretch(0,1)
2312 nz_label = QLabel(_('Display zeros') + ':')
2313 grid.addWidget(nz_label, 0, 0)
2314 nz_e = AmountEdit(None,True)
2315 nz_e.setText("%d"% self.num_zeros)
2316 grid.addWidget(nz_e, 0, 1)
2317 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2318 grid.addWidget(HelpButton(msg), 0, 2)
2319 if not self.config.is_modifiable('num_zeros'):
2320 for w in [nz_e, nz_label]: w.setEnabled(False)
2322 lang_label=QLabel(_('Language') + ':')
2323 grid.addWidget(lang_label, 1, 0)
2324 lang_combo = QComboBox()
2325 from electrum.i18n import languages
2326 lang_combo.addItems(languages.values())
2328 index = languages.keys().index(self.config.get("language",''))
2331 lang_combo.setCurrentIndex(index)
2332 grid.addWidget(lang_combo, 1, 1)
2333 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2334 if not self.config.is_modifiable('language'):
2335 for w in [lang_combo, lang_label]: w.setEnabled(False)
2338 fee_label = QLabel(_('Transaction fee') + ':')
2339 grid.addWidget(fee_label, 2, 0)
2340 fee_e = BTCAmountEdit(self.get_decimal_point)
2341 fee_e.setAmount(self.wallet.fee)
2342 grid.addWidget(fee_e, 2, 1)
2343 msg = _('Fee per kilobyte of transaction.') + '\n' \
2344 + _('Recommended value') + ': ' + self.format_amount(10000) + ' ' + self.base_unit()
2345 grid.addWidget(HelpButton(msg), 2, 2)
2346 if not self.config.is_modifiable('fee_per_kb'):
2347 for w in [fee_e, fee_label]: w.setEnabled(False)
2349 units = ['BTC', 'mBTC']
2350 unit_label = QLabel(_('Base unit') + ':')
2351 grid.addWidget(unit_label, 3, 0)
2352 unit_combo = QComboBox()
2353 unit_combo.addItems(units)
2354 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2355 grid.addWidget(unit_combo, 3, 1)
2356 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2357 + '\n1BTC=1000mBTC.\n' \
2358 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2360 usechange_cb = QCheckBox(_('Use change addresses'))
2361 usechange_cb.setChecked(self.wallet.use_change)
2362 grid.addWidget(usechange_cb, 4, 0)
2363 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2364 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2366 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2367 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2368 grid.addWidget(block_ex_label, 5, 0)
2369 block_ex_combo = QComboBox()
2370 block_ex_combo.addItems(block_explorers)
2371 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2372 grid.addWidget(block_ex_combo, 5, 1)
2373 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2375 show_tx = self.config.get('show_before_broadcast', False)
2376 showtx_cb = QCheckBox(_('Show before broadcast'))
2377 showtx_cb.setChecked(show_tx)
2378 grid.addWidget(showtx_cb, 6, 0)
2379 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2381 vbox.addLayout(grid)
2383 vbox.addLayout(ok_cancel_buttons(d))
2387 if not d.exec_(): return
2389 fee = fee_e.get_amount()
2391 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2394 self.wallet.set_fee(fee)
2396 nz = unicode(nz_e.text())
2401 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2404 if self.num_zeros != nz:
2406 self.config.set_key('num_zeros', nz, True)
2407 self.update_history_tab()
2408 self.update_receive_tab()
2410 usechange_result = usechange_cb.isChecked()
2411 if self.wallet.use_change != usechange_result:
2412 self.wallet.use_change = usechange_result
2413 self.wallet.storage.put('use_change', self.wallet.use_change)
2415 if showtx_cb.isChecked() != show_tx:
2416 self.config.set_key('show_before_broadcast', not show_tx)
2418 unit_result = units[unit_combo.currentIndex()]
2419 if self.base_unit() != unit_result:
2420 self.decimal_point = 8 if unit_result == 'BTC' else 5
2421 self.config.set_key('decimal_point', self.decimal_point, True)
2422 self.update_history_tab()
2423 self.update_status()
2425 need_restart = False
2427 lang_request = languages.keys()[lang_combo.currentIndex()]
2428 if lang_request != self.config.get('language'):
2429 self.config.set_key("language", lang_request, True)
2432 be_result = block_explorers[block_ex_combo.currentIndex()]
2433 self.config.set_key('block_explorer', be_result, True)
2435 run_hook('close_settings_dialog')
2438 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2441 def run_network_dialog(self):
2442 if not self.network:
2444 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2446 def closeEvent(self, event):
2448 self.config.set_key("is_maximized", self.isMaximized())
2449 if not self.isMaximized():
2451 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2452 self.save_column_widths()
2453 self.config.set_key("console-history", self.console.history[-50:], True)
2454 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2458 def plugins_dialog(self):
2459 from electrum.plugins import plugins
2462 d.setWindowTitle(_('Electrum Plugins'))
2465 vbox = QVBoxLayout(d)
2468 scroll = QScrollArea()
2469 scroll.setEnabled(True)
2470 scroll.setWidgetResizable(True)
2471 scroll.setMinimumSize(400,250)
2472 vbox.addWidget(scroll)
2476 w.setMinimumHeight(len(plugins)*35)
2478 grid = QGridLayout()
2479 grid.setColumnStretch(0,1)
2482 def do_toggle(cb, p, w):
2485 if w: w.setEnabled(r)
2487 def mk_toggle(cb, p, w):
2488 return lambda: do_toggle(cb,p,w)
2490 for i, p in enumerate(plugins):
2492 cb = QCheckBox(p.fullname())
2493 cb.setDisabled(not p.is_available())
2494 cb.setChecked(p.is_enabled())
2495 grid.addWidget(cb, i, 0)
2496 if p.requires_settings():
2497 w = p.settings_widget(self)
2498 w.setEnabled( p.is_enabled() )
2499 grid.addWidget(w, i, 1)
2502 cb.clicked.connect(mk_toggle(cb,p,w))
2503 grid.addWidget(HelpButton(p.description()), i, 2)
2505 print_msg(_("Error: cannot display plugin"), p)
2506 traceback.print_exc(file=sys.stdout)
2507 grid.setRowStretch(i+1,1)
2509 vbox.addLayout(close_button(d))
2514 def show_account_details(self, k):
2515 account = self.wallet.accounts[k]
2518 d.setWindowTitle(_('Account Details'))
2521 vbox = QVBoxLayout(d)
2522 name = self.wallet.get_account_name(k)
2523 label = QLabel('Name: ' + name)
2524 vbox.addWidget(label)
2526 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2528 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2530 vbox.addWidget(QLabel(_('Master Public Key:')))
2533 text.setReadOnly(True)
2534 text.setMaximumHeight(170)
2535 vbox.addWidget(text)
2537 mpk_text = '\n'.join( account.get_master_pubkeys() )
2538 text.setText(mpk_text)
2540 vbox.addLayout(close_button(d))