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, QRDialog
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
90 class StatusBarButton(QPushButton):
91 def __init__(self, icon, tooltip, func):
92 QPushButton.__init__(self, icon, '')
93 self.setToolTip(tooltip)
95 self.setMaximumWidth(25)
96 self.clicked.connect(func)
98 self.setIconSize(QSize(25,25))
100 def keyPressEvent(self, e):
101 if e.key() == QtCore.Qt.Key_Return:
113 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
115 class ElectrumWindow(QMainWindow):
119 def __init__(self, config, network, gui_object):
120 QMainWindow.__init__(self)
123 self.network = network
124 self.gui_object = gui_object
125 self.tray = gui_object.tray
126 self.go_lite = gui_object.go_lite
129 self.create_status_bar()
130 self.need_update = threading.Event()
132 self.decimal_point = config.get('decimal_point', 5)
133 self.num_zeros = int(config.get('num_zeros',0))
136 set_language(config.get('language'))
138 self.funds_error = False
139 self.completions = QStringListModel()
141 self.tabs = tabs = QTabWidget(self)
142 self.column_widths = self.config.get("column_widths_2", default_column_widths )
143 tabs.addTab(self.create_history_tab(), _('History') )
144 tabs.addTab(self.create_send_tab(), _('Send') )
145 tabs.addTab(self.create_receive_tab(), _('Receive') )
146 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
147 tabs.addTab(self.create_invoices_tab(), _('Invoices') )
148 tabs.addTab(self.create_console_tab(), _('Console') )
149 tabs.setMinimumSize(600, 400)
150 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
151 self.setCentralWidget(tabs)
153 g = self.config.get("winpos-qt",[100, 100, 840, 400])
154 self.setGeometry(g[0], g[1], g[2], g[3])
155 if self.config.get("is_maximized"):
158 self.setWindowIcon(QIcon(":icons/electrum.png"))
161 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
162 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
163 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
164 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
165 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
167 for i in range(tabs.count()):
168 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
170 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
171 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
172 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
173 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
174 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
176 self.history_list.setFocus(True)
180 self.network.register_callback('updated', lambda: self.need_update.set())
181 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
182 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
183 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
184 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
186 # set initial message
187 self.console.showMessage(self.network.banner)
190 self.payment_request = None
192 def update_account_selector(self):
194 accounts = self.wallet.get_account_names()
195 self.account_selector.clear()
196 if len(accounts) > 1:
197 self.account_selector.addItems([_("All accounts")] + accounts.values())
198 self.account_selector.setCurrentIndex(0)
199 self.account_selector.show()
201 self.account_selector.hide()
204 def load_wallet(self, wallet):
208 self.update_wallet_format()
210 self.invoices = self.wallet.storage.get('invoices', {})
211 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
212 self.current_account = self.wallet.storage.get("current_account", None)
213 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
214 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
215 self.setWindowTitle( title )
217 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
218 self.notify_transactions()
219 self.update_account_selector()
221 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
222 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
223 self.password_menu.setEnabled(not self.wallet.is_watching_only())
224 self.seed_menu.setEnabled(self.wallet.has_seed())
225 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
226 self.import_menu.setEnabled(self.wallet.can_import())
228 self.update_lock_icon()
229 self.update_buttons_on_seed()
230 self.update_console()
232 run_hook('load_wallet', wallet)
235 def update_wallet_format(self):
236 # convert old-format imported keys
237 if self.wallet.imported_keys:
238 password = self.password_dialog(_("Please enter your password in order to update imported keys"))
240 self.wallet.convert_imported_keys(password)
242 self.show_message("error")
245 def open_wallet(self):
246 wallet_folder = self.wallet.storage.path
247 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
251 storage = WalletStorage({'wallet_path': filename})
252 if not storage.file_exists:
253 self.show_message("file not found "+ filename)
256 self.wallet.stop_threads()
259 wallet = Wallet(storage)
260 wallet.start_threads(self.network)
262 self.load_wallet(wallet)
266 def backup_wallet(self):
268 path = self.wallet.storage.path
269 wallet_folder = os.path.dirname(path)
270 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
274 new_path = os.path.join(wallet_folder, filename)
277 shutil.copy2(path, new_path)
278 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
279 except (IOError, os.error), reason:
280 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
283 def new_wallet(self):
286 wallet_folder = os.path.dirname(self.wallet.storage.path)
287 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
290 filename = os.path.join(wallet_folder, filename)
292 storage = WalletStorage({'wallet_path': filename})
293 if storage.file_exists:
294 QMessageBox.critical(None, "Error", _("File exists"))
297 wizard = installwizard.InstallWizard(self.config, self.network, storage)
298 wallet = wizard.run('new')
300 self.load_wallet(wallet)
304 def init_menubar(self):
307 file_menu = menubar.addMenu(_("&File"))
308 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
309 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
310 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
311 file_menu.addAction(_("&Quit"), self.close)
313 wallet_menu = menubar.addMenu(_("&Wallet"))
314 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
315 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
317 wallet_menu.addSeparator()
319 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
320 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
321 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
323 wallet_menu.addSeparator()
324 labels_menu = wallet_menu.addMenu(_("&Labels"))
325 labels_menu.addAction(_("&Import"), self.do_import_labels)
326 labels_menu.addAction(_("&Export"), self.do_export_labels)
328 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
329 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
330 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
331 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
332 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
334 tools_menu = menubar.addMenu(_("&Tools"))
336 # Settings / Preferences are all reserved keywords in OSX using this as work around
337 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
338 tools_menu.addAction(_("&Network"), self.run_network_dialog)
339 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
340 tools_menu.addSeparator()
341 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
342 tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
343 tools_menu.addSeparator()
345 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
346 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
347 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
349 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
350 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
351 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
352 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
353 self.raw_transaction_menu = raw_transaction_menu
355 help_menu = menubar.addMenu(_("&Help"))
356 help_menu.addAction(_("&About"), self.show_about)
357 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
358 help_menu.addSeparator()
359 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
360 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
362 self.setMenuBar(menubar)
364 def show_about(self):
365 QMessageBox.about(self, "Electrum",
366 _("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."))
368 def show_report_bug(self):
369 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
370 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
373 def notify_transactions(self):
374 if not self.network or not self.network.is_connected():
377 print_error("Notifying GUI")
378 if len(self.network.pending_transactions_for_notifications) > 0:
379 # Combine the transactions if there are more then three
380 tx_amount = len(self.network.pending_transactions_for_notifications)
383 for tx in self.network.pending_transactions_for_notifications:
384 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
388 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
389 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
391 self.network.pending_transactions_for_notifications = []
393 for tx in self.network.pending_transactions_for_notifications:
395 self.network.pending_transactions_for_notifications.remove(tx)
396 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
398 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
400 def notify(self, message):
401 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
405 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
406 def getOpenFileName(self, title, filter = ""):
407 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
408 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
409 if fileName and directory != os.path.dirname(fileName):
410 self.config.set_key('io_dir', os.path.dirname(fileName), True)
413 def getSaveFileName(self, title, filename, filter = ""):
414 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
415 path = os.path.join( directory, filename )
416 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
417 if fileName and directory != os.path.dirname(fileName):
418 self.config.set_key('io_dir', os.path.dirname(fileName), True)
422 QMainWindow.close(self)
423 run_hook('close_main_window')
425 def connect_slots(self, sender):
426 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
427 self.previous_payto_e=''
429 def timer_actions(self):
430 if self.need_update.is_set():
432 self.need_update.clear()
433 run_hook('timer_actions')
435 def format_amount(self, x, is_diff=False, whitespaces=False):
436 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
439 def get_decimal_point(self):
440 return self.decimal_point
444 assert self.decimal_point in [5,8]
445 return "BTC" if self.decimal_point == 8 else "mBTC"
448 def update_status(self):
449 if self.network is None or not self.network.is_running():
451 icon = QIcon(":icons/status_disconnected.png")
453 elif self.network.is_connected():
454 if not self.wallet.up_to_date:
455 text = _("Synchronizing...")
456 icon = QIcon(":icons/status_waiting.png")
457 elif self.network.server_lag > 1:
458 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
459 icon = QIcon(":icons/status_lagging.png")
461 c, u = self.wallet.get_account_balance(self.current_account)
462 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
463 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
465 # append fiat balance and price from exchange rate plugin
467 run_hook('get_fiat_status_text', c+u, r)
472 self.tray.setToolTip(text)
473 icon = QIcon(":icons/status_connected.png")
475 text = _("Not connected")
476 icon = QIcon(":icons/status_disconnected.png")
478 self.balance_label.setText(text)
479 self.status_button.setIcon( icon )
482 def update_wallet(self):
484 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
485 self.update_history_tab()
486 self.update_receive_tab()
487 self.update_contacts_tab()
488 self.update_completions()
489 self.update_invoices_tab()
492 def create_history_tab(self):
493 self.history_list = l = MyTreeWidget(self)
495 for i,width in enumerate(self.column_widths['history']):
496 l.setColumnWidth(i, width)
497 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
498 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
499 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
501 l.customContextMenuRequested.connect(self.create_history_menu)
505 def create_history_menu(self, position):
506 self.history_list.selectedIndexes()
507 item = self.history_list.currentItem()
508 be = self.config.get('block_explorer', 'Blockchain.info')
509 if be == 'Blockchain.info':
510 block_explorer = 'https://blockchain.info/tx/'
511 elif be == 'Blockr.io':
512 block_explorer = 'https://blockr.io/tx/info/'
513 elif be == 'Insight.is':
514 block_explorer = 'http://live.insight.is/tx/'
516 tx_hash = str(item.data(0, Qt.UserRole).toString())
517 if not tx_hash: return
519 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
520 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
521 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
522 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
523 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
526 def show_transaction(self, tx):
527 import transaction_dialog
528 d = transaction_dialog.TxDialog(tx, self)
531 def tx_label_clicked(self, item, column):
532 if column==2 and item.isSelected():
534 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
535 self.history_list.editItem( item, column )
536 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
539 def tx_label_changed(self, item, column):
543 tx_hash = str(item.data(0, Qt.UserRole).toString())
544 tx = self.wallet.transactions.get(tx_hash)
545 text = unicode( item.text(2) )
546 self.wallet.set_label(tx_hash, text)
548 item.setForeground(2, QBrush(QColor('black')))
550 text = self.wallet.get_default_label(tx_hash)
551 item.setText(2, text)
552 item.setForeground(2, QBrush(QColor('gray')))
556 def edit_label(self, is_recv):
557 l = self.receive_list if is_recv else self.contacts_list
558 item = l.currentItem()
559 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
560 l.editItem( item, 1 )
561 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
565 def address_label_clicked(self, item, column, l, column_addr, column_label):
566 if column == column_label and item.isSelected():
567 is_editable = item.data(0, 32).toBool()
570 addr = unicode( item.text(column_addr) )
571 label = unicode( item.text(column_label) )
572 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
573 l.editItem( item, column )
574 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
577 def address_label_changed(self, item, column, l, column_addr, column_label):
578 if column == column_label:
579 addr = unicode( item.text(column_addr) )
580 text = unicode( item.text(column_label) )
581 is_editable = item.data(0, 32).toBool()
585 changed = self.wallet.set_label(addr, text)
587 self.update_history_tab()
588 self.update_completions()
590 self.current_item_changed(item)
592 run_hook('item_changed', item, column)
595 def current_item_changed(self, a):
596 run_hook('current_item_changed', a)
600 def update_history_tab(self):
602 self.history_list.clear()
603 for item in self.wallet.get_tx_history(self.current_account):
604 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
605 time_str = _("unknown")
608 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
610 time_str = _("error")
613 time_str = 'unverified'
614 icon = QIcon(":icons/unconfirmed.png")
617 icon = QIcon(":icons/unconfirmed.png")
619 icon = QIcon(":icons/clock%d.png"%conf)
621 icon = QIcon(":icons/confirmed.png")
623 if value is not None:
624 v_str = self.format_amount(value, True, whitespaces=True)
628 balance_str = self.format_amount(balance, whitespaces=True)
631 label, is_default_label = self.wallet.get_label(tx_hash)
633 label = _('Pruned transaction outputs')
634 is_default_label = False
636 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
637 item.setFont(2, QFont(MONOSPACE_FONT))
638 item.setFont(3, QFont(MONOSPACE_FONT))
639 item.setFont(4, QFont(MONOSPACE_FONT))
641 item.setForeground(3, QBrush(QColor("#BC1E1E")))
643 item.setData(0, Qt.UserRole, tx_hash)
644 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
646 item.setForeground(2, QBrush(QColor('grey')))
648 item.setIcon(0, icon)
649 self.history_list.insertTopLevelItem(0,item)
652 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
653 run_hook('history_tab_update')
656 def create_send_tab(self):
659 self.send_grid = grid = QGridLayout(w)
661 grid.setColumnMinimumWidth(3,300)
662 grid.setColumnStretch(5,1)
663 grid.setRowStretch(8, 1)
665 from paytoedit import PayToEdit
666 self.amount_e = BTCAmountEdit(self.get_decimal_point)
667 self.payto_e = PayToEdit(self)
668 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)'))
669 grid.addWidget(QLabel(_('Pay to')), 1, 0)
670 grid.addWidget(self.payto_e, 1, 1, 1, 3)
671 grid.addWidget(self.payto_help, 1, 4)
673 completer = QCompleter()
674 completer.setCaseSensitivity(False)
675 self.payto_e.setCompleter(completer)
676 completer.setModel(self.completions)
678 self.message_e = MyLineEdit()
679 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.'))
680 grid.addWidget(QLabel(_('Description')), 2, 0)
681 grid.addWidget(self.message_e, 2, 1, 1, 3)
682 grid.addWidget(self.message_help, 2, 4)
684 self.from_label = QLabel(_('From'))
685 grid.addWidget(self.from_label, 3, 0)
686 self.from_list = MyTreeWidget(self)
687 self.from_list.setColumnCount(2)
688 self.from_list.setColumnWidth(0, 350)
689 self.from_list.setColumnWidth(1, 50)
690 self.from_list.setHeaderHidden(True)
691 self.from_list.setMaximumHeight(80)
692 self.from_list.setContextMenuPolicy(Qt.CustomContextMenu)
693 self.from_list.customContextMenuRequested.connect(self.from_list_menu)
694 grid.addWidget(self.from_list, 3, 1, 1, 3)
695 self.set_pay_from([])
697 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
698 + _('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.') \
699 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
700 grid.addWidget(QLabel(_('Amount')), 4, 0)
701 grid.addWidget(self.amount_e, 4, 1, 1, 2)
702 grid.addWidget(self.amount_help, 4, 3)
704 self.fee_e = BTCAmountEdit(self.get_decimal_point)
705 grid.addWidget(QLabel(_('Fee')), 5, 0)
706 grid.addWidget(self.fee_e, 5, 1, 1, 2)
707 grid.addWidget(HelpButton(
708 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
709 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
710 + _('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)
712 self.send_button = EnterButton(_("Send"), self.do_send)
713 grid.addWidget(self.send_button, 6, 1)
715 b = EnterButton(_("Clear"), self.do_clear)
716 grid.addWidget(b, 6, 2)
718 self.payto_sig = QLabel('')
719 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
721 #QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
722 #QShortcut(QKeySequence("Down"), w, w.focusNextChild)
725 def entry_changed( is_fee ):
726 self.funds_error = False
728 if self.amount_e.is_shortcut:
729 self.amount_e.is_shortcut = False
730 sendable = self.get_sendable_balance()
731 # there is only one output because we are completely spending inputs
732 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, coins = self.get_coins())
733 fee = self.wallet.estimated_fee(inputs, 1)
735 self.amount_e.setAmount(amount)
736 self.fee_e.setAmount(fee)
739 amount = self.amount_e.get_amount()
740 fee = self.fee_e.get_amount()
742 if not is_fee: fee = None
745 # assume that there will be 2 outputs (one for change)
746 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, coins = self.get_coins())
748 self.fee_e.setAmount(fee)
751 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
755 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
756 self.funds_error = True
757 text = _( "Not enough funds" )
758 c, u = self.wallet.get_frozen_balance()
759 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
761 self.statusBar().showMessage(text)
762 self.amount_e.setPalette(palette)
763 self.fee_e.setPalette(palette)
765 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
766 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
768 run_hook('create_send_tab', grid)
771 def from_list_delete(self, item):
772 i = self.from_list.indexOfTopLevelItem(item)
774 self.redraw_from_list()
776 def from_list_menu(self, position):
777 item = self.from_list.itemAt(position)
779 menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
780 menu.exec_(self.from_list.viewport().mapToGlobal(position))
782 def set_pay_from(self, domain = None):
783 self.pay_from = [] if domain == [] else self.wallet.get_unspent_coins(domain)
784 self.redraw_from_list()
786 def redraw_from_list(self):
787 self.from_list.clear()
788 self.from_label.setHidden(len(self.pay_from) == 0)
789 self.from_list.setHidden(len(self.pay_from) == 0)
792 h = x.get('prevout_hash')
793 return h[0:8] + '...' + h[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
795 for item in self.pay_from:
796 self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
798 def update_completions(self):
800 for addr,label in self.wallet.labels.items():
801 if addr in self.wallet.addressbook:
802 l.append( label + ' <' + addr + '>')
804 run_hook('update_completions', l)
805 self.completions.setStringList(l)
809 return lambda s, *args: s.do_protect(func, args)
812 def read_send_tab(self):
814 if self.payment_request and self.payment_request.has_expired():
815 QMessageBox.warning(self, _('Error'), _('Payment request has expired'), _('OK'))
818 label = unicode( self.message_e.text() )
820 if self.payment_request:
821 outputs = self.payment_request.get_outputs()
823 outputs = self.payto_e.get_outputs()
826 QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
829 for addr, x in outputs:
830 if addr is None or not bitcoin.is_address(addr):
831 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address'), _('OK'))
834 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
837 amount = sum(map(lambda x:x[1], outputs))
839 fee = self.fee_e.get_amount()
841 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
844 confirm_amount = self.config.get('confirm_amount', 100000000)
845 if amount >= confirm_amount:
846 o = '\n'.join(map(lambda x:x[0], outputs))
847 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
850 confirm_fee = self.config.get('confirm_fee', 100000)
851 if fee >= confirm_fee:
852 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()}):
855 coins = self.get_coins()
856 return outputs, fee, label, coins
860 r = self.read_send_tab()
863 outputs, fee, label, coins = r
864 self.send_tx(outputs, fee, label, coins)
868 def send_tx(self, outputs, fee, label, coins, password):
869 self.send_button.setDisabled(True)
871 # first, create an unsigned tx
873 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
875 except Exception as e:
876 traceback.print_exc(file=sys.stdout)
877 self.show_message(str(e))
878 self.send_button.setDisabled(False)
881 # call hook to see if plugin needs gui interaction
882 run_hook('send_tx', tx)
888 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
889 self.wallet.sign_transaction(tx, keypairs, password)
890 return tx, fee, label
892 def sign_done(tx, fee, label):
894 self.show_message(tx.error)
895 self.send_button.setDisabled(False)
897 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
898 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
899 self.send_button.setDisabled(False)
902 self.wallet.set_label(tx.hash(), label)
904 if not tx.is_complete() or self.config.get('show_before_broadcast'):
905 self.show_transaction(tx)
907 self.send_button.setDisabled(False)
910 self.broadcast_transaction(tx)
912 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
913 self.waiting_dialog.start()
917 def broadcast_transaction(self, tx):
919 def broadcast_thread():
920 pr = self.payment_request
922 return self.wallet.sendtx(tx)
925 self.payment_request = None
926 return False, _("Payment request has expired")
928 status, msg = self.wallet.sendtx(tx)
932 self.invoices[pr.get_id()] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), pr.get_expiration_date(), PR_PAID, tx.hash())
933 self.wallet.storage.put('invoices', self.invoices)
934 self.update_invoices_tab()
935 self.payment_request = None
936 refund_address = self.wallet.addresses()[0]
937 ack_status, ack_msg = pr.send_ack(str(tx), refund_address)
943 def broadcast_done(status, msg):
945 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
948 QMessageBox.warning(self, _('Error'), msg, _('OK'))
949 self.send_button.setDisabled(False)
951 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
952 self.waiting_dialog.start()
956 def prepare_for_payment_request(self):
957 self.tabs.setCurrentIndex(1)
958 self.payto_e.is_pr = True
959 for e in [self.payto_e, self.amount_e, self.message_e]:
961 for h in [self.payto_help, self.amount_help, self.message_help]:
963 self.payto_e.setText(_("please wait..."))
966 def payment_request_ok(self):
967 pr = self.payment_request
969 if pr_id not in self.invoices:
970 self.invoices[pr_id] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), pr.get_expiration_date(), PR_UNPAID, None)
971 self.wallet.storage.put('invoices', self.invoices)
972 self.update_invoices_tab()
974 print_error('invoice already in list')
976 status = self.invoices[pr_id][4]
977 if status == PR_PAID:
979 self.show_message("invoice already paid")
980 self.payment_request = None
983 self.payto_help.show()
984 self.payto_help.set_alt(lambda: self.show_pr_details(pr))
986 if not pr.has_expired():
987 self.payto_e.setGreen()
989 self.payto_e.setExpired()
991 self.payto_e.setText(pr.domain)
992 self.amount_e.setText(self.format_amount(pr.get_amount()))
993 self.message_e.setText(pr.get_memo())
995 def payment_request_error(self):
997 self.show_message(self.payment_request.error)
998 self.payment_request = None
1000 def pay_from_URI(self,URI):
1003 address, amount, label, message, request_url = util.parse_URI(URI)
1005 address, amount, label, message, request_url = util.parse_URI(URI)
1006 except Exception as e:
1007 QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URI:') + '\n' + str(e), _('OK'))
1010 self.tabs.setCurrentIndex(1)
1014 if self.wallet.labels.get(address) != label:
1015 if self.question(_('Save label "%s" for address %s ?'%(label,address))):
1016 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1017 self.wallet.addressbook.append(address)
1018 self.wallet.set_label(address, label)
1020 label = self.wallet.labels.get(address)
1022 self.payto_e.setText(label + ' <'+ address +'>' if label else address)
1024 self.message_e.setText(message)
1026 self.amount_e.setAmount(amount)
1029 from electrum import paymentrequest
1030 def payment_request():
1031 self.payment_request = paymentrequest.PaymentRequest(self.config)
1032 self.payment_request.read(request_url)
1033 if self.payment_request.verify():
1034 self.emit(SIGNAL('payment_request_ok'))
1036 self.emit(SIGNAL('payment_request_error'))
1038 self.pr_thread = threading.Thread(target=payment_request).start()
1039 self.prepare_for_payment_request()
1044 self.payto_e.is_pr = False
1045 self.payto_sig.setVisible(False)
1046 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1050 for h in [self.payto_help, self.amount_help, self.message_help]:
1053 self.payto_help.set_alt(None)
1054 self.set_pay_from([])
1055 self.update_status()
1059 def set_addrs_frozen(self,addrs,freeze):
1061 if not addr: continue
1062 if addr in self.wallet.frozen_addresses and not freeze:
1063 self.wallet.unfreeze(addr)
1064 elif addr not in self.wallet.frozen_addresses and freeze:
1065 self.wallet.freeze(addr)
1066 self.update_receive_tab()
1070 def create_list_tab(self, headers):
1071 "generic tab creation method"
1072 l = MyTreeWidget(self)
1073 l.setColumnCount( len(headers) )
1074 l.setHeaderLabels( headers )
1077 vbox = QVBoxLayout()
1084 vbox.addWidget(buttons)
1089 def create_receive_tab(self):
1090 l, w = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1091 for i,width in enumerate(self.column_widths['receive']):
1092 l.setColumnWidth(i, width)
1093 l.setContextMenuPolicy(Qt.CustomContextMenu)
1094 l.customContextMenuRequested.connect(self.create_receive_menu)
1095 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1096 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1097 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1098 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1099 self.receive_list = l
1105 def save_column_widths(self):
1106 self.column_widths["receive"] = []
1107 for i in range(self.receive_list.columnCount() -1):
1108 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1110 self.column_widths["history"] = []
1111 for i in range(self.history_list.columnCount() - 1):
1112 self.column_widths["history"].append(self.history_list.columnWidth(i))
1114 self.column_widths["contacts"] = []
1115 for i in range(self.contacts_list.columnCount() - 1):
1116 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1118 self.config.set_key("column_widths_2", self.column_widths, True)
1121 def create_contacts_tab(self):
1122 l, w = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1123 l.setContextMenuPolicy(Qt.CustomContextMenu)
1124 l.customContextMenuRequested.connect(self.create_contact_menu)
1125 for i,width in enumerate(self.column_widths['contacts']):
1126 l.setColumnWidth(i, width)
1127 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1128 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1129 self.contacts_list = l
1133 def create_invoices_tab(self):
1134 l, w = self.create_list_tab([_('Requestor'), _('Memo'),_('Amount'), _('Status')])
1136 h.setStretchLastSection(False)
1137 h.setResizeMode(1, QHeaderView.Stretch)
1138 l.setContextMenuPolicy(Qt.CustomContextMenu)
1139 l.customContextMenuRequested.connect(self.create_invoice_menu)
1140 self.invoices_list = l
1143 def update_invoices_tab(self):
1144 invoices = self.wallet.storage.get('invoices', {})
1145 l = self.invoices_list
1147 for key, value in invoices.items():
1149 domain, memo, amount, expiration_date, status, tx_hash = value
1153 if status == PR_UNPAID and expiration_date and expiration_date < time.time():
1155 item = QTreeWidgetItem( [ domain, memo, self.format_amount(amount), format_status(status)] )
1156 l.addTopLevelItem(item)
1158 l.setCurrentItem(l.topLevelItem(0))
1162 def delete_imported_key(self, addr):
1163 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1164 self.wallet.delete_imported_key(addr)
1165 self.update_receive_tab()
1166 self.update_history_tab()
1168 def edit_account_label(self, k):
1169 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1171 label = unicode(text)
1172 self.wallet.set_label(k,label)
1173 self.update_receive_tab()
1175 def account_set_expanded(self, item, k, b):
1177 self.accounts_expanded[k] = b
1179 def create_account_menu(self, position, k, item):
1181 if item.isExpanded():
1182 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1184 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1185 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1186 if self.wallet.seed_version > 4:
1187 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1188 if self.wallet.account_is_pending(k):
1189 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1190 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1192 def delete_pending_account(self, k):
1193 self.wallet.delete_pending_account(k)
1194 self.update_receive_tab()
1196 def create_receive_menu(self, position):
1197 # fixme: this function apparently has a side effect.
1198 # if it is not called the menu pops up several times
1199 #self.receive_list.selectedIndexes()
1201 selected = self.receive_list.selectedItems()
1202 multi_select = len(selected) > 1
1203 addrs = [unicode(item.text(0)) for item in selected]
1204 if not multi_select:
1205 item = self.receive_list.itemAt(position)
1209 if not is_valid(addr):
1210 k = str(item.data(0,32).toString())
1212 self.create_account_menu(position, k, item)
1214 item.setExpanded(not item.isExpanded())
1218 if not multi_select:
1219 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1220 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1221 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1222 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1223 if not self.wallet.is_watching_only():
1224 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1225 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1226 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1227 if self.wallet.is_imported(addr):
1228 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1230 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1231 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1232 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1233 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1235 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1236 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1238 run_hook('receive_menu', menu, addrs)
1239 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1242 def get_sendable_balance(self):
1243 return sum(map(lambda x:x['value'], self.get_coins()))
1246 def get_coins(self):
1248 return self.pay_from
1250 domain = self.wallet.get_account_addresses(self.current_account)
1251 for i in self.wallet.frozen_addresses:
1252 if i in domain: domain.remove(i)
1253 return self.wallet.get_unspent_coins(domain)
1256 def send_from_addresses(self, addrs):
1257 self.set_pay_from( addrs )
1258 self.tabs.setCurrentIndex(1)
1261 def payto(self, addr):
1263 label = self.wallet.labels.get(addr)
1264 m_addr = label + ' <' + addr + '>' if label else addr
1265 self.tabs.setCurrentIndex(1)
1266 self.payto_e.setText(m_addr)
1267 self.amount_e.setFocus()
1270 def delete_contact(self, x):
1271 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1272 self.wallet.delete_contact(x)
1273 self.wallet.set_label(x, None)
1274 self.update_history_tab()
1275 self.update_contacts_tab()
1276 self.update_completions()
1279 def create_contact_menu(self, position):
1280 item = self.contacts_list.itemAt(position)
1283 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1285 addr = unicode(item.text(0))
1286 label = unicode(item.text(1))
1287 is_editable = item.data(0,32).toBool()
1288 payto_addr = item.data(0,33).toString()
1289 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1290 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1291 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1293 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1294 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1296 run_hook('create_contact_menu', menu, item)
1297 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1299 def delete_invoice(self, key):
1300 self.invoices.pop(key)
1301 self.wallet.storage.put('invoices', self.invoices)
1302 self.update_invoices_tab()
1304 def show_invoice(self, key):
1305 from electrum.paymentrequest import PaymentRequest
1306 domain, memo, value, expiration, status, tx_hash = self.invoices[key]
1307 pr = PaymentRequest(self.config)
1311 self.show_pr_details(pr)
1313 def show_pr_details(self, pr):
1314 msg = 'Domain: ' + pr.domain
1315 msg += '\nStatus: ' + pr.get_status()
1316 msg += '\nMemo: ' + pr.get_memo()
1317 msg += '\nPayment URL: ' + pr.payment_url
1318 msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[0] + ' ' + self.format_amount(x[1])+ self.base_unit(), pr.get_outputs()))
1319 QMessageBox.information(self, 'Invoice', msg , 'OK')
1321 def do_pay_invoice(self, key):
1322 from electrum.paymentrequest import PaymentRequest
1323 domain, memo, value, status, tx_hash = self.invoices[key]
1324 pr = PaymentRequest(self.config)
1327 self.payment_request = pr
1328 self.prepare_for_payment_request()
1330 self.payment_request_ok()
1332 self.payment_request_error()
1335 def create_invoice_menu(self, position):
1336 item = self.invoices_list.itemAt(position)
1339 k = self.invoices_list.indexOfTopLevelItem(item)
1340 key = self.invoices.keys()[k]
1341 domain, memo, value, expiration, status, tx_hash = self.invoices[key]
1343 menu.addAction(_("Details"), lambda: self.show_invoice(key))
1344 if status == PR_UNPAID:
1345 menu.addAction(_("Pay Now"), lambda: self.do_pay_invoice(key))
1346 menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
1347 menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
1350 def update_receive_item(self, item):
1351 item.setFont(0, QFont(MONOSPACE_FONT))
1352 address = str(item.data(0,0).toString())
1353 label = self.wallet.labels.get(address,'')
1354 item.setData(1,0,label)
1355 item.setData(0,32, True) # is editable
1357 run_hook('update_receive_item', address, item)
1359 if not self.wallet.is_mine(address): return
1361 c, u = self.wallet.get_addr_balance(address)
1362 balance = self.format_amount(c + u)
1363 item.setData(2,0,balance)
1365 if address in self.wallet.frozen_addresses:
1366 item.setBackgroundColor(0, QColor('lightblue'))
1369 def update_receive_tab(self):
1370 l = self.receive_list
1371 # extend the syntax for consistency
1372 l.addChild = l.addTopLevelItem
1373 l.insertChild = l.insertTopLevelItem
1377 accounts = self.wallet.get_accounts()
1378 if self.current_account is None:
1379 account_items = sorted(accounts.items())
1381 account_items = [(self.current_account, accounts.get(self.current_account))]
1384 for k, account in account_items:
1386 if len(accounts) > 1:
1387 name = self.wallet.get_account_name(k)
1388 c,u = self.wallet.get_account_balance(k)
1389 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1390 l.addTopLevelItem(account_item)
1391 account_item.setExpanded(self.accounts_expanded.get(k, True))
1392 account_item.setData(0, 32, k)
1396 sequences = [0,1] if account.has_change() else [0]
1397 for is_change in sequences:
1398 if len(sequences) > 1:
1399 name = _("Receiving") if not is_change else _("Change")
1400 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1401 account_item.addChild(seq_item)
1403 seq_item.setExpanded(True)
1405 seq_item = account_item
1407 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1413 for address in account.get_addresses(is_change):
1415 num, is_used = self.wallet.is_used(address)
1418 if gap > self.wallet.gap_limit:
1423 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1424 self.update_receive_item(item)
1426 item.setBackgroundColor(1, QColor('red'))
1430 seq_item.insertChild(0,used_item)
1432 used_item.addChild(item)
1434 seq_item.addChild(item)
1436 # we use column 1 because column 0 may be hidden
1437 l.setCurrentItem(l.topLevelItem(0),1)
1440 def update_contacts_tab(self):
1441 l = self.contacts_list
1444 for address in self.wallet.addressbook:
1445 label = self.wallet.labels.get(address,'')
1446 n = self.wallet.get_num_tx(address)
1447 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1448 item.setFont(0, QFont(MONOSPACE_FONT))
1449 # 32 = label can be edited (bool)
1450 item.setData(0,32, True)
1452 item.setData(0,33, address)
1453 l.addTopLevelItem(item)
1455 run_hook('update_contacts_tab', l)
1456 l.setCurrentItem(l.topLevelItem(0))
1460 def create_console_tab(self):
1461 from console import Console
1462 self.console = console = Console()
1466 def update_console(self):
1467 console = self.console
1468 console.history = self.config.get("console-history",[])
1469 console.history_index = len(console.history)
1471 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1472 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1474 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1476 def mkfunc(f, method):
1477 return lambda *args: apply( f, (method, args, self.password_dialog ))
1479 if m[0]=='_' or m in ['network','wallet']: continue
1480 methods[m] = mkfunc(c._run, m)
1482 console.updateNamespace(methods)
1485 def change_account(self,s):
1486 if s == _("All accounts"):
1487 self.current_account = None
1489 accounts = self.wallet.get_account_names()
1490 for k, v in accounts.items():
1492 self.current_account = k
1493 self.update_history_tab()
1494 self.update_status()
1495 self.update_receive_tab()
1497 def create_status_bar(self):
1500 sb.setFixedHeight(35)
1501 qtVersion = qVersion()
1503 self.balance_label = QLabel("")
1504 sb.addWidget(self.balance_label)
1506 from version_getter import UpdateLabel
1507 self.updatelabel = UpdateLabel(self.config, sb)
1509 self.account_selector = QComboBox()
1510 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1511 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1512 sb.addPermanentWidget(self.account_selector)
1514 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1515 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1517 self.lock_icon = QIcon()
1518 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1519 sb.addPermanentWidget( self.password_button )
1521 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1522 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1523 sb.addPermanentWidget( self.seed_button )
1524 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1525 sb.addPermanentWidget( self.status_button )
1527 run_hook('create_status_bar', (sb,))
1529 self.setStatusBar(sb)
1532 def update_lock_icon(self):
1533 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1534 self.password_button.setIcon( icon )
1537 def update_buttons_on_seed(self):
1538 if self.wallet.has_seed():
1539 self.seed_button.show()
1541 self.seed_button.hide()
1543 if not self.wallet.is_watching_only():
1544 self.password_button.show()
1545 self.send_button.setText(_("Send"))
1547 self.password_button.hide()
1548 self.send_button.setText(_("Create unsigned transaction"))
1551 def change_password_dialog(self):
1552 from password_dialog import PasswordDialog
1553 d = PasswordDialog(self.wallet, self)
1555 self.update_lock_icon()
1558 def new_contact_dialog(self):
1561 d.setWindowTitle(_("New Contact"))
1562 vbox = QVBoxLayout(d)
1563 vbox.addWidget(QLabel(_('New Contact')+':'))
1565 grid = QGridLayout()
1568 grid.addWidget(QLabel(_("Address")), 1, 0)
1569 grid.addWidget(line1, 1, 1)
1570 grid.addWidget(QLabel(_("Name")), 2, 0)
1571 grid.addWidget(line2, 2, 1)
1573 vbox.addLayout(grid)
1574 vbox.addLayout(ok_cancel_buttons(d))
1579 address = str(line1.text())
1580 label = unicode(line2.text())
1582 if not is_valid(address):
1583 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1586 self.wallet.add_contact(address)
1588 self.wallet.set_label(address, label)
1590 self.update_contacts_tab()
1591 self.update_history_tab()
1592 self.update_completions()
1593 self.tabs.setCurrentIndex(3)
1597 def new_account_dialog(self, password):
1599 dialog = QDialog(self)
1601 dialog.setWindowTitle(_("New Account"))
1603 vbox = QVBoxLayout()
1604 vbox.addWidget(QLabel(_('Account name')+':'))
1607 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1608 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1613 vbox.addLayout(ok_cancel_buttons(dialog))
1614 dialog.setLayout(vbox)
1618 name = str(e.text())
1621 self.wallet.create_pending_account(name, password)
1622 self.update_receive_tab()
1623 self.tabs.setCurrentIndex(2)
1628 def show_master_public_keys(self):
1630 dialog = QDialog(self)
1632 dialog.setWindowTitle(_("Master Public Keys"))
1634 main_layout = QGridLayout()
1635 mpk_dict = self.wallet.get_master_public_keys()
1637 for key, value in mpk_dict.items():
1638 main_layout.addWidget(QLabel(key), i, 0)
1639 mpk_text = QTextEdit()
1640 mpk_text.setReadOnly(True)
1641 mpk_text.setMaximumHeight(170)
1642 mpk_text.setText(value)
1643 main_layout.addWidget(mpk_text, i + 1, 0)
1646 vbox = QVBoxLayout()
1647 vbox.addLayout(main_layout)
1648 vbox.addLayout(close_button(dialog))
1650 dialog.setLayout(vbox)
1655 def show_seed_dialog(self, password):
1656 if not self.wallet.has_seed():
1657 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1661 mnemonic = self.wallet.get_mnemonic(password)
1663 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1665 from seed_dialog import SeedDialog
1666 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1671 def show_qrcode(self, data, title = _("QR code")):
1674 d = QRDialog(data, self, title)
1678 def do_protect(self, func, args):
1679 if self.wallet.use_encryption:
1680 password = self.password_dialog()
1686 if args != (False,):
1687 args = (self,) + args + (password,)
1689 args = (self,password)
1693 def show_public_keys(self, address):
1694 if not address: return
1696 pubkey_list = self.wallet.get_public_keys(address)
1697 except Exception as e:
1698 traceback.print_exc(file=sys.stdout)
1699 self.show_message(str(e))
1703 d.setMinimumSize(600, 200)
1705 vbox = QVBoxLayout()
1706 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1707 vbox.addWidget( QLabel(_("Public key") + ':'))
1709 keys.setReadOnly(True)
1710 keys.setText('\n'.join(pubkey_list))
1711 vbox.addWidget(keys)
1712 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1713 vbox.addLayout(close_button(d))
1718 def show_private_key(self, address, password):
1719 if not address: return
1721 pk_list = self.wallet.get_private_key(address, password)
1722 except Exception as e:
1723 traceback.print_exc(file=sys.stdout)
1724 self.show_message(str(e))
1728 d.setMinimumSize(600, 200)
1730 vbox = QVBoxLayout()
1731 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1732 vbox.addWidget( QLabel(_("Private key") + ':'))
1734 keys.setReadOnly(True)
1735 keys.setText('\n'.join(pk_list))
1736 vbox.addWidget(keys)
1737 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1738 vbox.addLayout(close_button(d))
1744 def do_sign(self, address, message, signature, password):
1745 message = unicode(message.toPlainText())
1746 message = message.encode('utf-8')
1748 sig = self.wallet.sign_message(str(address.text()), message, password)
1749 signature.setText(sig)
1750 except Exception as e:
1751 self.show_message(str(e))
1753 def do_verify(self, address, message, signature):
1754 message = unicode(message.toPlainText())
1755 message = message.encode('utf-8')
1756 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1757 self.show_message(_("Signature verified"))
1759 self.show_message(_("Error: wrong signature"))
1762 def sign_verify_message(self, address=''):
1765 d.setWindowTitle(_('Sign/verify Message'))
1766 d.setMinimumSize(410, 290)
1768 layout = QGridLayout(d)
1770 message_e = QTextEdit()
1771 layout.addWidget(QLabel(_('Message')), 1, 0)
1772 layout.addWidget(message_e, 1, 1)
1773 layout.setRowStretch(2,3)
1775 address_e = QLineEdit()
1776 address_e.setText(address)
1777 layout.addWidget(QLabel(_('Address')), 2, 0)
1778 layout.addWidget(address_e, 2, 1)
1780 signature_e = QTextEdit()
1781 layout.addWidget(QLabel(_('Signature')), 3, 0)
1782 layout.addWidget(signature_e, 3, 1)
1783 layout.setRowStretch(3,1)
1785 hbox = QHBoxLayout()
1787 b = QPushButton(_("Sign"))
1788 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1791 b = QPushButton(_("Verify"))
1792 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1795 b = QPushButton(_("Close"))
1796 b.clicked.connect(d.accept)
1798 layout.addLayout(hbox, 4, 1)
1803 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1805 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1806 message_e.setText(decrypted)
1807 except Exception as e:
1808 self.show_message(str(e))
1811 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1812 message = unicode(message_e.toPlainText())
1813 message = message.encode('utf-8')
1815 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1816 encrypted_e.setText(encrypted)
1817 except Exception as e:
1818 self.show_message(str(e))
1822 def encrypt_message(self, address = ''):
1825 d.setWindowTitle(_('Encrypt/decrypt Message'))
1826 d.setMinimumSize(610, 490)
1828 layout = QGridLayout(d)
1830 message_e = QTextEdit()
1831 layout.addWidget(QLabel(_('Message')), 1, 0)
1832 layout.addWidget(message_e, 1, 1)
1833 layout.setRowStretch(2,3)
1835 pubkey_e = QLineEdit()
1837 pubkey = self.wallet.getpubkeys(address)[0]
1838 pubkey_e.setText(pubkey)
1839 layout.addWidget(QLabel(_('Public key')), 2, 0)
1840 layout.addWidget(pubkey_e, 2, 1)
1842 encrypted_e = QTextEdit()
1843 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1844 layout.addWidget(encrypted_e, 3, 1)
1845 layout.setRowStretch(3,1)
1847 hbox = QHBoxLayout()
1848 b = QPushButton(_("Encrypt"))
1849 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1852 b = QPushButton(_("Decrypt"))
1853 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1856 b = QPushButton(_("Close"))
1857 b.clicked.connect(d.accept)
1860 layout.addLayout(hbox, 4, 1)
1864 def question(self, msg):
1865 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1867 def show_message(self, msg):
1868 QMessageBox.information(self, _('Message'), msg, _('OK'))
1870 def password_dialog(self, msg=None):
1873 d.setWindowTitle(_("Enter Password"))
1878 vbox = QVBoxLayout()
1880 msg = _('Please enter your password')
1881 vbox.addWidget(QLabel(msg))
1883 grid = QGridLayout()
1885 grid.addWidget(QLabel(_('Password')), 1, 0)
1886 grid.addWidget(pw, 1, 1)
1887 vbox.addLayout(grid)
1889 vbox.addLayout(ok_cancel_buttons(d))
1892 run_hook('password_dialog', pw, grid, 1)
1893 if not d.exec_(): return
1894 return unicode(pw.text())
1903 def tx_from_text(self, txt):
1904 "json or raw hexadecimal"
1907 tx = Transaction(txt)
1913 tx_dict = json.loads(str(txt))
1914 assert "hex" in tx_dict.keys()
1915 tx = Transaction(tx_dict["hex"])
1916 if tx_dict.has_key("input_info"):
1917 input_info = json.loads(tx_dict['input_info'])
1918 tx.add_input_info(input_info)
1921 traceback.print_exc(file=sys.stdout)
1924 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1928 def read_tx_from_file(self):
1929 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1933 with open(fileName, "r") as f:
1934 file_content = f.read()
1935 except (ValueError, IOError, os.error), reason:
1936 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1938 return self.tx_from_text(file_content)
1942 def sign_raw_transaction(self, tx, input_info, password):
1943 self.wallet.signrawtransaction(tx, input_info, [], password)
1945 def do_process_from_text(self):
1946 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1949 tx = self.tx_from_text(text)
1951 self.show_transaction(tx)
1953 def do_process_from_file(self):
1954 tx = self.read_tx_from_file()
1956 self.show_transaction(tx)
1958 def do_process_from_txid(self):
1959 from electrum import transaction
1960 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1962 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1964 tx = transaction.Transaction(r)
1966 self.show_transaction(tx)
1968 self.show_message("unknown transaction")
1970 def do_process_from_csvReader(self, csvReader):
1975 for position, row in enumerate(csvReader):
1977 if not is_valid(address):
1978 errors.append((position, address))
1980 amount = Decimal(row[1])
1981 amount = int(100000000*amount)
1982 outputs.append((address, amount))
1983 except (ValueError, IOError, os.error), reason:
1984 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1988 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1989 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1993 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1994 except Exception as e:
1995 self.show_message(str(e))
1998 self.show_transaction(tx)
2000 def do_process_from_csv_file(self):
2001 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
2005 with open(fileName, "r") as f:
2006 csvReader = csv.reader(f)
2007 self.do_process_from_csvReader(csvReader)
2008 except (ValueError, IOError, os.error), reason:
2009 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2012 def do_process_from_csv_text(self):
2013 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
2014 + _("Format: address, amount. One output per line"), _("Load CSV"))
2017 f = StringIO.StringIO(text)
2018 csvReader = csv.reader(f)
2019 self.do_process_from_csvReader(csvReader)
2024 def export_privkeys_dialog(self, password):
2025 if self.wallet.is_watching_only():
2026 self.show_message(_("This is a watching-only wallet"))
2030 d.setWindowTitle(_('Private keys'))
2031 d.setMinimumSize(850, 300)
2032 vbox = QVBoxLayout(d)
2034 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
2035 _("Exposing a single private key can compromise your entire wallet!"),
2036 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
2037 vbox.addWidget(QLabel(msg))
2043 defaultname = 'electrum-private-keys.csv'
2044 select_msg = _('Select file to export your private keys to')
2045 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2046 vbox.addLayout(hbox)
2048 h, b = ok_cancel_buttons2(d, _('Export'))
2053 addresses = self.wallet.addresses(True)
2055 def privkeys_thread():
2056 for addr in addresses:
2060 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
2061 d.emit(SIGNAL('computing_privkeys'))
2062 d.emit(SIGNAL('show_privkeys'))
2064 def show_privkeys():
2065 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
2069 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
2070 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
2071 threading.Thread(target=privkeys_thread).start()
2077 filename = filename_e.text()
2082 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
2083 except (IOError, os.error), reason:
2084 export_error_label = _("Electrum was unable to produce a private key-export.")
2085 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2087 except Exception as e:
2088 self.show_message(str(e))
2091 self.show_message(_("Private keys exported."))
2094 def do_export_privkeys(self, fileName, pklist, is_csv):
2095 with open(fileName, "w+") as f:
2097 transaction = csv.writer(f)
2098 transaction.writerow(["address", "private_key"])
2099 for addr, pk in pklist.items():
2100 transaction.writerow(["%34s"%addr,pk])
2103 f.write(json.dumps(pklist, indent = 4))
2106 def do_import_labels(self):
2107 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2108 if not labelsFile: return
2110 f = open(labelsFile, 'r')
2113 for key, value in json.loads(data).items():
2114 self.wallet.set_label(key, value)
2115 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2116 except (IOError, os.error), reason:
2117 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2120 def do_export_labels(self):
2121 labels = self.wallet.labels
2123 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2125 with open(fileName, 'w+') as f:
2126 json.dump(labels, f)
2127 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2128 except (IOError, os.error), reason:
2129 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2132 def export_history_dialog(self):
2135 d.setWindowTitle(_('Export History'))
2136 d.setMinimumSize(400, 200)
2137 vbox = QVBoxLayout(d)
2139 defaultname = os.path.expanduser('~/electrum-history.csv')
2140 select_msg = _('Select file to export your wallet transactions to')
2142 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2143 vbox.addLayout(hbox)
2147 h, b = ok_cancel_buttons2(d, _('Export'))
2152 filename = filename_e.text()
2157 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2158 except (IOError, os.error), reason:
2159 export_error_label = _("Electrum was unable to produce a transaction export.")
2160 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2163 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2166 def do_export_history(self, wallet, fileName, is_csv):
2167 history = wallet.get_tx_history()
2169 for item in history:
2170 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2172 if timestamp is not None:
2174 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2175 except [RuntimeError, TypeError, NameError] as reason:
2176 time_string = "unknown"
2179 time_string = "unknown"
2181 time_string = "pending"
2183 if value is not None:
2184 value_string = format_satoshis(value, True)
2189 fee_string = format_satoshis(fee, True)
2194 label, is_default_label = wallet.get_label(tx_hash)
2195 label = label.encode('utf-8')
2199 balance_string = format_satoshis(balance, False)
2201 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2203 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2205 with open(fileName, "w+") as f:
2207 transaction = csv.writer(f)
2208 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2210 transaction.writerow(line)
2213 f.write(json.dumps(lines, indent = 4))
2216 def sweep_key_dialog(self):
2218 d.setWindowTitle(_('Sweep private keys'))
2219 d.setMinimumSize(600, 300)
2221 vbox = QVBoxLayout(d)
2222 vbox.addWidget(QLabel(_("Enter private keys")))
2224 keys_e = QTextEdit()
2225 keys_e.setTabChangesFocus(True)
2226 vbox.addWidget(keys_e)
2228 h, address_e = address_field(self.wallet.addresses())
2232 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2233 vbox.addLayout(hbox)
2234 button.setEnabled(False)
2237 addr = str(address_e.text())
2238 if bitcoin.is_address(addr):
2242 pk = str(keys_e.toPlainText()).strip()
2243 if Wallet.is_private_key(pk):
2246 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2247 keys_e.textChanged.connect(f)
2248 address_e.textChanged.connect(f)
2252 fee = self.wallet.fee
2253 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2254 self.show_transaction(tx)
2258 def do_import_privkey(self, password):
2259 if not self.wallet.has_imported_keys():
2260 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2261 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2262 + _('Are you sure you understand what you are doing?'), 3, 4)
2265 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2268 text = str(text).split()
2273 addr = self.wallet.import_key(key, password)
2274 except Exception as e:
2280 addrlist.append(addr)
2282 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2284 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2285 self.update_receive_tab()
2286 self.update_history_tab()
2289 def settings_dialog(self):
2291 d.setWindowTitle(_('Electrum Settings'))
2293 vbox = QVBoxLayout()
2294 grid = QGridLayout()
2295 grid.setColumnStretch(0,1)
2297 nz_label = QLabel(_('Display zeros') + ':')
2298 grid.addWidget(nz_label, 0, 0)
2299 nz_e = AmountEdit(None,True)
2300 nz_e.setText("%d"% self.num_zeros)
2301 grid.addWidget(nz_e, 0, 1)
2302 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2303 grid.addWidget(HelpButton(msg), 0, 2)
2304 if not self.config.is_modifiable('num_zeros'):
2305 for w in [nz_e, nz_label]: w.setEnabled(False)
2307 lang_label=QLabel(_('Language') + ':')
2308 grid.addWidget(lang_label, 1, 0)
2309 lang_combo = QComboBox()
2310 from electrum.i18n import languages
2311 lang_combo.addItems(languages.values())
2313 index = languages.keys().index(self.config.get("language",''))
2316 lang_combo.setCurrentIndex(index)
2317 grid.addWidget(lang_combo, 1, 1)
2318 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2319 if not self.config.is_modifiable('language'):
2320 for w in [lang_combo, lang_label]: w.setEnabled(False)
2323 fee_label = QLabel(_('Transaction fee') + ':')
2324 grid.addWidget(fee_label, 2, 0)
2325 fee_e = BTCAmountEdit(self.get_decimal_point)
2326 fee_e.setAmount(self.wallet.fee)
2327 grid.addWidget(fee_e, 2, 1)
2328 msg = _('Fee per kilobyte of transaction.') + '\n' \
2329 + _('Recommended value') + ': ' + self.format_amount(10000) + ' ' + self.base_unit()
2330 grid.addWidget(HelpButton(msg), 2, 2)
2331 if not self.config.is_modifiable('fee_per_kb'):
2332 for w in [fee_e, fee_label]: w.setEnabled(False)
2334 units = ['BTC', 'mBTC']
2335 unit_label = QLabel(_('Base unit') + ':')
2336 grid.addWidget(unit_label, 3, 0)
2337 unit_combo = QComboBox()
2338 unit_combo.addItems(units)
2339 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2340 grid.addWidget(unit_combo, 3, 1)
2341 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2342 + '\n1BTC=1000mBTC.\n' \
2343 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2345 usechange_cb = QCheckBox(_('Use change addresses'))
2346 usechange_cb.setChecked(self.wallet.use_change)
2347 grid.addWidget(usechange_cb, 4, 0)
2348 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2349 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2351 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2352 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2353 grid.addWidget(block_ex_label, 5, 0)
2354 block_ex_combo = QComboBox()
2355 block_ex_combo.addItems(block_explorers)
2356 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2357 grid.addWidget(block_ex_combo, 5, 1)
2358 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2360 show_tx = self.config.get('show_before_broadcast', False)
2361 showtx_cb = QCheckBox(_('Show before broadcast'))
2362 showtx_cb.setChecked(show_tx)
2363 grid.addWidget(showtx_cb, 6, 0)
2364 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2366 vbox.addLayout(grid)
2368 vbox.addLayout(ok_cancel_buttons(d))
2372 if not d.exec_(): return
2374 fee = fee_e.get_amount()
2376 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2379 self.wallet.set_fee(fee)
2381 nz = unicode(nz_e.text())
2386 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2389 if self.num_zeros != nz:
2391 self.config.set_key('num_zeros', nz, True)
2392 self.update_history_tab()
2393 self.update_receive_tab()
2395 usechange_result = usechange_cb.isChecked()
2396 if self.wallet.use_change != usechange_result:
2397 self.wallet.use_change = usechange_result
2398 self.wallet.storage.put('use_change', self.wallet.use_change)
2400 if showtx_cb.isChecked() != show_tx:
2401 self.config.set_key('show_before_broadcast', not show_tx)
2403 unit_result = units[unit_combo.currentIndex()]
2404 if self.base_unit() != unit_result:
2405 self.decimal_point = 8 if unit_result == 'BTC' else 5
2406 self.config.set_key('decimal_point', self.decimal_point, True)
2407 self.update_history_tab()
2408 self.update_status()
2410 need_restart = False
2412 lang_request = languages.keys()[lang_combo.currentIndex()]
2413 if lang_request != self.config.get('language'):
2414 self.config.set_key("language", lang_request, True)
2417 be_result = block_explorers[block_ex_combo.currentIndex()]
2418 self.config.set_key('block_explorer', be_result, True)
2420 run_hook('close_settings_dialog')
2423 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2426 def run_network_dialog(self):
2427 if not self.network:
2429 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2431 def closeEvent(self, event):
2433 self.config.set_key("is_maximized", self.isMaximized())
2434 if not self.isMaximized():
2436 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2437 self.save_column_widths()
2438 self.config.set_key("console-history", self.console.history[-50:], True)
2439 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2443 def plugins_dialog(self):
2444 from electrum.plugins import plugins
2447 d.setWindowTitle(_('Electrum Plugins'))
2450 vbox = QVBoxLayout(d)
2453 scroll = QScrollArea()
2454 scroll.setEnabled(True)
2455 scroll.setWidgetResizable(True)
2456 scroll.setMinimumSize(400,250)
2457 vbox.addWidget(scroll)
2461 w.setMinimumHeight(len(plugins)*35)
2463 grid = QGridLayout()
2464 grid.setColumnStretch(0,1)
2467 def do_toggle(cb, p, w):
2470 if w: w.setEnabled(r)
2472 def mk_toggle(cb, p, w):
2473 return lambda: do_toggle(cb,p,w)
2475 for i, p in enumerate(plugins):
2477 cb = QCheckBox(p.fullname())
2478 cb.setDisabled(not p.is_available())
2479 cb.setChecked(p.is_enabled())
2480 grid.addWidget(cb, i, 0)
2481 if p.requires_settings():
2482 w = p.settings_widget(self)
2483 w.setEnabled( p.is_enabled() )
2484 grid.addWidget(w, i, 1)
2487 cb.clicked.connect(mk_toggle(cb,p,w))
2488 grid.addWidget(HelpButton(p.description()), i, 2)
2490 print_msg(_("Error: cannot display plugin"), p)
2491 traceback.print_exc(file=sys.stdout)
2492 grid.setRowStretch(i+1,1)
2494 vbox.addLayout(close_button(d))
2499 def show_account_details(self, k):
2500 account = self.wallet.accounts[k]
2503 d.setWindowTitle(_('Account Details'))
2506 vbox = QVBoxLayout(d)
2507 name = self.wallet.get_account_name(k)
2508 label = QLabel('Name: ' + name)
2509 vbox.addWidget(label)
2511 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2513 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2515 vbox.addWidget(QLabel(_('Master Public Key:')))
2518 text.setReadOnly(True)
2519 text.setMaximumHeight(170)
2520 vbox.addWidget(text)
2522 mpk_text = '\n'.join( account.get_master_pubkeys() )
2523 text.setText(mpk_text)
2525 vbox.addLayout(close_button(d))