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.gui_object = gui_object
111 self.tray = gui_object.tray
112 self.go_lite = gui_object.go_lite
115 self.create_status_bar()
116 self.need_update = threading.Event()
118 self.decimal_point = config.get('decimal_point', 5)
119 self.num_zeros = int(config.get('num_zeros',0))
121 set_language(config.get('language'))
123 self.funds_error = False
124 self.completions = QStringListModel()
126 self.tabs = tabs = QTabWidget(self)
127 self.column_widths = self.config.get("column_widths_2", default_column_widths )
128 tabs.addTab(self.create_history_tab(), _('History') )
129 tabs.addTab(self.create_send_tab(), _('Send') )
130 tabs.addTab(self.create_receive_tab(), _('Receive') )
131 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
132 tabs.addTab(self.create_console_tab(), _('Console') )
133 tabs.setMinimumSize(600, 400)
134 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
135 self.setCentralWidget(tabs)
137 g = self.config.get("winpos-qt",[100, 100, 840, 400])
138 self.setGeometry(g[0], g[1], g[2], g[3])
139 if self.config.get("is_maximized"):
142 self.setWindowIcon(QIcon(":icons/electrum.png"))
145 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
146 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
147 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
148 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
149 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
151 for i in range(tabs.count()):
152 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
154 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
155 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
156 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
157 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
158 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
160 self.history_list.setFocus(True)
164 self.network.register_callback('updated', lambda: self.need_update.set())
165 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
166 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
167 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
168 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
170 # set initial message
171 self.console.showMessage(self.network.banner)
176 def update_account_selector(self):
178 accounts = self.wallet.get_account_names()
179 self.account_selector.clear()
180 if len(accounts) > 1:
181 self.account_selector.addItems([_("All accounts")] + accounts.values())
182 self.account_selector.setCurrentIndex(0)
183 self.account_selector.show()
185 self.account_selector.hide()
188 def load_wallet(self, wallet):
191 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
192 self.current_account = self.wallet.storage.get("current_account", None)
194 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
195 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
196 self.setWindowTitle( title )
198 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
199 self.notify_transactions()
200 self.update_account_selector()
202 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
203 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
204 self.password_menu.setEnabled(not self.wallet.is_watching_only())
205 self.seed_menu.setEnabled(self.wallet.has_seed())
206 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
207 self.import_menu.setEnabled(self.wallet.can_import())
209 self.update_lock_icon()
210 self.update_buttons_on_seed()
211 self.update_console()
213 run_hook('load_wallet', wallet)
216 def open_wallet(self):
217 wallet_folder = self.wallet.storage.path
218 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
222 storage = WalletStorage({'wallet_path': filename})
223 if not storage.file_exists:
224 self.show_message("file not found "+ filename)
227 self.wallet.stop_threads()
230 wallet = Wallet(storage)
231 wallet.start_threads(self.network)
233 self.load_wallet(wallet)
237 def backup_wallet(self):
239 path = self.wallet.storage.path
240 wallet_folder = os.path.dirname(path)
241 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
245 new_path = os.path.join(wallet_folder, filename)
248 shutil.copy2(path, new_path)
249 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
250 except (IOError, os.error), reason:
251 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
254 def new_wallet(self):
257 wallet_folder = os.path.dirname(self.wallet.storage.path)
258 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
261 filename = os.path.join(wallet_folder, filename)
263 storage = WalletStorage({'wallet_path': filename})
264 if storage.file_exists:
265 QMessageBox.critical(None, "Error", _("File exists"))
268 wizard = installwizard.InstallWizard(self.config, self.network, storage)
269 wallet = wizard.run('new')
271 self.load_wallet(wallet)
275 def init_menubar(self):
278 file_menu = menubar.addMenu(_("&File"))
279 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
280 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
281 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
282 file_menu.addAction(_("&Quit"), self.close)
284 wallet_menu = menubar.addMenu(_("&Wallet"))
285 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
286 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
288 wallet_menu.addSeparator()
290 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
291 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
292 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
294 wallet_menu.addSeparator()
295 labels_menu = wallet_menu.addMenu(_("&Labels"))
296 labels_menu.addAction(_("&Import"), self.do_import_labels)
297 labels_menu.addAction(_("&Export"), self.do_export_labels)
299 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
300 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
301 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
302 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
303 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
305 tools_menu = menubar.addMenu(_("&Tools"))
307 # Settings / Preferences are all reserved keywords in OSX using this as work around
308 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
309 tools_menu.addAction(_("&Network"), self.run_network_dialog)
310 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
311 tools_menu.addSeparator()
312 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
313 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
314 tools_menu.addSeparator()
316 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
317 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
318 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
320 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
321 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
322 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
323 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
325 help_menu = menubar.addMenu(_("&Help"))
326 help_menu.addAction(_("&About"), self.show_about)
327 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
328 help_menu.addSeparator()
329 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
330 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
332 self.setMenuBar(menubar)
334 def show_about(self):
335 QMessageBox.about(self, "Electrum",
336 _("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."))
338 def show_report_bug(self):
339 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
340 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
343 def notify_transactions(self):
344 if not self.network or not self.network.is_connected():
347 print_error("Notifying GUI")
348 if len(self.network.pending_transactions_for_notifications) > 0:
349 # Combine the transactions if there are more then three
350 tx_amount = len(self.network.pending_transactions_for_notifications)
353 for tx in self.network.pending_transactions_for_notifications:
354 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
358 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
359 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
361 self.network.pending_transactions_for_notifications = []
363 for tx in self.network.pending_transactions_for_notifications:
365 self.network.pending_transactions_for_notifications.remove(tx)
366 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
368 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
370 def notify(self, message):
371 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
375 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
376 def getOpenFileName(self, title, filter = ""):
377 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
378 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
379 if fileName and directory != os.path.dirname(fileName):
380 self.config.set_key('io_dir', os.path.dirname(fileName), True)
383 def getSaveFileName(self, title, filename, filter = ""):
384 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
385 path = os.path.join( directory, filename )
386 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
387 if fileName and directory != os.path.dirname(fileName):
388 self.config.set_key('io_dir', os.path.dirname(fileName), True)
392 QMainWindow.close(self)
393 run_hook('close_main_window')
395 def connect_slots(self, sender):
396 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
397 self.previous_payto_e=''
399 def timer_actions(self):
400 if self.need_update.is_set():
402 self.need_update.clear()
403 run_hook('timer_actions')
405 def format_amount(self, x, is_diff=False, whitespaces=False):
406 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
408 def read_amount(self, x):
409 if x in['.', '']: return None
410 p = pow(10, self.decimal_point)
411 return int( p * Decimal(x) )
414 assert self.decimal_point in [5,8]
415 return "BTC" if self.decimal_point == 8 else "mBTC"
418 def update_status(self):
419 if self.network is None or not self.network.is_running():
421 icon = QIcon(":icons/status_disconnected.png")
423 elif self.network.is_connected():
424 if not self.wallet.up_to_date:
425 text = _("Synchronizing...")
426 icon = QIcon(":icons/status_waiting.png")
427 elif self.network.server_lag > 1:
428 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
429 icon = QIcon(":icons/status_lagging.png")
431 c, u = self.wallet.get_account_balance(self.current_account)
432 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
433 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
435 # append fiat balance and price from exchange rate plugin
437 run_hook('get_fiat_status_text', c+u, r)
442 self.tray.setToolTip(text)
443 icon = QIcon(":icons/status_connected.png")
445 text = _("Not connected")
446 icon = QIcon(":icons/status_disconnected.png")
448 self.balance_label.setText(text)
449 self.status_button.setIcon( icon )
452 def update_wallet(self):
454 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
455 self.update_history_tab()
456 self.update_receive_tab()
457 self.update_contacts_tab()
458 self.update_completions()
461 def create_history_tab(self):
462 self.history_list = l = MyTreeWidget(self)
464 for i,width in enumerate(self.column_widths['history']):
465 l.setColumnWidth(i, width)
466 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
467 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
468 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
470 l.customContextMenuRequested.connect(self.create_history_menu)
474 def create_history_menu(self, position):
475 self.history_list.selectedIndexes()
476 item = self.history_list.currentItem()
477 be = self.config.get('block_explorer', 'Blockchain.info')
478 if be == 'Blockchain.info':
479 block_explorer = 'https://blockchain.info/tx/'
480 elif be == 'Blockr.io':
481 block_explorer = 'https://blockr.io/tx/info/'
482 elif be == 'Insight.is':
483 block_explorer = 'http://live.insight.is/tx/'
485 tx_hash = str(item.data(0, Qt.UserRole).toString())
486 if not tx_hash: return
488 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
489 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
490 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
491 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
492 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
495 def show_transaction(self, tx):
496 import transaction_dialog
497 d = transaction_dialog.TxDialog(tx, self)
500 def tx_label_clicked(self, item, column):
501 if column==2 and item.isSelected():
503 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
504 self.history_list.editItem( item, column )
505 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
508 def tx_label_changed(self, item, column):
512 tx_hash = str(item.data(0, Qt.UserRole).toString())
513 tx = self.wallet.transactions.get(tx_hash)
514 text = unicode( item.text(2) )
515 self.wallet.set_label(tx_hash, text)
517 item.setForeground(2, QBrush(QColor('black')))
519 text = self.wallet.get_default_label(tx_hash)
520 item.setText(2, text)
521 item.setForeground(2, QBrush(QColor('gray')))
525 def edit_label(self, is_recv):
526 l = self.receive_list if is_recv else self.contacts_list
527 item = l.currentItem()
528 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
529 l.editItem( item, 1 )
530 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
534 def address_label_clicked(self, item, column, l, column_addr, column_label):
535 if column == column_label and item.isSelected():
536 is_editable = item.data(0, 32).toBool()
539 addr = unicode( item.text(column_addr) )
540 label = unicode( item.text(column_label) )
541 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
542 l.editItem( item, column )
543 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
546 def address_label_changed(self, item, column, l, column_addr, column_label):
547 if column == column_label:
548 addr = unicode( item.text(column_addr) )
549 text = unicode( item.text(column_label) )
550 is_editable = item.data(0, 32).toBool()
554 changed = self.wallet.set_label(addr, text)
556 self.update_history_tab()
557 self.update_completions()
559 self.current_item_changed(item)
561 run_hook('item_changed', item, column)
564 def current_item_changed(self, a):
565 run_hook('current_item_changed', a)
569 def update_history_tab(self):
571 self.history_list.clear()
572 for item in self.wallet.get_tx_history(self.current_account):
573 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
574 time_str = _("unknown")
577 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
579 time_str = _("error")
582 time_str = 'unverified'
583 icon = QIcon(":icons/unconfirmed.png")
586 icon = QIcon(":icons/unconfirmed.png")
588 icon = QIcon(":icons/clock%d.png"%conf)
590 icon = QIcon(":icons/confirmed.png")
592 if value is not None:
593 v_str = self.format_amount(value, True, whitespaces=True)
597 balance_str = self.format_amount(balance, whitespaces=True)
600 label, is_default_label = self.wallet.get_label(tx_hash)
602 label = _('Pruned transaction outputs')
603 is_default_label = False
605 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
606 item.setFont(2, QFont(MONOSPACE_FONT))
607 item.setFont(3, QFont(MONOSPACE_FONT))
608 item.setFont(4, QFont(MONOSPACE_FONT))
610 item.setForeground(3, QBrush(QColor("#BC1E1E")))
612 item.setData(0, Qt.UserRole, tx_hash)
613 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
615 item.setForeground(2, QBrush(QColor('grey')))
617 item.setIcon(0, icon)
618 self.history_list.insertTopLevelItem(0,item)
621 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
622 run_hook('history_tab_update')
625 def create_send_tab(self):
630 grid.setColumnMinimumWidth(3,300)
631 grid.setColumnStretch(5,1)
634 self.payto_e = QLineEdit()
635 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)'))
636 grid.addWidget(QLabel(_('Pay to')), 1, 0)
637 grid.addWidget(self.payto_e, 1, 1, 1, 3)
638 grid.addWidget(self.payto_help, 1, 4)
640 completer = QCompleter()
641 completer.setCaseSensitivity(False)
642 self.payto_e.setCompleter(completer)
643 completer.setModel(self.completions)
645 self.message_e = QLineEdit()
646 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.'))
647 grid.addWidget(QLabel(_('Description')), 2, 0)
648 grid.addWidget(self.message_e, 2, 1, 1, 3)
649 grid.addWidget(self.message_help, 2, 4)
651 self.from_label = QLabel(_('From'))
652 grid.addWidget(self.from_label, 3, 0)
653 self.from_list = QTreeWidget(self)
654 self.from_list.setColumnCount(2)
655 self.from_list.setColumnWidth(0, 350)
656 self.from_list.setColumnWidth(1, 50)
657 self.from_list.setHeaderHidden (True)
658 self.from_list.setMaximumHeight(80)
659 grid.addWidget(self.from_list, 3, 1, 1, 3)
660 self.set_pay_from([])
662 self.amount_e = AmountEdit(self.base_unit)
663 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
664 + _('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.') \
665 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
666 grid.addWidget(QLabel(_('Amount')), 4, 0)
667 grid.addWidget(self.amount_e, 4, 1, 1, 2)
668 grid.addWidget(self.amount_help, 4, 3)
670 self.fee_e = AmountEdit(self.base_unit)
671 grid.addWidget(QLabel(_('Fee')), 5, 0)
672 grid.addWidget(self.fee_e, 5, 1, 1, 2)
673 grid.addWidget(HelpButton(
674 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
675 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
676 + _('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)
678 run_hook('exchange_rate_button', grid)
680 self.send_button = EnterButton(_("Send"), self.do_send)
681 grid.addWidget(self.send_button, 6, 1)
683 b = EnterButton(_("Clear"),self.do_clear)
684 grid.addWidget(b, 6, 2)
686 self.payto_sig = QLabel('')
687 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
689 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
690 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
699 def entry_changed( is_fee ):
700 self.funds_error = False
702 if self.amount_e.is_shortcut:
703 self.amount_e.is_shortcut = False
704 sendable = self.get_sendable_balance()
705 # there is only one output because we are completely spending inputs
706 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
707 fee = self.wallet.estimated_fee(inputs, 1)
709 self.amount_e.setText( self.format_amount(amount) )
710 self.fee_e.setText( self.format_amount( fee ) )
713 amount = self.read_amount(str(self.amount_e.text()))
714 fee = self.read_amount(str(self.fee_e.text()))
716 if not is_fee: fee = None
719 # assume that there will be 2 outputs (one for change)
720 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
722 self.fee_e.setText( self.format_amount( fee ) )
725 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
729 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
730 self.funds_error = True
731 text = _( "Not enough funds" )
732 c, u = self.wallet.get_frozen_balance()
733 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
735 self.statusBar().showMessage(text)
736 self.amount_e.setPalette(palette)
737 self.fee_e.setPalette(palette)
739 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
740 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
742 run_hook('create_send_tab', grid)
746 def set_pay_from(self, l):
748 self.from_list.clear()
749 self.from_label.setHidden(len(self.pay_from) == 0)
750 self.from_list.setHidden(len(self.pay_from) == 0)
751 for addr in self.pay_from:
752 c, u = self.wallet.get_addr_balance(addr)
753 balance = self.format_amount(c + u)
754 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
757 def update_completions(self):
759 for addr,label in self.wallet.labels.items():
760 if addr in self.wallet.addressbook:
761 l.append( label + ' <' + addr + '>')
763 run_hook('update_completions', l)
764 self.completions.setStringList(l)
768 return lambda s, *args: s.do_protect(func, args)
772 label = unicode( self.message_e.text() )
774 if self.gui_object.payment_request:
775 outputs = self.gui_object.payment_request.outputs
776 amount = self.gui_object.payment_request.get_amount()
779 r = unicode( self.payto_e.text() )
782 # label or alias, with address in brackets
783 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
784 to_address = m.group(2) if m else r
785 if not is_valid(to_address):
786 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
790 amount = self.read_amount(unicode( self.amount_e.text()))
792 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
795 outputs = [(to_address, amount)]
798 fee = self.read_amount(unicode( self.fee_e.text()))
800 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
803 confirm_amount = self.config.get('confirm_amount', 100000000)
804 if amount >= confirm_amount:
805 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
808 confirm_fee = self.config.get('confirm_fee', 100000)
809 if fee >= confirm_fee:
810 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()}):
813 self.send_tx(outputs, fee, label)
818 def send_tx(self, outputs, fee, label, password):
820 # first, create an unsigned tx
821 domain = self.get_payment_sources()
823 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
825 except Exception as e:
826 traceback.print_exc(file=sys.stdout)
827 self.show_message(str(e))
830 # call hook to see if plugin needs gui interaction
831 run_hook('send_tx', tx)
837 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
838 self.wallet.sign_transaction(tx, keypairs, password)
839 return tx, fee, label
841 def sign_done(tx, fee, label):
843 self.show_message(tx.error)
845 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
846 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
849 self.wallet.set_label(tx.hash(), label)
851 if not self.gui_object.payment_request:
852 if not tx.is_complete() or self.config.get('show_before_broadcast'):
853 self.show_transaction(tx)
856 self.broadcast_transaction(tx)
858 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
859 self.waiting_dialog.start()
863 def broadcast_transaction(self, tx):
865 def broadcast_thread():
866 if self.gui_object.payment_request:
867 refund_address = self.wallet.addresses()[0]
868 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
869 self.gui_object.payment_request = None
871 status, msg = self.wallet.sendtx(tx)
874 def broadcast_done(status, msg):
876 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
879 QMessageBox.warning(self, _('Error'), msg, _('OK'))
881 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
882 self.waiting_dialog.start()
886 def prepare_for_payment_request(self):
887 style = "QWidget { background-color:none;border:none;}"
888 self.tabs.setCurrentIndex(1)
889 for e in [self.payto_e, self.amount_e, self.message_e]:
891 e.setStyleSheet(style)
892 for h in [self.payto_help, self.amount_help, self.message_help]:
894 self.payto_e.setText(_("please wait..."))
897 def payment_request_ok(self):
898 self.payto_e.setText(self.gui_object.payment_request.domain)
899 self.amount_e.setText(self.format_amount(self.gui_object.payment_request.get_amount()))
900 self.message_e.setText(self.gui_object.payment_request.memo)
902 def payment_request_error(self):
904 self.show_message(self.gui_object.payment_request.error)
907 def set_send(self, address, amount, label, message):
909 if label and self.wallet.labels.get(address) != label:
910 if self.question('Give label "%s" to address %s ?'%(label,address)):
911 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
912 self.wallet.addressbook.append(address)
913 self.wallet.set_label(address, label)
915 self.tabs.setCurrentIndex(1)
916 label = self.wallet.labels.get(address)
917 m_addr = label + ' <'+ address +'>' if label else address
918 self.payto_e.setText(m_addr)
920 self.message_e.setText(message)
922 self.amount_e.setText(amount)
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 for h in [self.payto_help, self.amount_help, self.message_help]:
934 self.set_pay_from([])
937 def set_frozen(self,entry,frozen):
939 entry.setReadOnly(True)
940 entry.setFrame(False)
942 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
943 entry.setPalette(palette)
945 entry.setReadOnly(False)
948 palette.setColor(entry.backgroundRole(), QColor('white'))
949 entry.setPalette(palette)
952 def set_addrs_frozen(self,addrs,freeze):
954 if not addr: continue
955 if addr in self.wallet.frozen_addresses and not freeze:
956 self.wallet.unfreeze(addr)
957 elif addr not in self.wallet.frozen_addresses and freeze:
958 self.wallet.freeze(addr)
959 self.update_receive_tab()
963 def create_list_tab(self, headers):
964 "generic tab creation method"
965 l = MyTreeWidget(self)
966 l.setColumnCount( len(headers) )
967 l.setHeaderLabels( headers )
977 vbox.addWidget(buttons)
982 buttons.setLayout(hbox)
987 def create_receive_tab(self):
988 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
989 l.setContextMenuPolicy(Qt.CustomContextMenu)
990 l.customContextMenuRequested.connect(self.create_receive_menu)
991 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
992 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
993 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
994 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
995 self.receive_list = l
996 self.receive_buttons_hbox = hbox
1003 def save_column_widths(self):
1004 self.column_widths["receive"] = []
1005 for i in range(self.receive_list.columnCount() -1):
1006 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1008 self.column_widths["history"] = []
1009 for i in range(self.history_list.columnCount() - 1):
1010 self.column_widths["history"].append(self.history_list.columnWidth(i))
1012 self.column_widths["contacts"] = []
1013 for i in range(self.contacts_list.columnCount() - 1):
1014 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1016 self.config.set_key("column_widths_2", self.column_widths, True)
1019 def create_contacts_tab(self):
1020 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1021 l.setContextMenuPolicy(Qt.CustomContextMenu)
1022 l.customContextMenuRequested.connect(self.create_contact_menu)
1023 for i,width in enumerate(self.column_widths['contacts']):
1024 l.setColumnWidth(i, width)
1026 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1027 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1028 self.contacts_list = l
1029 self.contacts_buttons_hbox = hbox
1034 def delete_imported_key(self, addr):
1035 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1036 self.wallet.delete_imported_key(addr)
1037 self.update_receive_tab()
1038 self.update_history_tab()
1040 def edit_account_label(self, k):
1041 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1043 label = unicode(text)
1044 self.wallet.set_label(k,label)
1045 self.update_receive_tab()
1047 def account_set_expanded(self, item, k, b):
1049 self.accounts_expanded[k] = b
1051 def create_account_menu(self, position, k, item):
1053 if item.isExpanded():
1054 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1056 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1057 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1058 if self.wallet.seed_version > 4:
1059 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1060 if self.wallet.account_is_pending(k):
1061 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1062 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1064 def delete_pending_account(self, k):
1065 self.wallet.delete_pending_account(k)
1066 self.update_receive_tab()
1068 def create_receive_menu(self, position):
1069 # fixme: this function apparently has a side effect.
1070 # if it is not called the menu pops up several times
1071 #self.receive_list.selectedIndexes()
1073 selected = self.receive_list.selectedItems()
1074 multi_select = len(selected) > 1
1075 addrs = [unicode(item.text(0)) for item in selected]
1076 if not multi_select:
1077 item = self.receive_list.itemAt(position)
1081 if not is_valid(addr):
1082 k = str(item.data(0,32).toString())
1084 self.create_account_menu(position, k, item)
1086 item.setExpanded(not item.isExpanded())
1090 if not multi_select:
1091 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1092 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1093 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1094 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1095 if not self.wallet.is_watching_only():
1096 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1097 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1098 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1099 if self.wallet.is_imported(addr):
1100 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1102 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1103 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1104 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1105 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1107 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1108 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1110 run_hook('receive_menu', menu, addrs)
1111 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1114 def get_sendable_balance(self):
1115 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1118 def get_payment_sources(self):
1120 return self.pay_from
1122 return self.wallet.get_account_addresses(self.current_account)
1125 def send_from_addresses(self, addrs):
1126 self.set_pay_from( addrs )
1127 self.tabs.setCurrentIndex(1)
1130 def payto(self, addr):
1132 label = self.wallet.labels.get(addr)
1133 m_addr = label + ' <' + addr + '>' if label else addr
1134 self.tabs.setCurrentIndex(1)
1135 self.payto_e.setText(m_addr)
1136 self.amount_e.setFocus()
1139 def delete_contact(self, x):
1140 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1141 self.wallet.delete_contact(x)
1142 self.wallet.set_label(x, None)
1143 self.update_history_tab()
1144 self.update_contacts_tab()
1145 self.update_completions()
1148 def create_contact_menu(self, position):
1149 item = self.contacts_list.itemAt(position)
1152 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1154 addr = unicode(item.text(0))
1155 label = unicode(item.text(1))
1156 is_editable = item.data(0,32).toBool()
1157 payto_addr = item.data(0,33).toString()
1158 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1159 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1160 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1162 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1163 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1165 run_hook('create_contact_menu', menu, item)
1166 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1169 def update_receive_item(self, item):
1170 item.setFont(0, QFont(MONOSPACE_FONT))
1171 address = str(item.data(0,0).toString())
1172 label = self.wallet.labels.get(address,'')
1173 item.setData(1,0,label)
1174 item.setData(0,32, True) # is editable
1176 run_hook('update_receive_item', address, item)
1178 if not self.wallet.is_mine(address): return
1180 c, u = self.wallet.get_addr_balance(address)
1181 balance = self.format_amount(c + u)
1182 item.setData(2,0,balance)
1184 if address in self.wallet.frozen_addresses:
1185 item.setBackgroundColor(0, QColor('lightblue'))
1188 def update_receive_tab(self):
1189 l = self.receive_list
1190 # extend the syntax for consistency
1191 l.addChild = l.addTopLevelItem
1192 l.insertChild = l.insertTopLevelItem
1195 for i,width in enumerate(self.column_widths['receive']):
1196 l.setColumnWidth(i, width)
1198 accounts = self.wallet.get_accounts()
1199 if self.current_account is None:
1200 account_items = sorted(accounts.items())
1202 account_items = [(self.current_account, accounts.get(self.current_account))]
1205 for k, account in account_items:
1207 if len(accounts) > 1:
1208 name = self.wallet.get_account_name(k)
1209 c,u = self.wallet.get_account_balance(k)
1210 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1211 l.addTopLevelItem(account_item)
1212 account_item.setExpanded(self.accounts_expanded.get(k, True))
1213 account_item.setData(0, 32, k)
1217 sequences = [0,1] if account.has_change() else [0]
1218 for is_change in sequences:
1219 if len(sequences) > 1:
1220 name = _("Receiving") if not is_change else _("Change")
1221 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1222 account_item.addChild(seq_item)
1224 seq_item.setExpanded(True)
1226 seq_item = account_item
1228 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1234 for address in account.get_addresses(is_change):
1236 num, is_used = self.wallet.is_used(address)
1239 if gap > self.wallet.gap_limit:
1244 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1245 self.update_receive_item(item)
1247 item.setBackgroundColor(1, QColor('red'))
1251 seq_item.insertChild(0,used_item)
1253 used_item.addChild(item)
1255 seq_item.addChild(item)
1257 # we use column 1 because column 0 may be hidden
1258 l.setCurrentItem(l.topLevelItem(0),1)
1261 def update_contacts_tab(self):
1262 l = self.contacts_list
1265 for address in self.wallet.addressbook:
1266 label = self.wallet.labels.get(address,'')
1267 n = self.wallet.get_num_tx(address)
1268 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1269 item.setFont(0, QFont(MONOSPACE_FONT))
1270 # 32 = label can be edited (bool)
1271 item.setData(0,32, True)
1273 item.setData(0,33, address)
1274 l.addTopLevelItem(item)
1276 run_hook('update_contacts_tab', l)
1277 l.setCurrentItem(l.topLevelItem(0))
1281 def create_console_tab(self):
1282 from console import Console
1283 self.console = console = Console()
1287 def update_console(self):
1288 console = self.console
1289 console.history = self.config.get("console-history",[])
1290 console.history_index = len(console.history)
1292 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1293 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1295 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1297 def mkfunc(f, method):
1298 return lambda *args: apply( f, (method, args, self.password_dialog ))
1300 if m[0]=='_' or m in ['network','wallet']: continue
1301 methods[m] = mkfunc(c._run, m)
1303 console.updateNamespace(methods)
1306 def change_account(self,s):
1307 if s == _("All accounts"):
1308 self.current_account = None
1310 accounts = self.wallet.get_account_names()
1311 for k, v in accounts.items():
1313 self.current_account = k
1314 self.update_history_tab()
1315 self.update_status()
1316 self.update_receive_tab()
1318 def create_status_bar(self):
1321 sb.setFixedHeight(35)
1322 qtVersion = qVersion()
1324 self.balance_label = QLabel("")
1325 sb.addWidget(self.balance_label)
1327 from version_getter import UpdateLabel
1328 self.updatelabel = UpdateLabel(self.config, sb)
1330 self.account_selector = QComboBox()
1331 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1332 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1333 sb.addPermanentWidget(self.account_selector)
1335 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1336 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1338 self.lock_icon = QIcon()
1339 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1340 sb.addPermanentWidget( self.password_button )
1342 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1343 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1344 sb.addPermanentWidget( self.seed_button )
1345 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1346 sb.addPermanentWidget( self.status_button )
1348 run_hook('create_status_bar', (sb,))
1350 self.setStatusBar(sb)
1353 def update_lock_icon(self):
1354 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1355 self.password_button.setIcon( icon )
1358 def update_buttons_on_seed(self):
1359 if self.wallet.has_seed():
1360 self.seed_button.show()
1362 self.seed_button.hide()
1364 if not self.wallet.is_watching_only():
1365 self.password_button.show()
1366 self.send_button.setText(_("Send"))
1368 self.password_button.hide()
1369 self.send_button.setText(_("Create unsigned transaction"))
1372 def change_password_dialog(self):
1373 from password_dialog import PasswordDialog
1374 d = PasswordDialog(self.wallet, self)
1376 self.update_lock_icon()
1379 def new_contact_dialog(self):
1382 d.setWindowTitle(_("New Contact"))
1383 vbox = QVBoxLayout(d)
1384 vbox.addWidget(QLabel(_('New Contact')+':'))
1386 grid = QGridLayout()
1389 grid.addWidget(QLabel(_("Address")), 1, 0)
1390 grid.addWidget(line1, 1, 1)
1391 grid.addWidget(QLabel(_("Name")), 2, 0)
1392 grid.addWidget(line2, 2, 1)
1394 vbox.addLayout(grid)
1395 vbox.addLayout(ok_cancel_buttons(d))
1400 address = str(line1.text())
1401 label = unicode(line2.text())
1403 if not is_valid(address):
1404 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1407 self.wallet.add_contact(address)
1409 self.wallet.set_label(address, label)
1411 self.update_contacts_tab()
1412 self.update_history_tab()
1413 self.update_completions()
1414 self.tabs.setCurrentIndex(3)
1418 def new_account_dialog(self, password):
1420 dialog = QDialog(self)
1422 dialog.setWindowTitle(_("New Account"))
1424 vbox = QVBoxLayout()
1425 vbox.addWidget(QLabel(_('Account name')+':'))
1428 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1429 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1434 vbox.addLayout(ok_cancel_buttons(dialog))
1435 dialog.setLayout(vbox)
1439 name = str(e.text())
1442 self.wallet.create_pending_account(name, password)
1443 self.update_receive_tab()
1444 self.tabs.setCurrentIndex(2)
1449 def show_master_public_keys(self):
1451 dialog = QDialog(self)
1453 dialog.setWindowTitle(_("Master Public Keys"))
1455 main_layout = QGridLayout()
1456 mpk_dict = self.wallet.get_master_public_keys()
1458 for key, value in mpk_dict.items():
1459 main_layout.addWidget(QLabel(key), i, 0)
1460 mpk_text = QTextEdit()
1461 mpk_text.setReadOnly(True)
1462 mpk_text.setMaximumHeight(170)
1463 mpk_text.setText(value)
1464 main_layout.addWidget(mpk_text, i + 1, 0)
1467 vbox = QVBoxLayout()
1468 vbox.addLayout(main_layout)
1469 vbox.addLayout(close_button(dialog))
1471 dialog.setLayout(vbox)
1476 def show_seed_dialog(self, password):
1477 if not self.wallet.has_seed():
1478 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1482 mnemonic = self.wallet.get_mnemonic(password)
1484 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1486 from seed_dialog import SeedDialog
1487 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1492 def show_qrcode(self, data, title = _("QR code")):
1496 d.setWindowTitle(title)
1497 d.setMinimumSize(270, 300)
1498 vbox = QVBoxLayout()
1499 qrw = QRCodeWidget(data)
1500 vbox.addWidget(qrw, 1)
1501 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1502 hbox = QHBoxLayout()
1505 filename = os.path.join(self.config.path, "qrcode.bmp")
1508 bmp.save_qrcode(qrw.qr, filename)
1509 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1511 def copy_to_clipboard():
1512 bmp.save_qrcode(qrw.qr, filename)
1513 self.app.clipboard().setImage(QImage(filename))
1514 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1516 b = QPushButton(_("Copy"))
1518 b.clicked.connect(copy_to_clipboard)
1520 b = QPushButton(_("Save"))
1522 b.clicked.connect(print_qr)
1524 b = QPushButton(_("Close"))
1526 b.clicked.connect(d.accept)
1529 vbox.addLayout(hbox)
1534 def do_protect(self, func, args):
1535 if self.wallet.use_encryption:
1536 password = self.password_dialog()
1542 if args != (False,):
1543 args = (self,) + args + (password,)
1545 args = (self,password)
1549 def show_public_keys(self, address):
1550 if not address: return
1552 pubkey_list = self.wallet.get_public_keys(address)
1553 except Exception as e:
1554 traceback.print_exc(file=sys.stdout)
1555 self.show_message(str(e))
1559 d.setMinimumSize(600, 200)
1561 vbox = QVBoxLayout()
1562 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1563 vbox.addWidget( QLabel(_("Public key") + ':'))
1565 keys.setReadOnly(True)
1566 keys.setText('\n'.join(pubkey_list))
1567 vbox.addWidget(keys)
1568 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1569 vbox.addLayout(close_button(d))
1574 def show_private_key(self, address, password):
1575 if not address: return
1577 pk_list = self.wallet.get_private_key(address, password)
1578 except Exception as e:
1579 traceback.print_exc(file=sys.stdout)
1580 self.show_message(str(e))
1584 d.setMinimumSize(600, 200)
1586 vbox = QVBoxLayout()
1587 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1588 vbox.addWidget( QLabel(_("Private key") + ':'))
1590 keys.setReadOnly(True)
1591 keys.setText('\n'.join(pk_list))
1592 vbox.addWidget(keys)
1593 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1594 vbox.addLayout(close_button(d))
1600 def do_sign(self, address, message, signature, password):
1601 message = unicode(message.toPlainText())
1602 message = message.encode('utf-8')
1604 sig = self.wallet.sign_message(str(address.text()), message, password)
1605 signature.setText(sig)
1606 except Exception as e:
1607 self.show_message(str(e))
1609 def do_verify(self, address, message, signature):
1610 message = unicode(message.toPlainText())
1611 message = message.encode('utf-8')
1612 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1613 self.show_message(_("Signature verified"))
1615 self.show_message(_("Error: wrong signature"))
1618 def sign_verify_message(self, address=''):
1621 d.setWindowTitle(_('Sign/verify Message'))
1622 d.setMinimumSize(410, 290)
1624 layout = QGridLayout(d)
1626 message_e = QTextEdit()
1627 layout.addWidget(QLabel(_('Message')), 1, 0)
1628 layout.addWidget(message_e, 1, 1)
1629 layout.setRowStretch(2,3)
1631 address_e = QLineEdit()
1632 address_e.setText(address)
1633 layout.addWidget(QLabel(_('Address')), 2, 0)
1634 layout.addWidget(address_e, 2, 1)
1636 signature_e = QTextEdit()
1637 layout.addWidget(QLabel(_('Signature')), 3, 0)
1638 layout.addWidget(signature_e, 3, 1)
1639 layout.setRowStretch(3,1)
1641 hbox = QHBoxLayout()
1643 b = QPushButton(_("Sign"))
1644 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1647 b = QPushButton(_("Verify"))
1648 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1651 b = QPushButton(_("Close"))
1652 b.clicked.connect(d.accept)
1654 layout.addLayout(hbox, 4, 1)
1659 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1661 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1662 message_e.setText(decrypted)
1663 except Exception as e:
1664 self.show_message(str(e))
1667 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1668 message = unicode(message_e.toPlainText())
1669 message = message.encode('utf-8')
1671 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1672 encrypted_e.setText(encrypted)
1673 except Exception as e:
1674 self.show_message(str(e))
1678 def encrypt_message(self, address = ''):
1681 d.setWindowTitle(_('Encrypt/decrypt Message'))
1682 d.setMinimumSize(610, 490)
1684 layout = QGridLayout(d)
1686 message_e = QTextEdit()
1687 layout.addWidget(QLabel(_('Message')), 1, 0)
1688 layout.addWidget(message_e, 1, 1)
1689 layout.setRowStretch(2,3)
1691 pubkey_e = QLineEdit()
1693 pubkey = self.wallet.getpubkeys(address)[0]
1694 pubkey_e.setText(pubkey)
1695 layout.addWidget(QLabel(_('Public key')), 2, 0)
1696 layout.addWidget(pubkey_e, 2, 1)
1698 encrypted_e = QTextEdit()
1699 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1700 layout.addWidget(encrypted_e, 3, 1)
1701 layout.setRowStretch(3,1)
1703 hbox = QHBoxLayout()
1704 b = QPushButton(_("Encrypt"))
1705 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1708 b = QPushButton(_("Decrypt"))
1709 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1712 b = QPushButton(_("Close"))
1713 b.clicked.connect(d.accept)
1716 layout.addLayout(hbox, 4, 1)
1720 def question(self, msg):
1721 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1723 def show_message(self, msg):
1724 QMessageBox.information(self, _('Message'), msg, _('OK'))
1726 def password_dialog(self ):
1729 d.setWindowTitle(_("Enter Password"))
1734 vbox = QVBoxLayout()
1735 msg = _('Please enter your password')
1736 vbox.addWidget(QLabel(msg))
1738 grid = QGridLayout()
1740 grid.addWidget(QLabel(_('Password')), 1, 0)
1741 grid.addWidget(pw, 1, 1)
1742 vbox.addLayout(grid)
1744 vbox.addLayout(ok_cancel_buttons(d))
1747 run_hook('password_dialog', pw, grid, 1)
1748 if not d.exec_(): return
1749 return unicode(pw.text())
1758 def tx_from_text(self, txt):
1759 "json or raw hexadecimal"
1762 tx = Transaction(txt)
1768 tx_dict = json.loads(str(txt))
1769 assert "hex" in tx_dict.keys()
1770 tx = Transaction(tx_dict["hex"])
1771 if tx_dict.has_key("input_info"):
1772 input_info = json.loads(tx_dict['input_info'])
1773 tx.add_input_info(input_info)
1776 traceback.print_exc(file=sys.stdout)
1779 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1783 def read_tx_from_file(self):
1784 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1788 with open(fileName, "r") as f:
1789 file_content = f.read()
1790 except (ValueError, IOError, os.error), reason:
1791 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1793 return self.tx_from_text(file_content)
1797 def sign_raw_transaction(self, tx, input_info, password):
1798 self.wallet.signrawtransaction(tx, input_info, [], password)
1800 def do_process_from_text(self):
1801 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1804 tx = self.tx_from_text(text)
1806 self.show_transaction(tx)
1808 def do_process_from_file(self):
1809 tx = self.read_tx_from_file()
1811 self.show_transaction(tx)
1813 def do_process_from_txid(self):
1814 from electrum import transaction
1815 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1817 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1819 tx = transaction.Transaction(r)
1821 self.show_transaction(tx)
1823 self.show_message("unknown transaction")
1825 def do_process_from_csvReader(self, csvReader):
1830 for position, row in enumerate(csvReader):
1832 if not is_valid(address):
1833 errors.append((position, address))
1835 amount = Decimal(row[1])
1836 amount = int(100000000*amount)
1837 outputs.append((address, amount))
1838 except (ValueError, IOError, os.error), reason:
1839 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1843 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1844 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1848 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1849 except Exception as e:
1850 self.show_message(str(e))
1853 self.show_transaction(tx)
1855 def do_process_from_csv_file(self):
1856 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1860 with open(fileName, "r") as f:
1861 csvReader = csv.reader(f)
1862 self.do_process_from_csvReader(csvReader)
1863 except (ValueError, IOError, os.error), reason:
1864 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1867 def do_process_from_csv_text(self):
1868 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1869 + _("Format: address, amount. One output per line"), _("Load CSV"))
1872 f = StringIO.StringIO(text)
1873 csvReader = csv.reader(f)
1874 self.do_process_from_csvReader(csvReader)
1879 def export_privkeys_dialog(self, password):
1880 if self.wallet.is_watching_only():
1881 self.show_message(_("This is a watching-only wallet"))
1885 d.setWindowTitle(_('Private keys'))
1886 d.setMinimumSize(850, 300)
1887 vbox = QVBoxLayout(d)
1889 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1890 _("Exposing a single private key can compromise your entire wallet!"),
1891 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1892 vbox.addWidget(QLabel(msg))
1898 defaultname = 'electrum-private-keys.csv'
1899 select_msg = _('Select file to export your private keys to')
1900 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1901 vbox.addLayout(hbox)
1903 h, b = ok_cancel_buttons2(d, _('Export'))
1908 addresses = self.wallet.addresses(True)
1910 def privkeys_thread():
1911 for addr in addresses:
1915 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1916 d.emit(SIGNAL('computing_privkeys'))
1917 d.emit(SIGNAL('show_privkeys'))
1919 def show_privkeys():
1920 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1924 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1925 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1926 threading.Thread(target=privkeys_thread).start()
1932 filename = filename_e.text()
1937 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1938 except (IOError, os.error), reason:
1939 export_error_label = _("Electrum was unable to produce a private key-export.")
1940 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1942 except Exception as e:
1943 self.show_message(str(e))
1946 self.show_message(_("Private keys exported."))
1949 def do_export_privkeys(self, fileName, pklist, is_csv):
1950 with open(fileName, "w+") as f:
1952 transaction = csv.writer(f)
1953 transaction.writerow(["address", "private_key"])
1954 for addr, pk in pklist.items():
1955 transaction.writerow(["%34s"%addr,pk])
1958 f.write(json.dumps(pklist, indent = 4))
1961 def do_import_labels(self):
1962 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1963 if not labelsFile: return
1965 f = open(labelsFile, 'r')
1968 for key, value in json.loads(data).items():
1969 self.wallet.set_label(key, value)
1970 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1971 except (IOError, os.error), reason:
1972 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1975 def do_export_labels(self):
1976 labels = self.wallet.labels
1978 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1980 with open(fileName, 'w+') as f:
1981 json.dump(labels, f)
1982 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1983 except (IOError, os.error), reason:
1984 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1987 def export_history_dialog(self):
1990 d.setWindowTitle(_('Export History'))
1991 d.setMinimumSize(400, 200)
1992 vbox = QVBoxLayout(d)
1994 defaultname = os.path.expanduser('~/electrum-history.csv')
1995 select_msg = _('Select file to export your wallet transactions to')
1997 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1998 vbox.addLayout(hbox)
2002 h, b = ok_cancel_buttons2(d, _('Export'))
2007 filename = filename_e.text()
2012 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2013 except (IOError, os.error), reason:
2014 export_error_label = _("Electrum was unable to produce a transaction export.")
2015 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2018 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2021 def do_export_history(self, wallet, fileName, is_csv):
2022 history = wallet.get_tx_history()
2024 for item in history:
2025 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2027 if timestamp is not None:
2029 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2030 except [RuntimeError, TypeError, NameError] as reason:
2031 time_string = "unknown"
2034 time_string = "unknown"
2036 time_string = "pending"
2038 if value is not None:
2039 value_string = format_satoshis(value, True)
2044 fee_string = format_satoshis(fee, True)
2049 label, is_default_label = wallet.get_label(tx_hash)
2050 label = label.encode('utf-8')
2054 balance_string = format_satoshis(balance, False)
2056 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2058 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2060 with open(fileName, "w+") as f:
2062 transaction = csv.writer(f)
2063 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2065 transaction.writerow(line)
2068 f.write(json.dumps(lines, indent = 4))
2071 def sweep_key_dialog(self):
2073 d.setWindowTitle(_('Sweep private keys'))
2074 d.setMinimumSize(600, 300)
2076 vbox = QVBoxLayout(d)
2077 vbox.addWidget(QLabel(_("Enter private keys")))
2079 keys_e = QTextEdit()
2080 keys_e.setTabChangesFocus(True)
2081 vbox.addWidget(keys_e)
2083 h, address_e = address_field(self.wallet.addresses())
2087 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2088 vbox.addLayout(hbox)
2089 button.setEnabled(False)
2092 addr = str(address_e.text())
2093 if bitcoin.is_address(addr):
2097 pk = str(keys_e.toPlainText()).strip()
2098 if Wallet.is_private_key(pk):
2101 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2102 keys_e.textChanged.connect(f)
2103 address_e.textChanged.connect(f)
2107 fee = self.wallet.fee
2108 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2109 self.show_transaction(tx)
2113 def do_import_privkey(self, password):
2114 if not self.wallet.imported_keys:
2115 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2116 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2117 + _('Are you sure you understand what you are doing?'), 3, 4)
2120 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2123 text = str(text).split()
2128 addr = self.wallet.import_key(key, password)
2129 except Exception as e:
2135 addrlist.append(addr)
2137 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2139 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2140 self.update_receive_tab()
2141 self.update_history_tab()
2144 def settings_dialog(self):
2146 d.setWindowTitle(_('Electrum Settings'))
2148 vbox = QVBoxLayout()
2149 grid = QGridLayout()
2150 grid.setColumnStretch(0,1)
2152 nz_label = QLabel(_('Display zeros') + ':')
2153 grid.addWidget(nz_label, 0, 0)
2154 nz_e = AmountEdit(None,True)
2155 nz_e.setText("%d"% self.num_zeros)
2156 grid.addWidget(nz_e, 0, 1)
2157 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2158 grid.addWidget(HelpButton(msg), 0, 2)
2159 if not self.config.is_modifiable('num_zeros'):
2160 for w in [nz_e, nz_label]: w.setEnabled(False)
2162 lang_label=QLabel(_('Language') + ':')
2163 grid.addWidget(lang_label, 1, 0)
2164 lang_combo = QComboBox()
2165 from electrum.i18n import languages
2166 lang_combo.addItems(languages.values())
2168 index = languages.keys().index(self.config.get("language",''))
2171 lang_combo.setCurrentIndex(index)
2172 grid.addWidget(lang_combo, 1, 1)
2173 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2174 if not self.config.is_modifiable('language'):
2175 for w in [lang_combo, lang_label]: w.setEnabled(False)
2178 fee_label = QLabel(_('Transaction fee') + ':')
2179 grid.addWidget(fee_label, 2, 0)
2180 fee_e = AmountEdit(self.base_unit)
2181 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2182 grid.addWidget(fee_e, 2, 1)
2183 msg = _('Fee per kilobyte of transaction.') + ' ' \
2184 + _('Recommended value') + ': ' + self.format_amount(20000)
2185 grid.addWidget(HelpButton(msg), 2, 2)
2186 if not self.config.is_modifiable('fee_per_kb'):
2187 for w in [fee_e, fee_label]: w.setEnabled(False)
2189 units = ['BTC', 'mBTC']
2190 unit_label = QLabel(_('Base unit') + ':')
2191 grid.addWidget(unit_label, 3, 0)
2192 unit_combo = QComboBox()
2193 unit_combo.addItems(units)
2194 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2195 grid.addWidget(unit_combo, 3, 1)
2196 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2197 + '\n1BTC=1000mBTC.\n' \
2198 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2200 usechange_cb = QCheckBox(_('Use change addresses'))
2201 usechange_cb.setChecked(self.wallet.use_change)
2202 grid.addWidget(usechange_cb, 4, 0)
2203 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2204 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2206 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2207 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2208 grid.addWidget(block_ex_label, 5, 0)
2209 block_ex_combo = QComboBox()
2210 block_ex_combo.addItems(block_explorers)
2211 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2212 grid.addWidget(block_ex_combo, 5, 1)
2213 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2215 show_tx = self.config.get('show_before_broadcast', False)
2216 showtx_cb = QCheckBox(_('Show before broadcast'))
2217 showtx_cb.setChecked(show_tx)
2218 grid.addWidget(showtx_cb, 6, 0)
2219 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2221 vbox.addLayout(grid)
2223 vbox.addLayout(ok_cancel_buttons(d))
2227 if not d.exec_(): return
2229 fee = unicode(fee_e.text())
2231 fee = self.read_amount(fee)
2233 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2236 self.wallet.set_fee(fee)
2238 nz = unicode(nz_e.text())
2243 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2246 if self.num_zeros != nz:
2248 self.config.set_key('num_zeros', nz, True)
2249 self.update_history_tab()
2250 self.update_receive_tab()
2252 usechange_result = usechange_cb.isChecked()
2253 if self.wallet.use_change != usechange_result:
2254 self.wallet.use_change = usechange_result
2255 self.wallet.storage.put('use_change', self.wallet.use_change)
2257 if showtx_cb.isChecked() != show_tx:
2258 self.config.set_key('show_before_broadcast', not show_tx)
2260 unit_result = units[unit_combo.currentIndex()]
2261 if self.base_unit() != unit_result:
2262 self.decimal_point = 8 if unit_result == 'BTC' else 5
2263 self.config.set_key('decimal_point', self.decimal_point, True)
2264 self.update_history_tab()
2265 self.update_status()
2267 need_restart = False
2269 lang_request = languages.keys()[lang_combo.currentIndex()]
2270 if lang_request != self.config.get('language'):
2271 self.config.set_key("language", lang_request, True)
2274 be_result = block_explorers[block_ex_combo.currentIndex()]
2275 self.config.set_key('block_explorer', be_result, True)
2277 run_hook('close_settings_dialog')
2280 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2283 def run_network_dialog(self):
2284 if not self.network:
2286 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2288 def closeEvent(self, event):
2290 self.config.set_key("is_maximized", self.isMaximized())
2291 if not self.isMaximized():
2293 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2294 self.save_column_widths()
2295 self.config.set_key("console-history", self.console.history[-50:], True)
2296 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2300 def plugins_dialog(self):
2301 from electrum.plugins import plugins
2304 d.setWindowTitle(_('Electrum Plugins'))
2307 vbox = QVBoxLayout(d)
2310 scroll = QScrollArea()
2311 scroll.setEnabled(True)
2312 scroll.setWidgetResizable(True)
2313 scroll.setMinimumSize(400,250)
2314 vbox.addWidget(scroll)
2318 w.setMinimumHeight(len(plugins)*35)
2320 grid = QGridLayout()
2321 grid.setColumnStretch(0,1)
2324 def do_toggle(cb, p, w):
2327 if w: w.setEnabled(r)
2329 def mk_toggle(cb, p, w):
2330 return lambda: do_toggle(cb,p,w)
2332 for i, p in enumerate(plugins):
2334 cb = QCheckBox(p.fullname())
2335 cb.setDisabled(not p.is_available())
2336 cb.setChecked(p.is_enabled())
2337 grid.addWidget(cb, i, 0)
2338 if p.requires_settings():
2339 w = p.settings_widget(self)
2340 w.setEnabled( p.is_enabled() )
2341 grid.addWidget(w, i, 1)
2344 cb.clicked.connect(mk_toggle(cb,p,w))
2345 grid.addWidget(HelpButton(p.description()), i, 2)
2347 print_msg(_("Error: cannot display plugin"), p)
2348 traceback.print_exc(file=sys.stdout)
2349 grid.setRowStretch(i+1,1)
2351 vbox.addLayout(close_button(d))
2356 def show_account_details(self, k):
2357 account = self.wallet.accounts[k]
2360 d.setWindowTitle(_('Account Details'))
2363 vbox = QVBoxLayout(d)
2364 name = self.wallet.get_account_name(k)
2365 label = QLabel('Name: ' + name)
2366 vbox.addWidget(label)
2368 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2370 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2372 vbox.addWidget(QLabel(_('Master Public Key:')))
2375 text.setReadOnly(True)
2376 text.setMaximumHeight(170)
2377 vbox.addWidget(text)
2379 mpk_text = '\n'.join( account.get_master_pubkeys() )
2380 text.setText(mpk_text)
2382 vbox.addLayout(close_button(d))