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
49 from qrtextedit import QRTextEdit
51 from decimal import Decimal
59 if platform.system() == 'Windows':
60 MONOSPACE_FONT = 'Lucida Console'
61 elif platform.system() == 'Darwin':
62 MONOSPACE_FONT = 'Monaco'
64 MONOSPACE_FONT = 'monospace'
68 # status of payment requests
71 PR_SENT = 2 # sent but not propagated
72 PR_PAID = 3 # send and propagated
73 PR_ERROR = 4 # could not parse
76 from electrum import ELECTRUM_VERSION
91 class StatusBarButton(QPushButton):
92 def __init__(self, icon, tooltip, func):
93 QPushButton.__init__(self, icon, '')
94 self.setToolTip(tooltip)
96 self.setMaximumWidth(25)
97 self.clicked.connect(func)
99 self.setIconSize(QSize(25,25))
101 def keyPressEvent(self, e):
102 if e.key() == QtCore.Qt.Key_Return:
114 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
116 class ElectrumWindow(QMainWindow):
120 def __init__(self, config, network, gui_object):
121 QMainWindow.__init__(self)
124 self.network = network
125 self.gui_object = gui_object
126 self.tray = gui_object.tray
127 self.go_lite = gui_object.go_lite
130 self.create_status_bar()
131 self.need_update = threading.Event()
133 self.decimal_point = config.get('decimal_point', 5)
134 self.num_zeros = int(config.get('num_zeros',0))
137 set_language(config.get('language'))
139 self.funds_error = False
140 self.completions = QStringListModel()
142 self.tabs = tabs = QTabWidget(self)
143 self.column_widths = self.config.get("column_widths_2", default_column_widths )
144 tabs.addTab(self.create_history_tab(), _('History') )
145 tabs.addTab(self.create_send_tab(), _('Send') )
146 tabs.addTab(self.create_receive_tab(), _('Receive') )
147 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
148 tabs.addTab(self.create_invoices_tab(), _('Invoices') )
149 tabs.addTab(self.create_console_tab(), _('Console') )
150 tabs.setMinimumSize(600, 400)
151 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
152 self.setCentralWidget(tabs)
154 g = self.config.get("winpos-qt",[100, 100, 840, 400])
155 self.setGeometry(g[0], g[1], g[2], g[3])
156 if self.config.get("is_maximized"):
159 self.setWindowIcon(QIcon(":icons/electrum.png"))
162 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
163 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
164 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
165 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
166 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
168 for i in range(tabs.count()):
169 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
171 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
172 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
173 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
174 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
175 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
177 self.history_list.setFocus(True)
181 self.network.register_callback('updated', lambda: self.need_update.set())
182 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
183 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
184 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
185 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
187 # set initial message
188 self.console.showMessage(self.network.banner)
191 self.payment_request = None
193 def update_account_selector(self):
195 accounts = self.wallet.get_account_names()
196 self.account_selector.clear()
197 if len(accounts) > 1:
198 self.account_selector.addItems([_("All accounts")] + accounts.values())
199 self.account_selector.setCurrentIndex(0)
200 self.account_selector.show()
202 self.account_selector.hide()
205 def load_wallet(self, wallet):
209 self.update_wallet_format()
211 self.invoices = self.wallet.storage.get('invoices', {})
212 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
213 self.current_account = self.wallet.storage.get("current_account", None)
214 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
215 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
216 self.setWindowTitle( title )
218 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
219 self.notify_transactions()
220 self.update_account_selector()
222 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
223 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
224 self.password_menu.setEnabled(not self.wallet.is_watching_only())
225 self.seed_menu.setEnabled(self.wallet.has_seed())
226 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
227 self.import_menu.setEnabled(self.wallet.can_import())
229 self.update_lock_icon()
230 self.update_buttons_on_seed()
231 self.update_console()
233 run_hook('load_wallet', wallet)
236 def update_wallet_format(self):
237 # convert old-format imported keys
238 if self.wallet.imported_keys:
239 password = self.password_dialog(_("Please enter your password in order to update imported keys"))
241 self.wallet.convert_imported_keys(password)
243 self.show_message("error")
246 def open_wallet(self):
247 wallet_folder = self.wallet.storage.path
248 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
252 storage = WalletStorage({'wallet_path': filename})
253 if not storage.file_exists:
254 self.show_message("file not found "+ filename)
257 self.wallet.stop_threads()
260 wallet = Wallet(storage)
261 wallet.start_threads(self.network)
263 self.load_wallet(wallet)
267 def backup_wallet(self):
269 path = self.wallet.storage.path
270 wallet_folder = os.path.dirname(path)
271 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
275 new_path = os.path.join(wallet_folder, filename)
278 shutil.copy2(path, new_path)
279 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
280 except (IOError, os.error), reason:
281 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
284 def new_wallet(self):
287 wallet_folder = os.path.dirname(self.wallet.storage.path)
288 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
291 filename = os.path.join(wallet_folder, filename)
293 storage = WalletStorage({'wallet_path': filename})
294 if storage.file_exists:
295 QMessageBox.critical(None, "Error", _("File exists"))
298 wizard = installwizard.InstallWizard(self.config, self.network, storage)
299 wallet = wizard.run('new')
301 self.load_wallet(wallet)
305 def init_menubar(self):
308 file_menu = menubar.addMenu(_("&File"))
309 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
310 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
311 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
312 file_menu.addAction(_("&Quit"), self.close)
314 wallet_menu = menubar.addMenu(_("&Wallet"))
315 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
316 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
318 wallet_menu.addSeparator()
320 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
321 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
322 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
324 wallet_menu.addSeparator()
325 labels_menu = wallet_menu.addMenu(_("&Labels"))
326 labels_menu.addAction(_("&Import"), self.do_import_labels)
327 labels_menu.addAction(_("&Export"), self.do_export_labels)
329 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
330 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
331 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
332 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
333 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
335 tools_menu = menubar.addMenu(_("&Tools"))
337 # Settings / Preferences are all reserved keywords in OSX using this as work around
338 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
339 tools_menu.addAction(_("&Network"), self.run_network_dialog)
340 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
341 tools_menu.addSeparator()
342 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
343 tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
344 tools_menu.addSeparator()
346 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
347 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
348 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
350 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
351 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
352 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
353 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
354 self.raw_transaction_menu = raw_transaction_menu
356 help_menu = menubar.addMenu(_("&Help"))
357 help_menu.addAction(_("&About"), self.show_about)
358 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
359 help_menu.addSeparator()
360 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
361 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
363 self.setMenuBar(menubar)
365 def show_about(self):
366 QMessageBox.about(self, "Electrum",
367 _("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."))
369 def show_report_bug(self):
370 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
371 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
374 def notify_transactions(self):
375 if not self.network or not self.network.is_connected():
378 print_error("Notifying GUI")
379 if len(self.network.pending_transactions_for_notifications) > 0:
380 # Combine the transactions if there are more then three
381 tx_amount = len(self.network.pending_transactions_for_notifications)
384 for tx in self.network.pending_transactions_for_notifications:
385 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
389 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
390 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
392 self.network.pending_transactions_for_notifications = []
394 for tx in self.network.pending_transactions_for_notifications:
396 self.network.pending_transactions_for_notifications.remove(tx)
397 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
399 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
401 def notify(self, message):
402 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
406 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
407 def getOpenFileName(self, title, filter = ""):
408 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
409 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
410 if fileName and directory != os.path.dirname(fileName):
411 self.config.set_key('io_dir', os.path.dirname(fileName), True)
414 def getSaveFileName(self, title, filename, filter = ""):
415 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
416 path = os.path.join( directory, filename )
417 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
418 if fileName and directory != os.path.dirname(fileName):
419 self.config.set_key('io_dir', os.path.dirname(fileName), True)
423 QMainWindow.close(self)
424 run_hook('close_main_window')
426 def connect_slots(self, sender):
427 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
428 self.previous_payto_e=''
430 def timer_actions(self):
431 if self.need_update.is_set():
433 self.need_update.clear()
434 run_hook('timer_actions')
436 def format_amount(self, x, is_diff=False, whitespaces=False):
437 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
440 def get_decimal_point(self):
441 return self.decimal_point
445 assert self.decimal_point in [5,8]
446 return "BTC" if self.decimal_point == 8 else "mBTC"
449 def update_status(self):
450 if self.network is None or not self.network.is_running():
452 icon = QIcon(":icons/status_disconnected.png")
454 elif self.network.is_connected():
455 if not self.wallet.up_to_date:
456 text = _("Synchronizing...")
457 icon = QIcon(":icons/status_waiting.png")
458 elif self.network.server_lag > 1:
459 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
460 icon = QIcon(":icons/status_lagging.png")
462 c, u = self.wallet.get_account_balance(self.current_account)
463 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
464 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
466 # append fiat balance and price from exchange rate plugin
468 run_hook('get_fiat_status_text', c+u, r)
473 self.tray.setToolTip(text)
474 icon = QIcon(":icons/status_connected.png")
476 text = _("Not connected")
477 icon = QIcon(":icons/status_disconnected.png")
479 self.balance_label.setText(text)
480 self.status_button.setIcon( icon )
483 def update_wallet(self):
485 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
486 self.update_history_tab()
487 self.update_receive_tab()
488 self.update_contacts_tab()
489 self.update_completions()
490 self.update_invoices_tab()
493 def create_history_tab(self):
494 self.history_list = l = MyTreeWidget(self)
496 for i,width in enumerate(self.column_widths['history']):
497 l.setColumnWidth(i, width)
498 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
499 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
500 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
502 l.customContextMenuRequested.connect(self.create_history_menu)
506 def create_history_menu(self, position):
507 self.history_list.selectedIndexes()
508 item = self.history_list.currentItem()
509 be = self.config.get('block_explorer', 'Blockchain.info')
510 if be == 'Blockchain.info':
511 block_explorer = 'https://blockchain.info/tx/'
512 elif be == 'Blockr.io':
513 block_explorer = 'https://blockr.io/tx/info/'
514 elif be == 'Insight.is':
515 block_explorer = 'http://live.insight.is/tx/'
517 tx_hash = str(item.data(0, Qt.UserRole).toString())
518 if not tx_hash: return
520 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
521 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
522 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
523 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
524 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
527 def show_transaction(self, tx):
528 import transaction_dialog
529 d = transaction_dialog.TxDialog(tx, self)
532 def tx_label_clicked(self, item, column):
533 if column==2 and item.isSelected():
535 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
536 self.history_list.editItem( item, column )
537 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
540 def tx_label_changed(self, item, column):
544 tx_hash = str(item.data(0, Qt.UserRole).toString())
545 tx = self.wallet.transactions.get(tx_hash)
546 text = unicode( item.text(2) )
547 self.wallet.set_label(tx_hash, text)
549 item.setForeground(2, QBrush(QColor('black')))
551 text = self.wallet.get_default_label(tx_hash)
552 item.setText(2, text)
553 item.setForeground(2, QBrush(QColor('gray')))
557 def edit_label(self, is_recv):
558 l = self.receive_list if is_recv else self.contacts_list
559 item = l.currentItem()
560 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
561 l.editItem( item, 1 )
562 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
566 def address_label_clicked(self, item, column, l, column_addr, column_label):
567 if column == column_label and item.isSelected():
568 is_editable = item.data(0, 32).toBool()
571 addr = unicode( item.text(column_addr) )
572 label = unicode( item.text(column_label) )
573 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
574 l.editItem( item, column )
575 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
578 def address_label_changed(self, item, column, l, column_addr, column_label):
579 if column == column_label:
580 addr = unicode( item.text(column_addr) )
581 text = unicode( item.text(column_label) )
582 is_editable = item.data(0, 32).toBool()
586 changed = self.wallet.set_label(addr, text)
588 self.update_history_tab()
589 self.update_completions()
591 self.current_item_changed(item)
593 run_hook('item_changed', item, column)
596 def current_item_changed(self, a):
597 run_hook('current_item_changed', a)
601 def update_history_tab(self):
603 self.history_list.clear()
604 for item in self.wallet.get_tx_history(self.current_account):
605 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
606 time_str = _("unknown")
609 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
611 time_str = _("error")
614 time_str = 'unverified'
615 icon = QIcon(":icons/unconfirmed.png")
618 icon = QIcon(":icons/unconfirmed.png")
620 icon = QIcon(":icons/clock%d.png"%conf)
622 icon = QIcon(":icons/confirmed.png")
624 if value is not None:
625 v_str = self.format_amount(value, True, whitespaces=True)
629 balance_str = self.format_amount(balance, whitespaces=True)
632 label, is_default_label = self.wallet.get_label(tx_hash)
634 label = _('Pruned transaction outputs')
635 is_default_label = False
637 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
638 item.setFont(2, QFont(MONOSPACE_FONT))
639 item.setFont(3, QFont(MONOSPACE_FONT))
640 item.setFont(4, QFont(MONOSPACE_FONT))
642 item.setForeground(3, QBrush(QColor("#BC1E1E")))
644 item.setData(0, Qt.UserRole, tx_hash)
645 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
647 item.setForeground(2, QBrush(QColor('grey')))
649 item.setIcon(0, icon)
650 self.history_list.insertTopLevelItem(0,item)
653 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
654 run_hook('history_tab_update')
657 def create_send_tab(self):
660 self.send_grid = grid = QGridLayout(w)
662 grid.setColumnMinimumWidth(3,300)
663 grid.setColumnStretch(5,1)
664 grid.setRowStretch(8, 1)
666 from paytoedit import PayToEdit
667 self.amount_e = BTCAmountEdit(self.get_decimal_point)
668 self.payto_e = PayToEdit(self)
669 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)'))
670 grid.addWidget(QLabel(_('Pay to')), 1, 0)
671 grid.addWidget(self.payto_e, 1, 1, 1, 3)
672 grid.addWidget(self.payto_help, 1, 4)
674 completer = QCompleter()
675 completer.setCaseSensitivity(False)
676 self.payto_e.setCompleter(completer)
677 completer.setModel(self.completions)
679 self.message_e = MyLineEdit()
680 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.'))
681 grid.addWidget(QLabel(_('Description')), 2, 0)
682 grid.addWidget(self.message_e, 2, 1, 1, 3)
683 grid.addWidget(self.message_help, 2, 4)
685 self.from_label = QLabel(_('From'))
686 grid.addWidget(self.from_label, 3, 0)
687 self.from_list = MyTreeWidget(self)
688 self.from_list.setColumnCount(2)
689 self.from_list.setColumnWidth(0, 350)
690 self.from_list.setColumnWidth(1, 50)
691 self.from_list.setHeaderHidden(True)
692 self.from_list.setMaximumHeight(80)
693 self.from_list.setContextMenuPolicy(Qt.CustomContextMenu)
694 self.from_list.customContextMenuRequested.connect(self.from_list_menu)
695 grid.addWidget(self.from_list, 3, 1, 1, 3)
696 self.set_pay_from([])
698 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
699 + _('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.') \
700 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
701 grid.addWidget(QLabel(_('Amount')), 4, 0)
702 grid.addWidget(self.amount_e, 4, 1, 1, 2)
703 grid.addWidget(self.amount_help, 4, 3)
705 self.fee_e = BTCAmountEdit(self.get_decimal_point)
706 grid.addWidget(QLabel(_('Fee')), 5, 0)
707 grid.addWidget(self.fee_e, 5, 1, 1, 2)
708 grid.addWidget(HelpButton(
709 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
710 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
711 + _('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)
713 self.send_button = EnterButton(_("Send"), self.do_send)
714 grid.addWidget(self.send_button, 6, 1)
716 b = EnterButton(_("Clear"), self.do_clear)
717 grid.addWidget(b, 6, 2)
719 self.payto_sig = QLabel('')
720 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
722 #QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
723 #QShortcut(QKeySequence("Down"), w, w.focusNextChild)
726 def entry_changed( is_fee ):
727 self.funds_error = False
729 if self.amount_e.is_shortcut:
730 self.amount_e.is_shortcut = False
731 sendable = self.get_sendable_balance()
732 # there is only one output because we are completely spending inputs
733 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, coins = self.get_coins())
734 fee = self.wallet.estimated_fee(inputs, 1)
736 self.amount_e.setAmount(amount)
737 self.fee_e.setAmount(fee)
740 amount = self.amount_e.get_amount()
741 fee = self.fee_e.get_amount()
743 if not is_fee: fee = None
746 # assume that there will be 2 outputs (one for change)
747 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, coins = self.get_coins())
749 self.fee_e.setAmount(fee)
752 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
756 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
757 self.funds_error = True
758 text = _( "Not enough funds" )
759 c, u = self.wallet.get_frozen_balance()
760 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
762 self.statusBar().showMessage(text)
763 self.amount_e.setPalette(palette)
764 self.fee_e.setPalette(palette)
766 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
767 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
769 run_hook('create_send_tab', grid)
772 def from_list_delete(self, item):
773 i = self.from_list.indexOfTopLevelItem(item)
775 self.redraw_from_list()
777 def from_list_menu(self, position):
778 item = self.from_list.itemAt(position)
780 menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
781 menu.exec_(self.from_list.viewport().mapToGlobal(position))
783 def set_pay_from(self, domain = None):
784 self.pay_from = [] if domain == [] else self.wallet.get_unspent_coins(domain)
785 self.redraw_from_list()
787 def redraw_from_list(self):
788 self.from_list.clear()
789 self.from_label.setHidden(len(self.pay_from) == 0)
790 self.from_list.setHidden(len(self.pay_from) == 0)
793 h = x.get('prevout_hash')
794 return h[0:8] + '...' + h[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
796 for item in self.pay_from:
797 self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
799 def update_completions(self):
801 for addr,label in self.wallet.labels.items():
802 if addr in self.wallet.addressbook:
803 l.append( label + ' <' + addr + '>')
805 run_hook('update_completions', l)
806 self.completions.setStringList(l)
810 return lambda s, *args: s.do_protect(func, args)
813 def read_send_tab(self):
815 if self.payment_request and self.payment_request.has_expired():
816 QMessageBox.warning(self, _('Error'), _('Payment request has expired'), _('OK'))
819 label = unicode( self.message_e.text() )
821 if self.payment_request:
822 outputs = self.payment_request.get_outputs()
824 outputs = self.payto_e.get_outputs()
827 QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
830 for addr, x in outputs:
831 if addr is None or not bitcoin.is_address(addr):
832 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address'), _('OK'))
835 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
838 amount = sum(map(lambda x:x[1], outputs))
840 fee = self.fee_e.get_amount()
842 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
845 confirm_amount = self.config.get('confirm_amount', 100000000)
846 if amount >= confirm_amount:
847 o = '\n'.join(map(lambda x:x[0], outputs))
848 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
851 confirm_fee = self.config.get('confirm_fee', 100000)
852 if fee >= confirm_fee:
853 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()}):
856 coins = self.get_coins()
857 return outputs, fee, label, coins
861 r = self.read_send_tab()
864 outputs, fee, label, coins = r
865 self.send_tx(outputs, fee, label, coins)
869 def send_tx(self, outputs, fee, label, coins, password):
870 self.send_button.setDisabled(True)
872 # first, create an unsigned tx
874 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
876 except Exception as e:
877 traceback.print_exc(file=sys.stdout)
878 self.show_message(str(e))
879 self.send_button.setDisabled(False)
882 # call hook to see if plugin needs gui interaction
883 run_hook('send_tx', tx)
889 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
890 self.wallet.sign_transaction(tx, keypairs, password)
891 return tx, fee, label
893 def sign_done(tx, fee, label):
895 self.show_message(tx.error)
896 self.send_button.setDisabled(False)
898 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
899 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
900 self.send_button.setDisabled(False)
903 self.wallet.set_label(tx.hash(), label)
905 if not tx.is_complete() or self.config.get('show_before_broadcast'):
906 self.show_transaction(tx)
908 self.send_button.setDisabled(False)
911 self.broadcast_transaction(tx)
913 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
914 self.waiting_dialog.start()
918 def broadcast_transaction(self, tx):
920 def broadcast_thread():
921 pr = self.payment_request
923 return self.wallet.sendtx(tx)
926 self.payment_request = None
927 return False, _("Payment request has expired")
929 status, msg = self.wallet.sendtx(tx)
933 self.invoices[pr.get_id()] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), pr.get_expiration_date(), PR_PAID, tx.hash())
934 self.wallet.storage.put('invoices', self.invoices)
935 self.update_invoices_tab()
936 self.payment_request = None
937 refund_address = self.wallet.addresses()[0]
938 ack_status, ack_msg = pr.send_ack(str(tx), refund_address)
944 def broadcast_done(status, msg):
946 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
949 QMessageBox.warning(self, _('Error'), msg, _('OK'))
950 self.send_button.setDisabled(False)
952 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
953 self.waiting_dialog.start()
957 def prepare_for_payment_request(self):
958 self.tabs.setCurrentIndex(1)
959 self.payto_e.is_pr = True
960 for e in [self.payto_e, self.amount_e, self.message_e]:
962 for h in [self.payto_help, self.amount_help, self.message_help]:
964 self.payto_e.setText(_("please wait..."))
967 def payment_request_ok(self):
968 pr = self.payment_request
970 if pr_id not in self.invoices:
971 self.invoices[pr_id] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), pr.get_expiration_date(), PR_UNPAID, None)
972 self.wallet.storage.put('invoices', self.invoices)
973 self.update_invoices_tab()
975 print_error('invoice already in list')
977 status = self.invoices[pr_id][4]
978 if status == PR_PAID:
980 self.show_message("invoice already paid")
981 self.payment_request = None
984 self.payto_help.show()
985 self.payto_help.set_alt(lambda: self.show_pr_details(pr))
987 if not pr.has_expired():
988 self.payto_e.setGreen()
990 self.payto_e.setExpired()
992 self.payto_e.setText(pr.domain)
993 self.amount_e.setText(self.format_amount(pr.get_amount()))
994 self.message_e.setText(pr.get_memo())
996 def payment_request_error(self):
998 self.show_message(self.payment_request.error)
999 self.payment_request = None
1001 def pay_from_URI(self,URI):
1004 address, amount, label, message, request_url = util.parse_URI(URI)
1006 address, amount, label, message, request_url = util.parse_URI(URI)
1007 except Exception as e:
1008 QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URI:') + '\n' + str(e), _('OK'))
1011 self.tabs.setCurrentIndex(1)
1015 if self.wallet.labels.get(address) != label:
1016 if self.question(_('Save label "%s" for address %s ?'%(label,address))):
1017 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1018 self.wallet.addressbook.append(address)
1019 self.wallet.set_label(address, label)
1021 label = self.wallet.labels.get(address)
1023 self.payto_e.setText(label + ' <'+ address +'>' if label else address)
1025 self.message_e.setText(message)
1027 self.amount_e.setAmount(amount)
1030 from electrum import paymentrequest
1031 def payment_request():
1032 self.payment_request = paymentrequest.PaymentRequest(self.config)
1033 self.payment_request.read(request_url)
1034 if self.payment_request.verify():
1035 self.emit(SIGNAL('payment_request_ok'))
1037 self.emit(SIGNAL('payment_request_error'))
1039 self.pr_thread = threading.Thread(target=payment_request).start()
1040 self.prepare_for_payment_request()
1045 self.payto_e.is_pr = False
1046 self.payto_sig.setVisible(False)
1047 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1051 for h in [self.payto_help, self.amount_help, self.message_help]:
1054 self.payto_help.set_alt(None)
1055 self.set_pay_from([])
1056 self.update_status()
1060 def set_addrs_frozen(self,addrs,freeze):
1062 if not addr: continue
1063 if addr in self.wallet.frozen_addresses and not freeze:
1064 self.wallet.unfreeze(addr)
1065 elif addr not in self.wallet.frozen_addresses and freeze:
1066 self.wallet.freeze(addr)
1067 self.update_receive_tab()
1071 def create_list_tab(self, headers):
1072 "generic tab creation method"
1073 l = MyTreeWidget(self)
1074 l.setColumnCount( len(headers) )
1075 l.setHeaderLabels( headers )
1078 vbox = QVBoxLayout()
1085 vbox.addWidget(buttons)
1090 def create_receive_tab(self):
1091 l, w = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1092 for i,width in enumerate(self.column_widths['receive']):
1093 l.setColumnWidth(i, width)
1094 l.setContextMenuPolicy(Qt.CustomContextMenu)
1095 l.customContextMenuRequested.connect(self.create_receive_menu)
1096 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1097 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1098 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1099 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1100 self.receive_list = l
1106 def save_column_widths(self):
1107 self.column_widths["receive"] = []
1108 for i in range(self.receive_list.columnCount() -1):
1109 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1111 self.column_widths["history"] = []
1112 for i in range(self.history_list.columnCount() - 1):
1113 self.column_widths["history"].append(self.history_list.columnWidth(i))
1115 self.column_widths["contacts"] = []
1116 for i in range(self.contacts_list.columnCount() - 1):
1117 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1119 self.config.set_key("column_widths_2", self.column_widths, True)
1122 def create_contacts_tab(self):
1123 l, w = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1124 l.setContextMenuPolicy(Qt.CustomContextMenu)
1125 l.customContextMenuRequested.connect(self.create_contact_menu)
1126 for i,width in enumerate(self.column_widths['contacts']):
1127 l.setColumnWidth(i, width)
1128 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1129 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1130 self.contacts_list = l
1134 def create_invoices_tab(self):
1135 l, w = self.create_list_tab([_('Requestor'), _('Memo'),_('Amount'), _('Status')])
1137 h.setStretchLastSection(False)
1138 h.setResizeMode(1, QHeaderView.Stretch)
1139 l.setContextMenuPolicy(Qt.CustomContextMenu)
1140 l.customContextMenuRequested.connect(self.create_invoice_menu)
1141 self.invoices_list = l
1144 def update_invoices_tab(self):
1145 invoices = self.wallet.storage.get('invoices', {})
1146 l = self.invoices_list
1148 for key, value in invoices.items():
1150 domain, memo, amount, expiration_date, status, tx_hash = value
1154 if status == PR_UNPAID and expiration_date and expiration_date < time.time():
1156 item = QTreeWidgetItem( [ domain, memo, self.format_amount(amount), format_status(status)] )
1157 l.addTopLevelItem(item)
1159 l.setCurrentItem(l.topLevelItem(0))
1163 def delete_imported_key(self, addr):
1164 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1165 self.wallet.delete_imported_key(addr)
1166 self.update_receive_tab()
1167 self.update_history_tab()
1169 def edit_account_label(self, k):
1170 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1172 label = unicode(text)
1173 self.wallet.set_label(k,label)
1174 self.update_receive_tab()
1176 def account_set_expanded(self, item, k, b):
1178 self.accounts_expanded[k] = b
1180 def create_account_menu(self, position, k, item):
1182 if item.isExpanded():
1183 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1185 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1186 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1187 if self.wallet.seed_version > 4:
1188 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1189 if self.wallet.account_is_pending(k):
1190 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1191 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1193 def delete_pending_account(self, k):
1194 self.wallet.delete_pending_account(k)
1195 self.update_receive_tab()
1197 def create_receive_menu(self, position):
1198 # fixme: this function apparently has a side effect.
1199 # if it is not called the menu pops up several times
1200 #self.receive_list.selectedIndexes()
1202 selected = self.receive_list.selectedItems()
1203 multi_select = len(selected) > 1
1204 addrs = [unicode(item.text(0)) for item in selected]
1205 if not multi_select:
1206 item = self.receive_list.itemAt(position)
1210 if not is_valid(addr):
1211 k = str(item.data(0,32).toString())
1213 self.create_account_menu(position, k, item)
1215 item.setExpanded(not item.isExpanded())
1219 if not multi_select:
1220 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1221 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1222 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1223 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1224 if not self.wallet.is_watching_only():
1225 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1226 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1227 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1228 if self.wallet.is_imported(addr):
1229 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1231 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1232 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1233 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1234 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1236 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1237 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1239 run_hook('receive_menu', menu, addrs)
1240 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1243 def get_sendable_balance(self):
1244 return sum(map(lambda x:x['value'], self.get_coins()))
1247 def get_coins(self):
1249 return self.pay_from
1251 domain = self.wallet.get_account_addresses(self.current_account)
1252 for i in self.wallet.frozen_addresses:
1253 if i in domain: domain.remove(i)
1254 return self.wallet.get_unspent_coins(domain)
1257 def send_from_addresses(self, addrs):
1258 self.set_pay_from( addrs )
1259 self.tabs.setCurrentIndex(1)
1262 def payto(self, addr):
1264 label = self.wallet.labels.get(addr)
1265 m_addr = label + ' <' + addr + '>' if label else addr
1266 self.tabs.setCurrentIndex(1)
1267 self.payto_e.setText(m_addr)
1268 self.amount_e.setFocus()
1271 def delete_contact(self, x):
1272 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1273 self.wallet.delete_contact(x)
1274 self.wallet.set_label(x, None)
1275 self.update_history_tab()
1276 self.update_contacts_tab()
1277 self.update_completions()
1280 def create_contact_menu(self, position):
1281 item = self.contacts_list.itemAt(position)
1284 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1286 addr = unicode(item.text(0))
1287 label = unicode(item.text(1))
1288 is_editable = item.data(0,32).toBool()
1289 payto_addr = item.data(0,33).toString()
1290 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1291 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1292 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1294 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1295 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1297 run_hook('create_contact_menu', menu, item)
1298 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1300 def delete_invoice(self, key):
1301 self.invoices.pop(key)
1302 self.wallet.storage.put('invoices', self.invoices)
1303 self.update_invoices_tab()
1305 def show_invoice(self, key):
1306 from electrum.paymentrequest import PaymentRequest
1307 domain, memo, value, expiration, status, tx_hash = self.invoices[key]
1308 pr = PaymentRequest(self.config)
1312 self.show_pr_details(pr)
1314 def show_pr_details(self, pr):
1315 msg = 'Domain: ' + pr.domain
1316 msg += '\nStatus: ' + pr.get_status()
1317 msg += '\nMemo: ' + pr.get_memo()
1318 msg += '\nPayment URL: ' + pr.payment_url
1319 msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[0] + ' ' + self.format_amount(x[1])+ self.base_unit(), pr.get_outputs()))
1320 QMessageBox.information(self, 'Invoice', msg , 'OK')
1322 def do_pay_invoice(self, key):
1323 from electrum.paymentrequest import PaymentRequest
1324 domain, memo, value, expiration, status, tx_hash = self.invoices[key]
1325 pr = PaymentRequest(self.config)
1328 self.payment_request = pr
1329 self.prepare_for_payment_request()
1331 self.payment_request_ok()
1333 self.payment_request_error()
1336 def create_invoice_menu(self, position):
1337 item = self.invoices_list.itemAt(position)
1340 k = self.invoices_list.indexOfTopLevelItem(item)
1341 key = self.invoices.keys()[k]
1342 domain, memo, value, expiration, status, tx_hash = self.invoices[key]
1344 menu.addAction(_("Details"), lambda: self.show_invoice(key))
1345 if status == PR_UNPAID:
1346 menu.addAction(_("Pay Now"), lambda: self.do_pay_invoice(key))
1347 menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
1348 menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
1351 def update_receive_item(self, item):
1352 item.setFont(0, QFont(MONOSPACE_FONT))
1353 address = str(item.data(0,0).toString())
1354 label = self.wallet.labels.get(address,'')
1355 item.setData(1,0,label)
1356 item.setData(0,32, True) # is editable
1358 run_hook('update_receive_item', address, item)
1360 if not self.wallet.is_mine(address): return
1362 c, u = self.wallet.get_addr_balance(address)
1363 balance = self.format_amount(c + u)
1364 item.setData(2,0,balance)
1366 if address in self.wallet.frozen_addresses:
1367 item.setBackgroundColor(0, QColor('lightblue'))
1370 def update_receive_tab(self):
1371 l = self.receive_list
1372 # extend the syntax for consistency
1373 l.addChild = l.addTopLevelItem
1374 l.insertChild = l.insertTopLevelItem
1378 accounts = self.wallet.get_accounts()
1379 if self.current_account is None:
1380 account_items = sorted(accounts.items())
1382 account_items = [(self.current_account, accounts.get(self.current_account))]
1385 for k, account in account_items:
1387 if len(accounts) > 1:
1388 name = self.wallet.get_account_name(k)
1389 c,u = self.wallet.get_account_balance(k)
1390 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1391 l.addTopLevelItem(account_item)
1392 account_item.setExpanded(self.accounts_expanded.get(k, True))
1393 account_item.setData(0, 32, k)
1397 sequences = [0,1] if account.has_change() else [0]
1398 for is_change in sequences:
1399 if len(sequences) > 1:
1400 name = _("Receiving") if not is_change else _("Change")
1401 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1402 account_item.addChild(seq_item)
1404 seq_item.setExpanded(True)
1406 seq_item = account_item
1408 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1414 for address in account.get_addresses(is_change):
1416 num, is_used = self.wallet.is_used(address)
1419 if gap > self.wallet.gap_limit:
1424 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1425 self.update_receive_item(item)
1427 item.setBackgroundColor(1, QColor('red'))
1431 seq_item.insertChild(0,used_item)
1433 used_item.addChild(item)
1435 seq_item.addChild(item)
1437 # we use column 1 because column 0 may be hidden
1438 l.setCurrentItem(l.topLevelItem(0),1)
1441 def update_contacts_tab(self):
1442 l = self.contacts_list
1445 for address in self.wallet.addressbook:
1446 label = self.wallet.labels.get(address,'')
1447 n = self.wallet.get_num_tx(address)
1448 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1449 item.setFont(0, QFont(MONOSPACE_FONT))
1450 # 32 = label can be edited (bool)
1451 item.setData(0,32, True)
1453 item.setData(0,33, address)
1454 l.addTopLevelItem(item)
1456 run_hook('update_contacts_tab', l)
1457 l.setCurrentItem(l.topLevelItem(0))
1461 def create_console_tab(self):
1462 from console import Console
1463 self.console = console = Console()
1467 def update_console(self):
1468 console = self.console
1469 console.history = self.config.get("console-history",[])
1470 console.history_index = len(console.history)
1472 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1473 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1475 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1477 def mkfunc(f, method):
1478 return lambda *args: apply( f, (method, args, self.password_dialog ))
1480 if m[0]=='_' or m in ['network','wallet']: continue
1481 methods[m] = mkfunc(c._run, m)
1483 console.updateNamespace(methods)
1486 def change_account(self,s):
1487 if s == _("All accounts"):
1488 self.current_account = None
1490 accounts = self.wallet.get_account_names()
1491 for k, v in accounts.items():
1493 self.current_account = k
1494 self.update_history_tab()
1495 self.update_status()
1496 self.update_receive_tab()
1498 def create_status_bar(self):
1501 sb.setFixedHeight(35)
1502 qtVersion = qVersion()
1504 self.balance_label = QLabel("")
1505 sb.addWidget(self.balance_label)
1507 from version_getter import UpdateLabel
1508 self.updatelabel = UpdateLabel(self.config, sb)
1510 self.account_selector = QComboBox()
1511 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1512 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1513 sb.addPermanentWidget(self.account_selector)
1515 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1516 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1518 self.lock_icon = QIcon()
1519 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1520 sb.addPermanentWidget( self.password_button )
1522 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1523 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1524 sb.addPermanentWidget( self.seed_button )
1525 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1526 sb.addPermanentWidget( self.status_button )
1528 run_hook('create_status_bar', (sb,))
1530 self.setStatusBar(sb)
1533 def update_lock_icon(self):
1534 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1535 self.password_button.setIcon( icon )
1538 def update_buttons_on_seed(self):
1539 if self.wallet.has_seed():
1540 self.seed_button.show()
1542 self.seed_button.hide()
1544 if not self.wallet.is_watching_only():
1545 self.password_button.show()
1546 self.send_button.setText(_("Send"))
1548 self.password_button.hide()
1549 self.send_button.setText(_("Create unsigned transaction"))
1552 def change_password_dialog(self):
1553 from password_dialog import PasswordDialog
1554 d = PasswordDialog(self.wallet, self)
1556 self.update_lock_icon()
1559 def new_contact_dialog(self):
1562 d.setWindowTitle(_("New Contact"))
1563 vbox = QVBoxLayout(d)
1564 vbox.addWidget(QLabel(_('New Contact')+':'))
1566 grid = QGridLayout()
1569 grid.addWidget(QLabel(_("Address")), 1, 0)
1570 grid.addWidget(line1, 1, 1)
1571 grid.addWidget(QLabel(_("Name")), 2, 0)
1572 grid.addWidget(line2, 2, 1)
1574 vbox.addLayout(grid)
1575 vbox.addLayout(ok_cancel_buttons(d))
1580 address = str(line1.text())
1581 label = unicode(line2.text())
1583 if not is_valid(address):
1584 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1587 self.wallet.add_contact(address)
1589 self.wallet.set_label(address, label)
1591 self.update_contacts_tab()
1592 self.update_history_tab()
1593 self.update_completions()
1594 self.tabs.setCurrentIndex(3)
1598 def new_account_dialog(self, password):
1600 dialog = QDialog(self)
1602 dialog.setWindowTitle(_("New Account"))
1604 vbox = QVBoxLayout()
1605 vbox.addWidget(QLabel(_('Account name')+':'))
1608 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1609 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1614 vbox.addLayout(ok_cancel_buttons(dialog))
1615 dialog.setLayout(vbox)
1619 name = str(e.text())
1622 self.wallet.create_pending_account(name, password)
1623 self.update_receive_tab()
1624 self.tabs.setCurrentIndex(2)
1629 def show_master_public_keys(self):
1631 dialog = QDialog(self)
1633 dialog.setWindowTitle(_("Master Public Keys"))
1635 main_layout = QGridLayout()
1636 mpk_dict = self.wallet.get_master_public_keys()
1638 for key, value in mpk_dict.items():
1639 main_layout.addWidget(QLabel(key), i, 0)
1640 mpk_text = QTextEdit()
1641 mpk_text.setReadOnly(True)
1642 mpk_text.setMaximumHeight(170)
1643 mpk_text.setText(value)
1644 main_layout.addWidget(mpk_text, i + 1, 0)
1647 vbox = QVBoxLayout()
1648 vbox.addLayout(main_layout)
1649 vbox.addLayout(close_button(dialog))
1651 dialog.setLayout(vbox)
1656 def show_seed_dialog(self, password):
1657 if not self.wallet.has_seed():
1658 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1662 mnemonic = self.wallet.get_mnemonic(password)
1664 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1666 from seed_dialog import SeedDialog
1667 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1672 def show_qrcode(self, data, title = _("QR code")):
1675 d = QRDialog(data, self, title)
1679 def do_protect(self, func, args):
1680 if self.wallet.use_encryption:
1681 password = self.password_dialog()
1687 if args != (False,):
1688 args = (self,) + args + (password,)
1690 args = (self,password)
1694 def show_public_keys(self, address):
1695 if not address: return
1697 pubkey_list = self.wallet.get_public_keys(address)
1698 except Exception as e:
1699 traceback.print_exc(file=sys.stdout)
1700 self.show_message(str(e))
1704 d.setMinimumSize(600, 200)
1706 vbox = QVBoxLayout()
1707 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1708 vbox.addWidget( QLabel(_("Public key") + ':'))
1710 keys.setReadOnly(True)
1711 keys.setText('\n'.join(pubkey_list))
1712 vbox.addWidget(keys)
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.addLayout(close_button(d))
1743 def do_sign(self, address, message, signature, password):
1744 message = unicode(message.toPlainText())
1745 message = message.encode('utf-8')
1747 sig = self.wallet.sign_message(str(address.text()), message, password)
1748 signature.setText(sig)
1749 except Exception as e:
1750 self.show_message(str(e))
1752 def do_verify(self, address, message, signature):
1753 message = unicode(message.toPlainText())
1754 message = message.encode('utf-8')
1755 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1756 self.show_message(_("Signature verified"))
1758 self.show_message(_("Error: wrong signature"))
1761 def sign_verify_message(self, address=''):
1764 d.setWindowTitle(_('Sign/verify Message'))
1765 d.setMinimumSize(410, 290)
1767 layout = QGridLayout(d)
1769 message_e = QTextEdit()
1770 layout.addWidget(QLabel(_('Message')), 1, 0)
1771 layout.addWidget(message_e, 1, 1)
1772 layout.setRowStretch(2,3)
1774 address_e = QLineEdit()
1775 address_e.setText(address)
1776 layout.addWidget(QLabel(_('Address')), 2, 0)
1777 layout.addWidget(address_e, 2, 1)
1779 signature_e = QTextEdit()
1780 layout.addWidget(QLabel(_('Signature')), 3, 0)
1781 layout.addWidget(signature_e, 3, 1)
1782 layout.setRowStretch(3,1)
1784 hbox = QHBoxLayout()
1786 b = QPushButton(_("Sign"))
1787 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1790 b = QPushButton(_("Verify"))
1791 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1794 b = QPushButton(_("Close"))
1795 b.clicked.connect(d.accept)
1797 layout.addLayout(hbox, 4, 1)
1802 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1804 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1805 message_e.setText(decrypted)
1806 except Exception as e:
1807 self.show_message(str(e))
1810 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1811 message = unicode(message_e.toPlainText())
1812 message = message.encode('utf-8')
1814 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1815 encrypted_e.setText(encrypted)
1816 except Exception as e:
1817 self.show_message(str(e))
1821 def encrypt_message(self, address = ''):
1824 d.setWindowTitle(_('Encrypt/decrypt Message'))
1825 d.setMinimumSize(610, 490)
1827 layout = QGridLayout(d)
1829 message_e = QTextEdit()
1830 layout.addWidget(QLabel(_('Message')), 1, 0)
1831 layout.addWidget(message_e, 1, 1)
1832 layout.setRowStretch(2,3)
1834 pubkey_e = QLineEdit()
1836 pubkey = self.wallet.getpubkeys(address)[0]
1837 pubkey_e.setText(pubkey)
1838 layout.addWidget(QLabel(_('Public key')), 2, 0)
1839 layout.addWidget(pubkey_e, 2, 1)
1841 encrypted_e = QTextEdit()
1842 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1843 layout.addWidget(encrypted_e, 3, 1)
1844 layout.setRowStretch(3,1)
1846 hbox = QHBoxLayout()
1847 b = QPushButton(_("Encrypt"))
1848 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1851 b = QPushButton(_("Decrypt"))
1852 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1855 b = QPushButton(_("Close"))
1856 b.clicked.connect(d.accept)
1859 layout.addLayout(hbox, 4, 1)
1863 def question(self, msg):
1864 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1866 def show_message(self, msg):
1867 QMessageBox.information(self, _('Message'), msg, _('OK'))
1869 def password_dialog(self, msg=None):
1872 d.setWindowTitle(_("Enter Password"))
1877 vbox = QVBoxLayout()
1879 msg = _('Please enter your password')
1880 vbox.addWidget(QLabel(msg))
1882 grid = QGridLayout()
1884 grid.addWidget(QLabel(_('Password')), 1, 0)
1885 grid.addWidget(pw, 1, 1)
1886 vbox.addLayout(grid)
1888 vbox.addLayout(ok_cancel_buttons(d))
1891 run_hook('password_dialog', pw, grid, 1)
1892 if not d.exec_(): return
1893 return unicode(pw.text())
1902 def tx_from_text(self, txt):
1903 "json or raw hexadecimal"
1906 tx = Transaction(txt)
1912 tx_dict = json.loads(str(txt))
1913 assert "hex" in tx_dict.keys()
1914 tx = Transaction(tx_dict["hex"])
1915 if tx_dict.has_key("input_info"):
1916 input_info = json.loads(tx_dict['input_info'])
1917 tx.add_input_info(input_info)
1920 traceback.print_exc(file=sys.stdout)
1923 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1927 def read_tx_from_file(self):
1928 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1932 with open(fileName, "r") as f:
1933 file_content = f.read()
1934 except (ValueError, IOError, os.error), reason:
1935 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1937 return self.tx_from_text(file_content)
1941 def sign_raw_transaction(self, tx, input_info, password):
1942 self.wallet.signrawtransaction(tx, input_info, [], password)
1944 def do_process_from_text(self):
1945 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1948 tx = self.tx_from_text(text)
1950 self.show_transaction(tx)
1952 def do_process_from_file(self):
1953 tx = self.read_tx_from_file()
1955 self.show_transaction(tx)
1957 def do_process_from_txid(self):
1958 from electrum import transaction
1959 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1961 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1963 tx = transaction.Transaction(r)
1965 self.show_transaction(tx)
1967 self.show_message("unknown transaction")
1969 def do_process_from_csvReader(self, csvReader):
1974 for position, row in enumerate(csvReader):
1976 if not is_valid(address):
1977 errors.append((position, address))
1979 amount = Decimal(row[1])
1980 amount = int(100000000*amount)
1981 outputs.append((address, amount))
1982 except (ValueError, IOError, os.error), reason:
1983 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1987 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1988 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1992 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1993 except Exception as e:
1994 self.show_message(str(e))
1997 self.show_transaction(tx)
1999 def do_process_from_csv_file(self):
2000 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
2004 with open(fileName, "r") as f:
2005 csvReader = csv.reader(f)
2006 self.do_process_from_csvReader(csvReader)
2007 except (ValueError, IOError, os.error), reason:
2008 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2011 def do_process_from_csv_text(self):
2012 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
2013 + _("Format: address, amount. One output per line"), _("Load CSV"))
2016 f = StringIO.StringIO(text)
2017 csvReader = csv.reader(f)
2018 self.do_process_from_csvReader(csvReader)
2023 def export_privkeys_dialog(self, password):
2024 if self.wallet.is_watching_only():
2025 self.show_message(_("This is a watching-only wallet"))
2029 d.setWindowTitle(_('Private keys'))
2030 d.setMinimumSize(850, 300)
2031 vbox = QVBoxLayout(d)
2033 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
2034 _("Exposing a single private key can compromise your entire wallet!"),
2035 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
2036 vbox.addWidget(QLabel(msg))
2042 defaultname = 'electrum-private-keys.csv'
2043 select_msg = _('Select file to export your private keys to')
2044 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2045 vbox.addLayout(hbox)
2047 h, b = ok_cancel_buttons2(d, _('Export'))
2052 addresses = self.wallet.addresses(True)
2054 def privkeys_thread():
2055 for addr in addresses:
2059 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
2060 d.emit(SIGNAL('computing_privkeys'))
2061 d.emit(SIGNAL('show_privkeys'))
2063 def show_privkeys():
2064 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
2068 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
2069 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
2070 threading.Thread(target=privkeys_thread).start()
2076 filename = filename_e.text()
2081 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
2082 except (IOError, os.error), reason:
2083 export_error_label = _("Electrum was unable to produce a private key-export.")
2084 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2086 except Exception as e:
2087 self.show_message(str(e))
2090 self.show_message(_("Private keys exported."))
2093 def do_export_privkeys(self, fileName, pklist, is_csv):
2094 with open(fileName, "w+") as f:
2096 transaction = csv.writer(f)
2097 transaction.writerow(["address", "private_key"])
2098 for addr, pk in pklist.items():
2099 transaction.writerow(["%34s"%addr,pk])
2102 f.write(json.dumps(pklist, indent = 4))
2105 def do_import_labels(self):
2106 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2107 if not labelsFile: return
2109 f = open(labelsFile, 'r')
2112 for key, value in json.loads(data).items():
2113 self.wallet.set_label(key, value)
2114 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2115 except (IOError, os.error), reason:
2116 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2119 def do_export_labels(self):
2120 labels = self.wallet.labels
2122 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2124 with open(fileName, 'w+') as f:
2125 json.dump(labels, f)
2126 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2127 except (IOError, os.error), reason:
2128 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2131 def export_history_dialog(self):
2134 d.setWindowTitle(_('Export History'))
2135 d.setMinimumSize(400, 200)
2136 vbox = QVBoxLayout(d)
2138 defaultname = os.path.expanduser('~/electrum-history.csv')
2139 select_msg = _('Select file to export your wallet transactions to')
2141 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2142 vbox.addLayout(hbox)
2146 h, b = ok_cancel_buttons2(d, _('Export'))
2151 filename = filename_e.text()
2156 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2157 except (IOError, os.error), reason:
2158 export_error_label = _("Electrum was unable to produce a transaction export.")
2159 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2162 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2165 def do_export_history(self, wallet, fileName, is_csv):
2166 history = wallet.get_tx_history()
2168 for item in history:
2169 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2171 if timestamp is not None:
2173 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2174 except [RuntimeError, TypeError, NameError] as reason:
2175 time_string = "unknown"
2178 time_string = "unknown"
2180 time_string = "pending"
2182 if value is not None:
2183 value_string = format_satoshis(value, True)
2188 fee_string = format_satoshis(fee, True)
2193 label, is_default_label = wallet.get_label(tx_hash)
2194 label = label.encode('utf-8')
2198 balance_string = format_satoshis(balance, False)
2200 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2202 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2204 with open(fileName, "w+") as f:
2206 transaction = csv.writer(f)
2207 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2209 transaction.writerow(line)
2212 f.write(json.dumps(lines, indent = 4))
2215 def sweep_key_dialog(self):
2217 d.setWindowTitle(_('Sweep private keys'))
2218 d.setMinimumSize(600, 300)
2220 vbox = QVBoxLayout(d)
2221 vbox.addWidget(QLabel(_("Enter private keys")))
2223 keys_e = QTextEdit()
2224 keys_e.setTabChangesFocus(True)
2225 vbox.addWidget(keys_e)
2227 h, address_e = address_field(self.wallet.addresses())
2231 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2232 vbox.addLayout(hbox)
2233 button.setEnabled(False)
2236 addr = str(address_e.text())
2237 if bitcoin.is_address(addr):
2241 pk = str(keys_e.toPlainText()).strip()
2242 if Wallet.is_private_key(pk):
2245 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2246 keys_e.textChanged.connect(f)
2247 address_e.textChanged.connect(f)
2251 fee = self.wallet.fee
2252 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2253 self.show_transaction(tx)
2257 def do_import_privkey(self, password):
2258 if not self.wallet.has_imported_keys():
2259 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2260 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2261 + _('Are you sure you understand what you are doing?'), 3, 4)
2264 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2267 text = str(text).split()
2272 addr = self.wallet.import_key(key, password)
2273 except Exception as e:
2279 addrlist.append(addr)
2281 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2283 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2284 self.update_receive_tab()
2285 self.update_history_tab()
2288 def settings_dialog(self):
2290 d.setWindowTitle(_('Electrum Settings'))
2292 vbox = QVBoxLayout()
2293 grid = QGridLayout()
2294 grid.setColumnStretch(0,1)
2296 nz_label = QLabel(_('Display zeros') + ':')
2297 grid.addWidget(nz_label, 0, 0)
2298 nz_e = AmountEdit(None,True)
2299 nz_e.setText("%d"% self.num_zeros)
2300 grid.addWidget(nz_e, 0, 1)
2301 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2302 grid.addWidget(HelpButton(msg), 0, 2)
2303 if not self.config.is_modifiable('num_zeros'):
2304 for w in [nz_e, nz_label]: w.setEnabled(False)
2306 lang_label=QLabel(_('Language') + ':')
2307 grid.addWidget(lang_label, 1, 0)
2308 lang_combo = QComboBox()
2309 from electrum.i18n import languages
2310 lang_combo.addItems(languages.values())
2312 index = languages.keys().index(self.config.get("language",''))
2315 lang_combo.setCurrentIndex(index)
2316 grid.addWidget(lang_combo, 1, 1)
2317 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2318 if not self.config.is_modifiable('language'):
2319 for w in [lang_combo, lang_label]: w.setEnabled(False)
2322 fee_label = QLabel(_('Transaction fee') + ':')
2323 grid.addWidget(fee_label, 2, 0)
2324 fee_e = BTCAmountEdit(self.get_decimal_point)
2325 fee_e.setAmount(self.wallet.fee)
2326 grid.addWidget(fee_e, 2, 1)
2327 msg = _('Fee per kilobyte of transaction.') + '\n' \
2328 + _('Recommended value') + ': ' + self.format_amount(10000) + ' ' + self.base_unit()
2329 grid.addWidget(HelpButton(msg), 2, 2)
2330 if not self.config.is_modifiable('fee_per_kb'):
2331 for w in [fee_e, fee_label]: w.setEnabled(False)
2333 units = ['BTC', 'mBTC']
2334 unit_label = QLabel(_('Base unit') + ':')
2335 grid.addWidget(unit_label, 3, 0)
2336 unit_combo = QComboBox()
2337 unit_combo.addItems(units)
2338 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2339 grid.addWidget(unit_combo, 3, 1)
2340 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2341 + '\n1BTC=1000mBTC.\n' \
2342 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2344 usechange_cb = QCheckBox(_('Use change addresses'))
2345 usechange_cb.setChecked(self.wallet.use_change)
2346 grid.addWidget(usechange_cb, 4, 0)
2347 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2348 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2350 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2351 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2352 grid.addWidget(block_ex_label, 5, 0)
2353 block_ex_combo = QComboBox()
2354 block_ex_combo.addItems(block_explorers)
2355 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2356 grid.addWidget(block_ex_combo, 5, 1)
2357 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2359 show_tx = self.config.get('show_before_broadcast', False)
2360 showtx_cb = QCheckBox(_('Show before broadcast'))
2361 showtx_cb.setChecked(show_tx)
2362 grid.addWidget(showtx_cb, 6, 0)
2363 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2365 vbox.addLayout(grid)
2367 vbox.addLayout(ok_cancel_buttons(d))
2371 if not d.exec_(): return
2373 fee = fee_e.get_amount()
2375 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2378 self.wallet.set_fee(fee)
2380 nz = unicode(nz_e.text())
2385 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2388 if self.num_zeros != nz:
2390 self.config.set_key('num_zeros', nz, True)
2391 self.update_history_tab()
2392 self.update_receive_tab()
2394 usechange_result = usechange_cb.isChecked()
2395 if self.wallet.use_change != usechange_result:
2396 self.wallet.use_change = usechange_result
2397 self.wallet.storage.put('use_change', self.wallet.use_change)
2399 if showtx_cb.isChecked() != show_tx:
2400 self.config.set_key('show_before_broadcast', not show_tx)
2402 unit_result = units[unit_combo.currentIndex()]
2403 if self.base_unit() != unit_result:
2404 self.decimal_point = 8 if unit_result == 'BTC' else 5
2405 self.config.set_key('decimal_point', self.decimal_point, True)
2406 self.update_history_tab()
2407 self.update_status()
2409 need_restart = False
2411 lang_request = languages.keys()[lang_combo.currentIndex()]
2412 if lang_request != self.config.get('language'):
2413 self.config.set_key("language", lang_request, True)
2416 be_result = block_explorers[block_ex_combo.currentIndex()]
2417 self.config.set_key('block_explorer', be_result, True)
2419 run_hook('close_settings_dialog')
2422 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2425 def run_network_dialog(self):
2426 if not self.network:
2428 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2430 def closeEvent(self, event):
2432 self.config.set_key("is_maximized", self.isMaximized())
2433 if not self.isMaximized():
2435 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2436 self.save_column_widths()
2437 self.config.set_key("console-history", self.console.history[-50:], True)
2438 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2442 def plugins_dialog(self):
2443 from electrum.plugins import plugins
2446 d.setWindowTitle(_('Electrum Plugins'))
2449 vbox = QVBoxLayout(d)
2452 scroll = QScrollArea()
2453 scroll.setEnabled(True)
2454 scroll.setWidgetResizable(True)
2455 scroll.setMinimumSize(400,250)
2456 vbox.addWidget(scroll)
2460 w.setMinimumHeight(len(plugins)*35)
2462 grid = QGridLayout()
2463 grid.setColumnStretch(0,1)
2466 def do_toggle(cb, p, w):
2469 if w: w.setEnabled(r)
2471 def mk_toggle(cb, p, w):
2472 return lambda: do_toggle(cb,p,w)
2474 for i, p in enumerate(plugins):
2476 cb = QCheckBox(p.fullname())
2477 cb.setDisabled(not p.is_available())
2478 cb.setChecked(p.is_enabled())
2479 grid.addWidget(cb, i, 0)
2480 if p.requires_settings():
2481 w = p.settings_widget(self)
2482 w.setEnabled( p.is_enabled() )
2483 grid.addWidget(w, i, 1)
2486 cb.clicked.connect(mk_toggle(cb,p,w))
2487 grid.addWidget(HelpButton(p.description()), i, 2)
2489 print_msg(_("Error: cannot display plugin"), p)
2490 traceback.print_exc(file=sys.stdout)
2491 grid.setRowStretch(i+1,1)
2493 vbox.addLayout(close_button(d))
2498 def show_account_details(self, k):
2499 account = self.wallet.accounts[k]
2502 d.setWindowTitle(_('Account Details'))
2505 vbox = QVBoxLayout(d)
2506 name = self.wallet.get_account_name(k)
2507 label = QLabel('Name: ' + name)
2508 vbox.addWidget(label)
2510 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2512 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2514 vbox.addWidget(QLabel(_('Master Public Key:')))
2517 text.setReadOnly(True)
2518 text.setMaximumHeight(170)
2519 vbox.addWidget(text)
2521 mpk_text = '\n'.join( account.get_master_pubkeys() )
2522 text.setText(mpk_text)
2524 vbox.addLayout(close_button(d))