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
45 from electrum import bmp, pyqrnative
47 from amountedit import AmountEdit
48 from network_dialog import NetworkDialog
49 from qrcodewidget import QRCodeWidget
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'
66 from electrum import ELECTRUM_VERSION
76 class StatusBarButton(QPushButton):
77 def __init__(self, icon, tooltip, func):
78 QPushButton.__init__(self, icon, '')
79 self.setToolTip(tooltip)
81 self.setMaximumWidth(25)
82 self.clicked.connect(func)
84 self.setIconSize(QSize(25,25))
86 def keyPressEvent(self, e):
87 if e.key() == QtCore.Qt.Key_Return:
99 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
101 class ElectrumWindow(QMainWindow):
105 def __init__(self, config, network, gui_object):
106 QMainWindow.__init__(self)
109 self.network = network
110 self.tray = gui_object.tray
111 self.go_lite = gui_object.go_lite
114 self.create_status_bar()
115 self.need_update = threading.Event()
117 self.decimal_point = config.get('decimal_point', 5)
118 self.num_zeros = int(config.get('num_zeros',0))
120 set_language(config.get('language'))
122 self.funds_error = False
123 self.completions = QStringListModel()
125 self.tabs = tabs = QTabWidget(self)
126 self.column_widths = self.config.get("column_widths_2", default_column_widths )
127 tabs.addTab(self.create_history_tab(), _('History') )
128 tabs.addTab(self.create_send_tab(), _('Send') )
129 tabs.addTab(self.create_receive_tab(), _('Receive') )
130 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
131 tabs.addTab(self.create_console_tab(), _('Console') )
132 tabs.setMinimumSize(600, 400)
133 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
134 self.setCentralWidget(tabs)
136 g = self.config.get("winpos-qt",[100, 100, 840, 400])
137 self.setGeometry(g[0], g[1], g[2], g[3])
139 self.setWindowIcon(QIcon(":icons/electrum.png"))
142 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
143 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
144 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
145 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
146 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
148 for i in range(tabs.count()):
149 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
151 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
152 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
153 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
154 self.connect(self, QtCore.SIGNAL('send_tx2'), self.send_tx2)
155 self.connect(self, QtCore.SIGNAL('send_tx3'), self.send_tx3)
157 self.history_list.setFocus(True)
161 self.network.register_callback('updated', lambda: self.need_update.set())
162 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
163 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
164 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
165 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
167 # set initial message
168 self.console.showMessage(self.network.banner)
173 def update_account_selector(self):
175 accounts = self.wallet.get_account_names()
176 self.account_selector.clear()
177 if len(accounts) > 1:
178 self.account_selector.addItems([_("All accounts")] + accounts.values())
179 self.account_selector.setCurrentIndex(0)
180 self.account_selector.show()
182 self.account_selector.hide()
185 def load_wallet(self, wallet):
188 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
189 self.current_account = self.wallet.storage.get("current_account", None)
191 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
192 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
193 self.setWindowTitle( title )
195 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
196 self.notify_transactions()
197 self.update_account_selector()
198 self.new_account.setEnabled(self.wallet.can_create_accounts())
199 self.update_lock_icon()
200 self.update_buttons_on_seed()
201 self.update_console()
203 run_hook('load_wallet', wallet)
206 def open_wallet(self):
207 wallet_folder = self.wallet.storage.path
208 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
212 storage = WalletStorage({'wallet_path': filename})
213 if not storage.file_exists:
214 self.show_message("file not found "+ filename)
217 self.wallet.stop_threads()
220 wallet = Wallet(storage)
221 wallet.start_threads(self.network)
223 self.load_wallet(wallet)
227 def backup_wallet(self):
229 path = self.wallet.storage.path
230 wallet_folder = os.path.dirname(path)
231 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
235 new_path = os.path.join(wallet_folder, filename)
238 shutil.copy2(path, new_path)
239 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
240 except (IOError, os.error), reason:
241 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
244 def new_wallet(self):
247 wallet_folder = os.path.dirname(self.wallet.storage.path)
248 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
251 filename = os.path.join(wallet_folder, filename)
253 storage = WalletStorage({'wallet_path': filename})
254 if storage.file_exists:
255 QMessageBox.critical(None, "Error", _("File exists"))
258 wizard = installwizard.InstallWizard(self.config, self.network, storage)
259 wallet = wizard.run()
261 self.load_wallet(wallet)
265 def init_menubar(self):
268 file_menu = menubar.addMenu(_("&File"))
269 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
270 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
271 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
272 file_menu.addAction(_("&Quit"), self.close)
274 wallet_menu = menubar.addMenu(_("&Wallet"))
275 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
276 self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
278 wallet_menu.addSeparator()
280 wallet_menu.addAction(_("&Password"), self.change_password_dialog)
281 wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
282 wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
284 wallet_menu.addSeparator()
285 labels_menu = wallet_menu.addMenu(_("&Labels"))
286 labels_menu.addAction(_("&Import"), self.do_import_labels)
287 labels_menu.addAction(_("&Export"), self.do_export_labels)
289 keys_menu = wallet_menu.addMenu(_("&Private keys"))
290 keys_menu.addAction(_("&Import"), self.do_import_privkey)
291 keys_menu.addAction(_("&Export"), self.do_export_privkeys)
293 wallet_menu.addAction(_("&Export History"), self.do_export_history)
295 tools_menu = menubar.addMenu(_("&Tools"))
297 # Settings / Preferences are all reserved keywords in OSX using this as work around
298 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
299 tools_menu.addAction(_("&Network"), self.run_network_dialog)
300 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
301 tools_menu.addSeparator()
302 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
303 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
304 tools_menu.addSeparator()
306 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
307 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
308 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
310 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
311 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
312 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
313 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
315 help_menu = menubar.addMenu(_("&Help"))
316 help_menu.addAction(_("&About"), self.show_about)
317 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
318 help_menu.addSeparator()
319 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
320 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
322 self.setMenuBar(menubar)
324 def show_about(self):
325 QMessageBox.about(self, "Electrum",
326 _("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."))
328 def show_report_bug(self):
329 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
330 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
333 def notify_transactions(self):
334 if not self.network or not self.network.is_connected():
337 print_error("Notifying GUI")
338 if len(self.network.pending_transactions_for_notifications) > 0:
339 # Combine the transactions if there are more then three
340 tx_amount = len(self.network.pending_transactions_for_notifications)
343 for tx in self.network.pending_transactions_for_notifications:
344 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
348 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
349 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
351 self.network.pending_transactions_for_notifications = []
353 for tx in self.network.pending_transactions_for_notifications:
355 self.network.pending_transactions_for_notifications.remove(tx)
356 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
358 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
360 def notify(self, message):
361 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
365 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
366 def getOpenFileName(self, title, filter = ""):
367 directory = self.config.get('io_dir', os.path.expanduser('~'))
368 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
369 if fileName and directory != os.path.dirname(fileName):
370 self.config.set_key('io_dir', os.path.dirname(fileName), True)
373 def getSaveFileName(self, title, filename, filter = ""):
374 directory = self.config.get('io_dir', os.path.expanduser('~'))
375 path = os.path.join( directory, filename )
376 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
377 if fileName and directory != os.path.dirname(fileName):
378 self.config.set_key('io_dir', os.path.dirname(fileName), True)
382 QMainWindow.close(self)
383 run_hook('close_main_window')
385 def connect_slots(self, sender):
386 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
387 self.previous_payto_e=''
389 def timer_actions(self):
390 if self.need_update.is_set():
392 self.need_update.clear()
393 run_hook('timer_actions')
395 def format_amount(self, x, is_diff=False, whitespaces=False):
396 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
398 def read_amount(self, x):
399 if x in['.', '']: return None
400 p = pow(10, self.decimal_point)
401 return int( p * Decimal(x) )
404 assert self.decimal_point in [5,8]
405 return "BTC" if self.decimal_point == 8 else "mBTC"
408 def update_status(self):
409 if self.network is None or not self.network.is_running():
411 icon = QIcon(":icons/status_disconnected.png")
413 elif self.network.is_connected():
414 if not self.wallet.up_to_date:
415 text = _("Synchronizing...")
416 icon = QIcon(":icons/status_waiting.png")
417 elif self.network.server_lag > 1:
418 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
419 icon = QIcon(":icons/status_lagging.png")
421 c, u = self.wallet.get_account_balance(self.current_account)
422 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
423 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
425 # append fiat balance and price from exchange rate plugin
427 run_hook('get_fiat_status_text', c+u, r)
432 self.tray.setToolTip(text)
433 icon = QIcon(":icons/status_connected.png")
435 text = _("Not connected")
436 icon = QIcon(":icons/status_disconnected.png")
438 self.balance_label.setText(text)
439 self.status_button.setIcon( icon )
442 def update_wallet(self):
444 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
445 self.update_history_tab()
446 self.update_receive_tab()
447 self.update_contacts_tab()
448 self.update_completions()
451 def create_history_tab(self):
452 self.history_list = l = MyTreeWidget(self)
454 for i,width in enumerate(self.column_widths['history']):
455 l.setColumnWidth(i, width)
456 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
457 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
458 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
460 l.customContextMenuRequested.connect(self.create_history_menu)
464 def create_history_menu(self, position):
465 self.history_list.selectedIndexes()
466 item = self.history_list.currentItem()
467 be = self.config.get('block_explorer', 'Blockchain.info')
468 if be == 'Blockchain.info':
469 block_explorer = 'https://blockchain.info/tx/'
470 elif be == 'Blockr.io':
471 block_explorer = 'https://blockr.io/tx/info/'
472 elif be == 'Insight.is':
473 block_explorer = 'http://live.insight.is/tx/'
475 tx_hash = str(item.data(0, Qt.UserRole).toString())
476 if not tx_hash: return
478 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
479 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
480 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
481 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
482 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
485 def show_transaction(self, tx):
486 import transaction_dialog
487 d = transaction_dialog.TxDialog(tx, self)
490 def tx_label_clicked(self, item, column):
491 if column==2 and item.isSelected():
493 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
494 self.history_list.editItem( item, column )
495 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
498 def tx_label_changed(self, item, column):
502 tx_hash = str(item.data(0, Qt.UserRole).toString())
503 tx = self.wallet.transactions.get(tx_hash)
504 text = unicode( item.text(2) )
505 self.wallet.set_label(tx_hash, text)
507 item.setForeground(2, QBrush(QColor('black')))
509 text = self.wallet.get_default_label(tx_hash)
510 item.setText(2, text)
511 item.setForeground(2, QBrush(QColor('gray')))
515 def edit_label(self, is_recv):
516 l = self.receive_list if is_recv else self.contacts_list
517 item = l.currentItem()
518 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
519 l.editItem( item, 1 )
520 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
524 def address_label_clicked(self, item, column, l, column_addr, column_label):
525 if column == column_label and item.isSelected():
526 is_editable = item.data(0, 32).toBool()
529 addr = unicode( item.text(column_addr) )
530 label = unicode( item.text(column_label) )
531 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
532 l.editItem( item, column )
533 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
536 def address_label_changed(self, item, column, l, column_addr, column_label):
537 if column == column_label:
538 addr = unicode( item.text(column_addr) )
539 text = unicode( item.text(column_label) )
540 is_editable = item.data(0, 32).toBool()
544 changed = self.wallet.set_label(addr, text)
546 self.update_history_tab()
547 self.update_completions()
549 self.current_item_changed(item)
551 run_hook('item_changed', item, column)
554 def current_item_changed(self, a):
555 run_hook('current_item_changed', a)
559 def update_history_tab(self):
561 self.history_list.clear()
562 for item in self.wallet.get_tx_history(self.current_account):
563 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
564 time_str = _("unknown")
567 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
569 time_str = _("error")
572 time_str = 'unverified'
573 icon = QIcon(":icons/unconfirmed.png")
576 icon = QIcon(":icons/unconfirmed.png")
578 icon = QIcon(":icons/clock%d.png"%conf)
580 icon = QIcon(":icons/confirmed.png")
582 if value is not None:
583 v_str = self.format_amount(value, True, whitespaces=True)
587 balance_str = self.format_amount(balance, whitespaces=True)
590 label, is_default_label = self.wallet.get_label(tx_hash)
592 label = _('Pruned transaction outputs')
593 is_default_label = False
595 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
596 item.setFont(2, QFont(MONOSPACE_FONT))
597 item.setFont(3, QFont(MONOSPACE_FONT))
598 item.setFont(4, QFont(MONOSPACE_FONT))
600 item.setForeground(3, QBrush(QColor("#BC1E1E")))
602 item.setData(0, Qt.UserRole, tx_hash)
603 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
605 item.setForeground(2, QBrush(QColor('grey')))
607 item.setIcon(0, icon)
608 self.history_list.insertTopLevelItem(0,item)
611 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
612 run_hook('history_tab_update')
615 def create_send_tab(self):
620 grid.setColumnMinimumWidth(3,300)
621 grid.setColumnStretch(5,1)
624 self.payto_e = QLineEdit()
625 grid.addWidget(QLabel(_('Pay to')), 1, 0)
626 grid.addWidget(self.payto_e, 1, 1, 1, 3)
628 grid.addWidget(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)')), 1, 4)
630 completer = QCompleter()
631 completer.setCaseSensitivity(False)
632 self.payto_e.setCompleter(completer)
633 completer.setModel(self.completions)
635 self.message_e = QLineEdit()
636 grid.addWidget(QLabel(_('Description')), 2, 0)
637 grid.addWidget(self.message_e, 2, 1, 1, 3)
638 grid.addWidget(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.')), 2, 4)
640 self.from_label = QLabel(_('From'))
641 grid.addWidget(self.from_label, 3, 0)
642 self.from_list = QTreeWidget(self)
643 self.from_list.setColumnCount(2)
644 self.from_list.setColumnWidth(0, 350)
645 self.from_list.setColumnWidth(1, 50)
646 self.from_list.setHeaderHidden (True)
647 self.from_list.setMaximumHeight(80)
648 grid.addWidget(self.from_list, 3, 1, 1, 3)
649 self.set_pay_from([])
651 self.amount_e = AmountEdit(self.base_unit)
652 grid.addWidget(QLabel(_('Amount')), 4, 0)
653 grid.addWidget(self.amount_e, 4, 1, 1, 2)
654 grid.addWidget(HelpButton(
655 _('Amount to be sent.') + '\n\n' \
656 + _('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.') \
657 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
659 self.fee_e = AmountEdit(self.base_unit)
660 grid.addWidget(QLabel(_('Fee')), 5, 0)
661 grid.addWidget(self.fee_e, 5, 1, 1, 2)
662 grid.addWidget(HelpButton(
663 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
664 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
665 + _('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)
667 run_hook('exchange_rate_button', grid)
669 self.send_button = EnterButton(_("Send"), self.do_send)
670 grid.addWidget(self.send_button, 6, 1)
672 b = EnterButton(_("Clear"),self.do_clear)
673 grid.addWidget(b, 6, 2)
675 self.payto_sig = QLabel('')
676 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
678 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
679 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
688 def entry_changed( is_fee ):
689 self.funds_error = False
691 if self.amount_e.is_shortcut:
692 self.amount_e.is_shortcut = False
693 sendable = self.get_sendable_balance()
694 # there is only one output because we are completely spending inputs
695 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
696 fee = self.wallet.estimated_fee(inputs, 1)
698 self.amount_e.setText( self.format_amount(amount) )
699 self.fee_e.setText( self.format_amount( fee ) )
702 amount = self.read_amount(str(self.amount_e.text()))
703 fee = self.read_amount(str(self.fee_e.text()))
705 if not is_fee: fee = None
708 # assume that there will be 2 outputs (one for change)
709 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
711 self.fee_e.setText( self.format_amount( fee ) )
714 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
718 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
719 self.funds_error = True
720 text = _( "Not enough funds" )
721 c, u = self.wallet.get_frozen_balance()
722 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
724 self.statusBar().showMessage(text)
725 self.amount_e.setPalette(palette)
726 self.fee_e.setPalette(palette)
728 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
729 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
731 run_hook('create_send_tab', grid)
735 def set_pay_from(self, l):
737 self.from_list.clear()
738 self.from_label.setHidden(len(self.pay_from) == 0)
739 self.from_list.setHidden(len(self.pay_from) == 0)
740 for addr in self.pay_from:
741 c, u = self.wallet.get_addr_balance(addr)
742 balance = self.format_amount(c + u)
743 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
746 def update_completions(self):
748 for addr,label in self.wallet.labels.items():
749 if addr in self.wallet.addressbook:
750 l.append( label + ' <' + addr + '>')
752 run_hook('update_completions', l)
753 self.completions.setStringList(l)
757 return lambda s, *args: s.do_protect(func, args)
762 label = unicode( self.message_e.text() )
763 r = unicode( self.payto_e.text() )
766 # label or alias, with address in brackets
767 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
768 to_address = m.group(2) if m else r
770 if not is_valid(to_address):
771 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
775 amount = self.read_amount(unicode( self.amount_e.text()))
777 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
780 fee = self.read_amount(unicode( self.fee_e.text()))
782 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
785 confirm_amount = self.config.get('confirm_amount', 100000000)
786 if amount >= confirm_amount:
787 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
790 confirm_fee = self.config.get('confirm_fee', 100000)
791 if fee >= confirm_fee:
792 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()}):
795 self.send_tx(to_address, amount, fee, label)
798 def waiting_dialog(self, message):
800 d.setWindowTitle('Please wait')
802 vbox = QVBoxLayout(d)
809 def send_tx(self, to_address, amount, fee, label, password):
811 # first, create an unsigned tx
812 domain = self.get_payment_sources()
813 outputs = [(to_address, amount)]
815 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
817 except Exception as e:
818 traceback.print_exc(file=sys.stdout)
819 self.show_message(str(e))
822 # call hook to see if plugin needs gui interaction
823 run_hook('send_tx', tx)
829 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
830 self.wallet.sign_transaction(tx, keypairs, password)
831 self.signed_tx_data = (tx, fee, label)
832 self.emit(SIGNAL('send_tx2'))
833 self.tx_wait_dialog = self.waiting_dialog('Signing..')
834 threading.Thread(target=sign_thread).start()
836 # add recipient to addressbook
837 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
838 self.wallet.addressbook.append(to_address)
842 tx, fee, label = self.signed_tx_data
843 self.tx_wait_dialog.accept()
846 self.show_message(tx.error)
849 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
850 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
854 self.wallet.set_label(tx.hash(), label)
856 if not tx.is_complete() or self.config.get('show_before_broadcast'):
857 self.show_transaction(tx)
861 def broadcast_thread():
862 self.tx_broadcast_result = self.wallet.sendtx(tx)
863 self.emit(SIGNAL('send_tx3'))
864 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
865 threading.Thread(target=broadcast_thread).start()
869 self.tx_broadcast_dialog.accept()
870 status, msg = self.tx_broadcast_result
872 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
874 self.update_contacts_tab()
876 QMessageBox.warning(self, _('Error'), msg, _('OK'))
883 def set_url(self, url):
885 address, amount, label, message, signature, identity, url = util.parse_url(url)
887 QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URL'), _('OK'))
891 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
892 elif amount: amount = str(Decimal(amount))
895 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
898 self.mini.set_payment_fields(address, amount)
900 if label and self.wallet.labels.get(address) != label:
901 if self.question('Give label "%s" to address %s ?'%(label,address)):
902 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
903 self.wallet.addressbook.append(address)
904 self.wallet.set_label(address, label)
906 run_hook('set_url', url, self.show_message, self.question)
908 self.tabs.setCurrentIndex(1)
909 label = self.wallet.labels.get(address)
910 m_addr = label + ' <'+ address +'>' if label else address
911 self.payto_e.setText(m_addr)
913 self.message_e.setText(message)
915 self.amount_e.setText(amount)
918 self.set_frozen(self.payto_e,True)
919 self.set_frozen(self.amount_e,True)
920 self.set_frozen(self.message_e,True)
921 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
923 self.payto_sig.setVisible(False)
926 self.payto_sig.setVisible(False)
927 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
929 self.set_frozen(e,False)
931 self.set_pay_from([])
934 def set_frozen(self,entry,frozen):
936 entry.setReadOnly(True)
937 entry.setFrame(False)
939 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
940 entry.setPalette(palette)
942 entry.setReadOnly(False)
945 palette.setColor(entry.backgroundRole(), QColor('white'))
946 entry.setPalette(palette)
949 def set_addrs_frozen(self,addrs,freeze):
951 if not addr: continue
952 if addr in self.wallet.frozen_addresses and not freeze:
953 self.wallet.unfreeze(addr)
954 elif addr not in self.wallet.frozen_addresses and freeze:
955 self.wallet.freeze(addr)
956 self.update_receive_tab()
960 def create_list_tab(self, headers):
961 "generic tab creation method"
962 l = MyTreeWidget(self)
963 l.setColumnCount( len(headers) )
964 l.setHeaderLabels( headers )
974 vbox.addWidget(buttons)
979 buttons.setLayout(hbox)
984 def create_receive_tab(self):
985 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
986 l.setContextMenuPolicy(Qt.CustomContextMenu)
987 l.customContextMenuRequested.connect(self.create_receive_menu)
988 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
989 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
990 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
991 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
992 self.receive_list = l
993 self.receive_buttons_hbox = hbox
1000 def save_column_widths(self):
1001 self.column_widths["receive"] = []
1002 for i in range(self.receive_list.columnCount() -1):
1003 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1005 self.column_widths["history"] = []
1006 for i in range(self.history_list.columnCount() - 1):
1007 self.column_widths["history"].append(self.history_list.columnWidth(i))
1009 self.column_widths["contacts"] = []
1010 for i in range(self.contacts_list.columnCount() - 1):
1011 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1013 self.config.set_key("column_widths_2", self.column_widths, True)
1016 def create_contacts_tab(self):
1017 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1018 l.setContextMenuPolicy(Qt.CustomContextMenu)
1019 l.customContextMenuRequested.connect(self.create_contact_menu)
1020 for i,width in enumerate(self.column_widths['contacts']):
1021 l.setColumnWidth(i, width)
1023 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1024 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1025 self.contacts_list = l
1026 self.contacts_buttons_hbox = hbox
1031 def delete_imported_key(self, addr):
1032 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1033 self.wallet.delete_imported_key(addr)
1034 self.update_receive_tab()
1035 self.update_history_tab()
1037 def edit_account_label(self, k):
1038 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1040 label = unicode(text)
1041 self.wallet.set_label(k,label)
1042 self.update_receive_tab()
1044 def account_set_expanded(self, item, k, b):
1046 self.accounts_expanded[k] = b
1048 def create_account_menu(self, position, k, item):
1050 if item.isExpanded():
1051 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1053 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1054 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1055 if self.wallet.seed_version > 4:
1056 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1057 if self.wallet.account_is_pending(k):
1058 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1059 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1061 def delete_pending_account(self, k):
1062 self.wallet.delete_pending_account(k)
1063 self.update_receive_tab()
1065 def create_receive_menu(self, position):
1066 # fixme: this function apparently has a side effect.
1067 # if it is not called the menu pops up several times
1068 #self.receive_list.selectedIndexes()
1070 selected = self.receive_list.selectedItems()
1071 multi_select = len(selected) > 1
1072 addrs = [unicode(item.text(0)) for item in selected]
1073 if not multi_select:
1074 item = self.receive_list.itemAt(position)
1078 if not is_valid(addr):
1079 k = str(item.data(0,32).toString())
1081 self.create_account_menu(position, k, item)
1083 item.setExpanded(not item.isExpanded())
1087 if not multi_select:
1088 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1089 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1090 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1091 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1092 if self.wallet.seed:
1093 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1094 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1095 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1096 if addr in self.wallet.imported_keys:
1097 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1099 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1100 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1101 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1102 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1104 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1105 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1107 run_hook('receive_menu', menu, addrs)
1108 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1111 def get_sendable_balance(self):
1112 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1115 def get_payment_sources(self):
1117 return self.pay_from
1119 return self.wallet.get_account_addresses(self.current_account)
1122 def send_from_addresses(self, addrs):
1123 self.set_pay_from( addrs )
1124 self.tabs.setCurrentIndex(1)
1127 def payto(self, addr):
1129 label = self.wallet.labels.get(addr)
1130 m_addr = label + ' <' + addr + '>' if label else addr
1131 self.tabs.setCurrentIndex(1)
1132 self.payto_e.setText(m_addr)
1133 self.amount_e.setFocus()
1136 def delete_contact(self, x):
1137 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1138 self.wallet.delete_contact(x)
1139 self.wallet.set_label(x, None)
1140 self.update_history_tab()
1141 self.update_contacts_tab()
1142 self.update_completions()
1145 def create_contact_menu(self, position):
1146 item = self.contacts_list.itemAt(position)
1149 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1151 addr = unicode(item.text(0))
1152 label = unicode(item.text(1))
1153 is_editable = item.data(0,32).toBool()
1154 payto_addr = item.data(0,33).toString()
1155 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1156 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1157 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1159 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1160 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1162 run_hook('create_contact_menu', menu, item)
1163 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1166 def update_receive_item(self, item):
1167 item.setFont(0, QFont(MONOSPACE_FONT))
1168 address = str(item.data(0,0).toString())
1169 label = self.wallet.labels.get(address,'')
1170 item.setData(1,0,label)
1171 item.setData(0,32, True) # is editable
1173 run_hook('update_receive_item', address, item)
1175 if not self.wallet.is_mine(address): return
1177 c, u = self.wallet.get_addr_balance(address)
1178 balance = self.format_amount(c + u)
1179 item.setData(2,0,balance)
1181 if address in self.wallet.frozen_addresses:
1182 item.setBackgroundColor(0, QColor('lightblue'))
1185 def update_receive_tab(self):
1186 l = self.receive_list
1189 l.setColumnHidden(2, False)
1190 l.setColumnHidden(3, False)
1191 for i,width in enumerate(self.column_widths['receive']):
1192 l.setColumnWidth(i, width)
1194 if self.current_account is None:
1195 account_items = self.wallet.accounts.items()
1196 elif self.current_account != -1:
1197 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1201 pending_accounts = self.wallet.get_pending_accounts()
1203 for k, account in account_items:
1205 if len(account_items) + len(pending_accounts) > 1:
1206 name = self.wallet.get_account_name(k)
1207 c,u = self.wallet.get_account_balance(k)
1208 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1209 l.addTopLevelItem(account_item)
1210 account_item.setExpanded(self.accounts_expanded.get(k, True))
1211 account_item.setData(0, 32, k)
1212 if not self.wallet.is_seeded(k):
1213 icon = QIcon(":icons/key.png")
1214 account_item.setIcon(0, icon)
1218 for is_change in ([0,1]):
1219 name = _("Receiving") if not is_change else _("Change")
1220 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1222 account_item.addChild(seq_item)
1224 l.addTopLevelItem(seq_item)
1226 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1228 if not is_change: seq_item.setExpanded(True)
1233 for address in account.get_addresses(is_change):
1234 h = self.wallet.history.get(address,[])
1238 if gap > self.wallet.gap_limit:
1243 c, u = self.wallet.get_addr_balance(address)
1244 num_tx = '*' if h == ['*'] else "%d"%len(h)
1245 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1246 self.update_receive_item(item)
1248 item.setBackgroundColor(1, QColor('red'))
1249 if len(h) > 0 and c == -u:
1251 seq_item.insertChild(0,used_item)
1253 used_item.addChild(item)
1255 seq_item.addChild(item)
1258 for k, addr in pending_accounts:
1259 name = self.wallet.labels.get(k,'')
1260 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1261 self.update_receive_item(item)
1262 l.addTopLevelItem(account_item)
1263 account_item.setExpanded(True)
1264 account_item.setData(0, 32, k)
1265 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1266 account_item.addChild(item)
1267 self.update_receive_item(item)
1270 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1271 c,u = self.wallet.get_imported_balance()
1272 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1273 l.addTopLevelItem(account_item)
1274 account_item.setExpanded(True)
1275 for address in self.wallet.imported_keys.keys():
1276 item = QTreeWidgetItem( [ address, '', '', ''] )
1277 self.update_receive_item(item)
1278 account_item.addChild(item)
1281 # we use column 1 because column 0 may be hidden
1282 l.setCurrentItem(l.topLevelItem(0),1)
1285 def update_contacts_tab(self):
1286 l = self.contacts_list
1289 for address in self.wallet.addressbook:
1290 label = self.wallet.labels.get(address,'')
1291 n = self.wallet.get_num_tx(address)
1292 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1293 item.setFont(0, QFont(MONOSPACE_FONT))
1294 # 32 = label can be edited (bool)
1295 item.setData(0,32, True)
1297 item.setData(0,33, address)
1298 l.addTopLevelItem(item)
1300 run_hook('update_contacts_tab', l)
1301 l.setCurrentItem(l.topLevelItem(0))
1305 def create_console_tab(self):
1306 from console import Console
1307 self.console = console = Console()
1311 def update_console(self):
1312 console = self.console
1313 console.history = self.config.get("console-history",[])
1314 console.history_index = len(console.history)
1316 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1317 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1319 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1321 def mkfunc(f, method):
1322 return lambda *args: apply( f, (method, args, self.password_dialog ))
1324 if m[0]=='_' or m in ['network','wallet']: continue
1325 methods[m] = mkfunc(c._run, m)
1327 console.updateNamespace(methods)
1330 def change_account(self,s):
1331 if s == _("All accounts"):
1332 self.current_account = None
1334 accounts = self.wallet.get_account_names()
1335 for k, v in accounts.items():
1337 self.current_account = k
1338 self.update_history_tab()
1339 self.update_status()
1340 self.update_receive_tab()
1342 def create_status_bar(self):
1345 sb.setFixedHeight(35)
1346 qtVersion = qVersion()
1348 self.balance_label = QLabel("")
1349 sb.addWidget(self.balance_label)
1351 from version_getter import UpdateLabel
1352 self.updatelabel = UpdateLabel(self.config, sb)
1354 self.account_selector = QComboBox()
1355 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1356 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1357 sb.addPermanentWidget(self.account_selector)
1359 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1360 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1362 self.lock_icon = QIcon()
1363 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1364 sb.addPermanentWidget( self.password_button )
1366 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1367 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1368 sb.addPermanentWidget( self.seed_button )
1369 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1370 sb.addPermanentWidget( self.status_button )
1372 run_hook('create_status_bar', (sb,))
1374 self.setStatusBar(sb)
1377 def update_lock_icon(self):
1378 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1379 self.password_button.setIcon( icon )
1382 def update_buttons_on_seed(self):
1383 if not self.wallet.is_watching_only():
1384 self.seed_button.show()
1385 self.password_button.show()
1386 self.send_button.setText(_("Send"))
1388 self.password_button.hide()
1389 self.seed_button.hide()
1390 self.send_button.setText(_("Create unsigned transaction"))
1393 def change_password_dialog(self):
1394 from password_dialog import PasswordDialog
1395 d = PasswordDialog(self.wallet, self)
1397 self.update_lock_icon()
1400 def new_contact_dialog(self):
1403 d.setWindowTitle(_("New Contact"))
1404 vbox = QVBoxLayout(d)
1405 vbox.addWidget(QLabel(_('New Contact')+':'))
1407 grid = QGridLayout()
1410 grid.addWidget(QLabel(_("Address")), 1, 0)
1411 grid.addWidget(line1, 1, 1)
1412 grid.addWidget(QLabel(_("Name")), 2, 0)
1413 grid.addWidget(line2, 2, 1)
1415 vbox.addLayout(grid)
1416 vbox.addLayout(ok_cancel_buttons(d))
1421 address = str(line1.text())
1422 label = unicode(line2.text())
1424 if not is_valid(address):
1425 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1428 self.wallet.add_contact(address)
1430 self.wallet.set_label(address, label)
1432 self.update_contacts_tab()
1433 self.update_history_tab()
1434 self.update_completions()
1435 self.tabs.setCurrentIndex(3)
1439 def new_account_dialog(self, password):
1441 dialog = QDialog(self)
1443 dialog.setWindowTitle(_("New Account"))
1445 vbox = QVBoxLayout()
1446 vbox.addWidget(QLabel(_('Account name')+':'))
1449 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1450 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1455 vbox.addLayout(ok_cancel_buttons(dialog))
1456 dialog.setLayout(vbox)
1460 name = str(e.text())
1463 self.wallet.create_pending_account(name, password)
1464 self.update_receive_tab()
1465 self.tabs.setCurrentIndex(2)
1470 def show_master_public_keys(self):
1472 dialog = QDialog(self)
1474 dialog.setWindowTitle(_("Master Public Keys"))
1476 main_layout = QGridLayout()
1477 mpk_dict = self.wallet.get_master_public_keys()
1479 for key, value in mpk_dict.items():
1480 main_layout.addWidget(QLabel(key), i, 0)
1481 mpk_text = QTextEdit()
1482 mpk_text.setReadOnly(True)
1483 mpk_text.setMaximumHeight(170)
1484 mpk_text.setText(value)
1485 main_layout.addWidget(mpk_text, i + 1, 0)
1488 vbox = QVBoxLayout()
1489 vbox.addLayout(main_layout)
1490 vbox.addLayout(close_button(dialog))
1492 dialog.setLayout(vbox)
1497 def show_seed_dialog(self, password):
1498 if self.wallet.is_watching_only():
1499 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1502 if self.wallet.seed:
1504 mnemonic = self.wallet.get_mnemonic(password)
1506 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1508 from seed_dialog import SeedDialog
1509 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1513 for k in self.wallet.master_private_keys.keys():
1514 pk = self.wallet.get_master_private_key(k, password)
1516 from seed_dialog import PrivateKeysDialog
1517 d = PrivateKeysDialog(self,l)
1524 def show_qrcode(self, data, title = _("QR code")):
1528 d.setWindowTitle(title)
1529 d.setMinimumSize(270, 300)
1530 vbox = QVBoxLayout()
1531 qrw = QRCodeWidget(data)
1532 vbox.addWidget(qrw, 1)
1533 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1534 hbox = QHBoxLayout()
1537 filename = os.path.join(self.config.path, "qrcode.bmp")
1540 bmp.save_qrcode(qrw.qr, filename)
1541 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1543 def copy_to_clipboard():
1544 bmp.save_qrcode(qrw.qr, filename)
1545 self.app.clipboard().setImage(QImage(filename))
1546 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1548 b = QPushButton(_("Copy"))
1550 b.clicked.connect(copy_to_clipboard)
1552 b = QPushButton(_("Save"))
1554 b.clicked.connect(print_qr)
1556 b = QPushButton(_("Close"))
1558 b.clicked.connect(d.accept)
1561 vbox.addLayout(hbox)
1566 def do_protect(self, func, args):
1567 if self.wallet.use_encryption:
1568 password = self.password_dialog()
1574 if args != (False,):
1575 args = (self,) + args + (password,)
1577 args = (self,password)
1581 def show_public_keys(self, address):
1582 if not address: return
1584 pubkey_list = self.wallet.get_public_keys(address)
1585 except Exception as e:
1586 traceback.print_exc(file=sys.stdout)
1587 self.show_message(str(e))
1591 d.setMinimumSize(600, 200)
1593 vbox = QVBoxLayout()
1594 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1595 vbox.addWidget( QLabel(_("Public key") + ':'))
1597 keys.setReadOnly(True)
1598 keys.setText('\n'.join(pubkey_list))
1599 vbox.addWidget(keys)
1600 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1601 vbox.addLayout(close_button(d))
1606 def show_private_key(self, address, password):
1607 if not address: return
1609 pk_list = self.wallet.get_private_key(address, password)
1610 except Exception as e:
1611 traceback.print_exc(file=sys.stdout)
1612 self.show_message(str(e))
1616 d.setMinimumSize(600, 200)
1618 vbox = QVBoxLayout()
1619 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1620 vbox.addWidget( QLabel(_("Private key") + ':'))
1622 keys.setReadOnly(True)
1623 keys.setText('\n'.join(pk_list))
1624 vbox.addWidget(keys)
1625 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1626 vbox.addLayout(close_button(d))
1632 def do_sign(self, address, message, signature, password):
1633 message = unicode(message.toPlainText())
1634 message = message.encode('utf-8')
1636 sig = self.wallet.sign_message(str(address.text()), message, password)
1637 signature.setText(sig)
1638 except Exception as e:
1639 self.show_message(str(e))
1641 def do_verify(self, address, message, signature):
1642 message = unicode(message.toPlainText())
1643 message = message.encode('utf-8')
1644 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1645 self.show_message(_("Signature verified"))
1647 self.show_message(_("Error: wrong signature"))
1650 def sign_verify_message(self, address=''):
1653 d.setWindowTitle(_('Sign/verify Message'))
1654 d.setMinimumSize(410, 290)
1656 layout = QGridLayout(d)
1658 message_e = QTextEdit()
1659 layout.addWidget(QLabel(_('Message')), 1, 0)
1660 layout.addWidget(message_e, 1, 1)
1661 layout.setRowStretch(2,3)
1663 address_e = QLineEdit()
1664 address_e.setText(address)
1665 layout.addWidget(QLabel(_('Address')), 2, 0)
1666 layout.addWidget(address_e, 2, 1)
1668 signature_e = QTextEdit()
1669 layout.addWidget(QLabel(_('Signature')), 3, 0)
1670 layout.addWidget(signature_e, 3, 1)
1671 layout.setRowStretch(3,1)
1673 hbox = QHBoxLayout()
1675 b = QPushButton(_("Sign"))
1676 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1679 b = QPushButton(_("Verify"))
1680 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1683 b = QPushButton(_("Close"))
1684 b.clicked.connect(d.accept)
1686 layout.addLayout(hbox, 4, 1)
1691 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1693 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1694 message_e.setText(decrypted)
1695 except Exception as e:
1696 self.show_message(str(e))
1699 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1700 message = unicode(message_e.toPlainText())
1701 message = message.encode('utf-8')
1703 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1704 encrypted_e.setText(encrypted)
1705 except Exception as e:
1706 self.show_message(str(e))
1710 def encrypt_message(self, address = ''):
1713 d.setWindowTitle(_('Encrypt/decrypt Message'))
1714 d.setMinimumSize(610, 490)
1716 layout = QGridLayout(d)
1718 message_e = QTextEdit()
1719 layout.addWidget(QLabel(_('Message')), 1, 0)
1720 layout.addWidget(message_e, 1, 1)
1721 layout.setRowStretch(2,3)
1723 pubkey_e = QLineEdit()
1725 pubkey = self.wallet.getpubkeys(address)[0]
1726 pubkey_e.setText(pubkey)
1727 layout.addWidget(QLabel(_('Public key')), 2, 0)
1728 layout.addWidget(pubkey_e, 2, 1)
1730 encrypted_e = QTextEdit()
1731 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1732 layout.addWidget(encrypted_e, 3, 1)
1733 layout.setRowStretch(3,1)
1735 hbox = QHBoxLayout()
1736 b = QPushButton(_("Encrypt"))
1737 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1740 b = QPushButton(_("Decrypt"))
1741 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1744 b = QPushButton(_("Close"))
1745 b.clicked.connect(d.accept)
1748 layout.addLayout(hbox, 4, 1)
1752 def question(self, msg):
1753 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1755 def show_message(self, msg):
1756 QMessageBox.information(self, _('Message'), msg, _('OK'))
1758 def password_dialog(self ):
1761 d.setWindowTitle(_("Enter Password"))
1766 vbox = QVBoxLayout()
1767 msg = _('Please enter your password')
1768 vbox.addWidget(QLabel(msg))
1770 grid = QGridLayout()
1772 grid.addWidget(QLabel(_('Password')), 1, 0)
1773 grid.addWidget(pw, 1, 1)
1774 vbox.addLayout(grid)
1776 vbox.addLayout(ok_cancel_buttons(d))
1779 run_hook('password_dialog', pw, grid, 1)
1780 if not d.exec_(): return
1781 return unicode(pw.text())
1790 def tx_from_text(self, txt):
1791 "json or raw hexadecimal"
1794 tx = Transaction(txt)
1800 tx_dict = json.loads(str(txt))
1801 assert "hex" in tx_dict.keys()
1802 assert "complete" in tx_dict.keys()
1803 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1804 if not tx_dict["complete"]:
1805 assert "input_info" in tx_dict.keys()
1806 input_info = json.loads(tx_dict['input_info'])
1807 tx.add_input_info(input_info)
1812 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1816 def read_tx_from_file(self):
1817 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1821 with open(fileName, "r") as f:
1822 file_content = f.read()
1823 except (ValueError, IOError, os.error), reason:
1824 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1826 return self.tx_from_text(file_content)
1830 def sign_raw_transaction(self, tx, input_info, password):
1831 self.wallet.signrawtransaction(tx, input_info, [], password)
1833 def do_process_from_text(self):
1834 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1837 tx = self.tx_from_text(text)
1839 self.show_transaction(tx)
1841 def do_process_from_file(self):
1842 tx = self.read_tx_from_file()
1844 self.show_transaction(tx)
1846 def do_process_from_txid(self):
1847 from electrum import transaction
1848 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1850 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1852 tx = transaction.Transaction(r)
1854 self.show_transaction(tx)
1856 self.show_message("unknown transaction")
1858 def do_process_from_csvReader(self, csvReader):
1863 for position, row in enumerate(csvReader):
1865 if not is_valid(address):
1866 errors.append((position, address))
1868 amount = Decimal(row[1])
1869 amount = int(100000000*amount)
1870 outputs.append((address, amount))
1871 except (ValueError, IOError, os.error), reason:
1872 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1876 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1877 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1881 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1882 except Exception as e:
1883 self.show_message(str(e))
1886 self.show_transaction(tx)
1888 def do_process_from_csv_file(self):
1889 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1893 with open(fileName, "r") as f:
1894 csvReader = csv.reader(f)
1895 self.do_process_from_csvReader(csvReader)
1896 except (ValueError, IOError, os.error), reason:
1897 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1900 def do_process_from_csv_text(self):
1901 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1902 + _("Format: address, amount. One output per line"), _("Load CSV"))
1905 f = StringIO.StringIO(text)
1906 csvReader = csv.reader(f)
1907 self.do_process_from_csvReader(csvReader)
1912 def do_export_privkeys(self, password):
1913 if not self.wallet.seed:
1914 self.show_message(_("This wallet has no seed"))
1917 self.show_message("%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."), _("Exposing a single private key can compromise your entire wallet!"), _("In particular, DO NOT use 'redeem private key' services proposed by third parties.")))
1920 select_export = _('Select file to export your private keys to')
1921 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1923 with open(fileName, "w+") as csvfile:
1924 transaction = csv.writer(csvfile)
1925 transaction.writerow(["address", "private_key"])
1927 addresses = self.wallet.addresses(True)
1929 for addr in addresses:
1930 pk = "".join(self.wallet.get_private_key(addr, password))
1931 transaction.writerow(["%34s"%addr,pk])
1933 self.show_message(_("Private keys exported."))
1935 except (IOError, os.error), reason:
1936 export_error_label = _("Electrum was unable to produce a private key-export.")
1937 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1939 except Exception as e:
1940 self.show_message(str(e))
1944 def do_import_labels(self):
1945 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1946 if not labelsFile: return
1948 f = open(labelsFile, 'r')
1951 for key, value in json.loads(data).items():
1952 self.wallet.set_label(key, value)
1953 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1954 except (IOError, os.error), reason:
1955 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1958 def do_export_labels(self):
1959 labels = self.wallet.labels
1961 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1963 with open(fileName, 'w+') as f:
1964 json.dump(labels, f)
1965 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1966 except (IOError, os.error), reason:
1967 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1970 def do_export_history(self):
1971 wallet = self.wallet
1972 select_export = _('Select file to export your wallet transactions to')
1973 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
1978 with open(fileName, "w+") as csvfile:
1979 transaction = csv.writer(csvfile)
1980 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
1981 for item in wallet.get_tx_history():
1982 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
1984 if timestamp is not None:
1986 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
1987 except [RuntimeError, TypeError, NameError] as reason:
1988 time_string = "unknown"
1991 time_string = "unknown"
1993 time_string = "pending"
1995 if value is not None:
1996 value_string = format_satoshis(value, True)
2001 fee_string = format_satoshis(fee, True)
2006 label, is_default_label = wallet.get_label(tx_hash)
2007 label = label.encode('utf-8')
2011 balance_string = format_satoshis(balance, False)
2012 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2013 QMessageBox.information(None,_("CSV Export created"), _("Your CSV export has been successfully created."))
2015 except (IOError, os.error), reason:
2016 export_error_label = _("Electrum was unable to produce a transaction export.")
2017 QMessageBox.critical(None,_("Unable to create csv"), export_error_label + "\n" + str(reason))
2022 def do_import_privkey(self, password):
2023 if not self.wallet.imported_keys:
2024 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2025 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2026 + _('Are you sure you understand what you are doing?'), 3, 4)
2029 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2032 text = str(text).split()
2037 addr = self.wallet.import_key(key, password)
2038 except Exception as e:
2044 addrlist.append(addr)
2046 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2048 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2049 self.update_receive_tab()
2050 self.update_history_tab()
2053 def settings_dialog(self):
2055 d.setWindowTitle(_('Electrum Settings'))
2057 vbox = QVBoxLayout()
2058 grid = QGridLayout()
2059 grid.setColumnStretch(0,1)
2061 nz_label = QLabel(_('Display zeros') + ':')
2062 grid.addWidget(nz_label, 0, 0)
2063 nz_e = AmountEdit(None,True)
2064 nz_e.setText("%d"% self.num_zeros)
2065 grid.addWidget(nz_e, 0, 1)
2066 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2067 grid.addWidget(HelpButton(msg), 0, 2)
2068 if not self.config.is_modifiable('num_zeros'):
2069 for w in [nz_e, nz_label]: w.setEnabled(False)
2071 lang_label=QLabel(_('Language') + ':')
2072 grid.addWidget(lang_label, 1, 0)
2073 lang_combo = QComboBox()
2074 from electrum.i18n import languages
2075 lang_combo.addItems(languages.values())
2077 index = languages.keys().index(self.config.get("language",''))
2080 lang_combo.setCurrentIndex(index)
2081 grid.addWidget(lang_combo, 1, 1)
2082 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2083 if not self.config.is_modifiable('language'):
2084 for w in [lang_combo, lang_label]: w.setEnabled(False)
2087 fee_label = QLabel(_('Transaction fee') + ':')
2088 grid.addWidget(fee_label, 2, 0)
2089 fee_e = AmountEdit(self.base_unit)
2090 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2091 grid.addWidget(fee_e, 2, 1)
2092 msg = _('Fee per kilobyte of transaction.') + ' ' \
2093 + _('Recommended value') + ': ' + self.format_amount(20000)
2094 grid.addWidget(HelpButton(msg), 2, 2)
2095 if not self.config.is_modifiable('fee_per_kb'):
2096 for w in [fee_e, fee_label]: w.setEnabled(False)
2098 units = ['BTC', 'mBTC']
2099 unit_label = QLabel(_('Base unit') + ':')
2100 grid.addWidget(unit_label, 3, 0)
2101 unit_combo = QComboBox()
2102 unit_combo.addItems(units)
2103 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2104 grid.addWidget(unit_combo, 3, 1)
2105 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2106 + '\n1BTC=1000mBTC.\n' \
2107 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2109 usechange_cb = QCheckBox(_('Use change addresses'))
2110 usechange_cb.setChecked(self.wallet.use_change)
2111 grid.addWidget(usechange_cb, 4, 0)
2112 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2113 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2115 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2116 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2117 grid.addWidget(block_ex_label, 5, 0)
2118 block_ex_combo = QComboBox()
2119 block_ex_combo.addItems(block_explorers)
2120 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2121 grid.addWidget(block_ex_combo, 5, 1)
2122 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2124 show_tx = self.config.get('show_before_broadcast', False)
2125 showtx_cb = QCheckBox(_('Show before broadcast'))
2126 showtx_cb.setChecked(show_tx)
2127 grid.addWidget(showtx_cb, 6, 0)
2128 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2130 vbox.addLayout(grid)
2132 vbox.addLayout(ok_cancel_buttons(d))
2136 if not d.exec_(): return
2138 fee = unicode(fee_e.text())
2140 fee = self.read_amount(fee)
2142 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2145 self.wallet.set_fee(fee)
2147 nz = unicode(nz_e.text())
2152 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2155 if self.num_zeros != nz:
2157 self.config.set_key('num_zeros', nz, True)
2158 self.update_history_tab()
2159 self.update_receive_tab()
2161 usechange_result = usechange_cb.isChecked()
2162 if self.wallet.use_change != usechange_result:
2163 self.wallet.use_change = usechange_result
2164 self.wallet.storage.put('use_change', self.wallet.use_change)
2166 if showtx_cb.isChecked() != show_tx:
2167 self.config.set_key('show_before_broadcast', not show_tx)
2169 unit_result = units[unit_combo.currentIndex()]
2170 if self.base_unit() != unit_result:
2171 self.decimal_point = 8 if unit_result == 'BTC' else 5
2172 self.config.set_key('decimal_point', self.decimal_point, True)
2173 self.update_history_tab()
2174 self.update_status()
2176 need_restart = False
2178 lang_request = languages.keys()[lang_combo.currentIndex()]
2179 if lang_request != self.config.get('language'):
2180 self.config.set_key("language", lang_request, True)
2183 be_result = block_explorers[block_ex_combo.currentIndex()]
2184 self.config.set_key('block_explorer', be_result, True)
2186 run_hook('close_settings_dialog')
2189 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2192 def run_network_dialog(self):
2193 if not self.network:
2195 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2197 def closeEvent(self, event):
2200 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2201 self.save_column_widths()
2202 self.config.set_key("console-history", self.console.history[-50:], True)
2203 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2207 def plugins_dialog(self):
2208 from electrum.plugins import plugins
2211 d.setWindowTitle(_('Electrum Plugins'))
2214 vbox = QVBoxLayout(d)
2217 scroll = QScrollArea()
2218 scroll.setEnabled(True)
2219 scroll.setWidgetResizable(True)
2220 scroll.setMinimumSize(400,250)
2221 vbox.addWidget(scroll)
2225 w.setMinimumHeight(len(plugins)*35)
2227 grid = QGridLayout()
2228 grid.setColumnStretch(0,1)
2231 def do_toggle(cb, p, w):
2234 if w: w.setEnabled(r)
2236 def mk_toggle(cb, p, w):
2237 return lambda: do_toggle(cb,p,w)
2239 for i, p in enumerate(plugins):
2241 cb = QCheckBox(p.fullname())
2242 cb.setDisabled(not p.is_available())
2243 cb.setChecked(p.is_enabled())
2244 grid.addWidget(cb, i, 0)
2245 if p.requires_settings():
2246 w = p.settings_widget(self)
2247 w.setEnabled( p.is_enabled() )
2248 grid.addWidget(w, i, 1)
2251 cb.clicked.connect(mk_toggle(cb,p,w))
2252 grid.addWidget(HelpButton(p.description()), i, 2)
2254 print_msg(_("Error: cannot display plugin"), p)
2255 traceback.print_exc(file=sys.stdout)
2256 grid.setRowStretch(i+1,1)
2258 vbox.addLayout(close_button(d))
2263 def show_account_details(self, k):
2264 account = self.wallet.accounts[k]
2267 d.setWindowTitle(_('Account Details'))
2270 vbox = QVBoxLayout(d)
2271 name = self.wallet.get_account_name(k)
2272 label = QLabel('Name: ' + name)
2273 vbox.addWidget(label)
2275 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2277 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2279 vbox.addWidget(QLabel(_('Master Public Key:')))
2282 text.setReadOnly(True)
2283 text.setMaximumHeight(170)
2284 vbox.addWidget(text)
2286 mpk_text = '\n'.join( account.get_master_pubkeys() )
2287 text.setText(mpk_text)
2289 vbox.addLayout(close_button(d))