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()
199 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
200 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
201 self.password_menu.setEnabled(not self.wallet.is_watching_only())
202 self.seed_menu.setEnabled(self.wallet.has_seed())
203 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
205 self.update_lock_icon()
206 self.update_buttons_on_seed()
207 self.update_console()
209 run_hook('load_wallet', wallet)
212 def open_wallet(self):
213 wallet_folder = self.wallet.storage.path
214 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
218 storage = WalletStorage({'wallet_path': filename})
219 if not storage.file_exists:
220 self.show_message("file not found "+ filename)
223 self.wallet.stop_threads()
226 wallet = Wallet(storage)
227 wallet.start_threads(self.network)
229 self.load_wallet(wallet)
233 def backup_wallet(self):
235 path = self.wallet.storage.path
236 wallet_folder = os.path.dirname(path)
237 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
241 new_path = os.path.join(wallet_folder, filename)
244 shutil.copy2(path, new_path)
245 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
246 except (IOError, os.error), reason:
247 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
250 def new_wallet(self):
253 wallet_folder = os.path.dirname(self.wallet.storage.path)
254 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
257 filename = os.path.join(wallet_folder, filename)
259 storage = WalletStorage({'wallet_path': filename})
260 if storage.file_exists:
261 QMessageBox.critical(None, "Error", _("File exists"))
264 wizard = installwizard.InstallWizard(self.config, self.network, storage)
265 wallet = wizard.run('new')
267 self.load_wallet(wallet)
271 def init_menubar(self):
274 file_menu = menubar.addMenu(_("&File"))
275 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
276 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
277 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
278 file_menu.addAction(_("&Quit"), self.close)
280 wallet_menu = menubar.addMenu(_("&Wallet"))
281 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
282 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
284 wallet_menu.addSeparator()
286 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
287 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
288 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
290 wallet_menu.addSeparator()
291 labels_menu = wallet_menu.addMenu(_("&Labels"))
292 labels_menu.addAction(_("&Import"), self.do_import_labels)
293 labels_menu.addAction(_("&Export"), self.do_export_labels)
295 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
296 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
297 self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
298 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
300 wallet_menu.addAction(_("&Export History"), self.do_export_history)
302 tools_menu = menubar.addMenu(_("&Tools"))
304 # Settings / Preferences are all reserved keywords in OSX using this as work around
305 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
306 tools_menu.addAction(_("&Network"), self.run_network_dialog)
307 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
308 tools_menu.addSeparator()
309 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
310 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
311 tools_menu.addSeparator()
313 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
314 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
315 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
317 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
318 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
319 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
320 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
322 help_menu = menubar.addMenu(_("&Help"))
323 help_menu.addAction(_("&About"), self.show_about)
324 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
325 help_menu.addSeparator()
326 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
327 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
329 self.setMenuBar(menubar)
331 def show_about(self):
332 QMessageBox.about(self, "Electrum",
333 _("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."))
335 def show_report_bug(self):
336 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
337 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
340 def notify_transactions(self):
341 if not self.network or not self.network.is_connected():
344 print_error("Notifying GUI")
345 if len(self.network.pending_transactions_for_notifications) > 0:
346 # Combine the transactions if there are more then three
347 tx_amount = len(self.network.pending_transactions_for_notifications)
350 for tx in self.network.pending_transactions_for_notifications:
351 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
355 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
356 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
358 self.network.pending_transactions_for_notifications = []
360 for tx in self.network.pending_transactions_for_notifications:
362 self.network.pending_transactions_for_notifications.remove(tx)
363 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
365 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
367 def notify(self, message):
368 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
372 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
373 def getOpenFileName(self, title, filter = ""):
374 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
375 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
376 if fileName and directory != os.path.dirname(fileName):
377 self.config.set_key('io_dir', os.path.dirname(fileName), True)
380 def getSaveFileName(self, title, filename, filter = ""):
381 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
382 path = os.path.join( directory, filename )
383 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
384 if fileName and directory != os.path.dirname(fileName):
385 self.config.set_key('io_dir', os.path.dirname(fileName), True)
389 QMainWindow.close(self)
390 run_hook('close_main_window')
392 def connect_slots(self, sender):
393 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
394 self.previous_payto_e=''
396 def timer_actions(self):
397 if self.need_update.is_set():
399 self.need_update.clear()
400 run_hook('timer_actions')
402 def format_amount(self, x, is_diff=False, whitespaces=False):
403 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
405 def read_amount(self, x):
406 if x in['.', '']: return None
407 p = pow(10, self.decimal_point)
408 return int( p * Decimal(x) )
411 assert self.decimal_point in [5,8]
412 return "BTC" if self.decimal_point == 8 else "mBTC"
415 def update_status(self):
416 if self.network is None or not self.network.is_running():
418 icon = QIcon(":icons/status_disconnected.png")
420 elif self.network.is_connected():
421 if not self.wallet.up_to_date:
422 text = _("Synchronizing...")
423 icon = QIcon(":icons/status_waiting.png")
424 elif self.network.server_lag > 1:
425 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
426 icon = QIcon(":icons/status_lagging.png")
428 c, u = self.wallet.get_account_balance(self.current_account)
429 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
430 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
432 # append fiat balance and price from exchange rate plugin
434 run_hook('get_fiat_status_text', c+u, r)
439 self.tray.setToolTip(text)
440 icon = QIcon(":icons/status_connected.png")
442 text = _("Not connected")
443 icon = QIcon(":icons/status_disconnected.png")
445 self.balance_label.setText(text)
446 self.status_button.setIcon( icon )
449 def update_wallet(self):
451 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
452 self.update_history_tab()
453 self.update_receive_tab()
454 self.update_contacts_tab()
455 self.update_completions()
458 def create_history_tab(self):
459 self.history_list = l = MyTreeWidget(self)
461 for i,width in enumerate(self.column_widths['history']):
462 l.setColumnWidth(i, width)
463 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
464 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
465 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
467 l.customContextMenuRequested.connect(self.create_history_menu)
471 def create_history_menu(self, position):
472 self.history_list.selectedIndexes()
473 item = self.history_list.currentItem()
474 be = self.config.get('block_explorer', 'Blockchain.info')
475 if be == 'Blockchain.info':
476 block_explorer = 'https://blockchain.info/tx/'
477 elif be == 'Blockr.io':
478 block_explorer = 'https://blockr.io/tx/info/'
479 elif be == 'Insight.is':
480 block_explorer = 'http://live.insight.is/tx/'
482 tx_hash = str(item.data(0, Qt.UserRole).toString())
483 if not tx_hash: return
485 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
486 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
487 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
488 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
489 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
492 def show_transaction(self, tx):
493 import transaction_dialog
494 d = transaction_dialog.TxDialog(tx, self)
497 def tx_label_clicked(self, item, column):
498 if column==2 and item.isSelected():
500 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
501 self.history_list.editItem( item, column )
502 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
505 def tx_label_changed(self, item, column):
509 tx_hash = str(item.data(0, Qt.UserRole).toString())
510 tx = self.wallet.transactions.get(tx_hash)
511 text = unicode( item.text(2) )
512 self.wallet.set_label(tx_hash, text)
514 item.setForeground(2, QBrush(QColor('black')))
516 text = self.wallet.get_default_label(tx_hash)
517 item.setText(2, text)
518 item.setForeground(2, QBrush(QColor('gray')))
522 def edit_label(self, is_recv):
523 l = self.receive_list if is_recv else self.contacts_list
524 item = l.currentItem()
525 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
526 l.editItem( item, 1 )
527 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
531 def address_label_clicked(self, item, column, l, column_addr, column_label):
532 if column == column_label and item.isSelected():
533 is_editable = item.data(0, 32).toBool()
536 addr = unicode( item.text(column_addr) )
537 label = unicode( item.text(column_label) )
538 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
539 l.editItem( item, column )
540 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
543 def address_label_changed(self, item, column, l, column_addr, column_label):
544 if column == column_label:
545 addr = unicode( item.text(column_addr) )
546 text = unicode( item.text(column_label) )
547 is_editable = item.data(0, 32).toBool()
551 changed = self.wallet.set_label(addr, text)
553 self.update_history_tab()
554 self.update_completions()
556 self.current_item_changed(item)
558 run_hook('item_changed', item, column)
561 def current_item_changed(self, a):
562 run_hook('current_item_changed', a)
566 def update_history_tab(self):
568 self.history_list.clear()
569 for item in self.wallet.get_tx_history(self.current_account):
570 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
571 time_str = _("unknown")
574 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
576 time_str = _("error")
579 time_str = 'unverified'
580 icon = QIcon(":icons/unconfirmed.png")
583 icon = QIcon(":icons/unconfirmed.png")
585 icon = QIcon(":icons/clock%d.png"%conf)
587 icon = QIcon(":icons/confirmed.png")
589 if value is not None:
590 v_str = self.format_amount(value, True, whitespaces=True)
594 balance_str = self.format_amount(balance, whitespaces=True)
597 label, is_default_label = self.wallet.get_label(tx_hash)
599 label = _('Pruned transaction outputs')
600 is_default_label = False
602 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
603 item.setFont(2, QFont(MONOSPACE_FONT))
604 item.setFont(3, QFont(MONOSPACE_FONT))
605 item.setFont(4, QFont(MONOSPACE_FONT))
607 item.setForeground(3, QBrush(QColor("#BC1E1E")))
609 item.setData(0, Qt.UserRole, tx_hash)
610 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
612 item.setForeground(2, QBrush(QColor('grey')))
614 item.setIcon(0, icon)
615 self.history_list.insertTopLevelItem(0,item)
618 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
619 run_hook('history_tab_update')
622 def create_send_tab(self):
627 grid.setColumnMinimumWidth(3,300)
628 grid.setColumnStretch(5,1)
631 self.payto_e = QLineEdit()
632 grid.addWidget(QLabel(_('Pay to')), 1, 0)
633 grid.addWidget(self.payto_e, 1, 1, 1, 3)
635 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)
637 completer = QCompleter()
638 completer.setCaseSensitivity(False)
639 self.payto_e.setCompleter(completer)
640 completer.setModel(self.completions)
642 self.message_e = QLineEdit()
643 grid.addWidget(QLabel(_('Description')), 2, 0)
644 grid.addWidget(self.message_e, 2, 1, 1, 3)
645 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)
647 self.from_label = QLabel(_('From'))
648 grid.addWidget(self.from_label, 3, 0)
649 self.from_list = QTreeWidget(self)
650 self.from_list.setColumnCount(2)
651 self.from_list.setColumnWidth(0, 350)
652 self.from_list.setColumnWidth(1, 50)
653 self.from_list.setHeaderHidden (True)
654 self.from_list.setMaximumHeight(80)
655 grid.addWidget(self.from_list, 3, 1, 1, 3)
656 self.set_pay_from([])
658 self.amount_e = AmountEdit(self.base_unit)
659 grid.addWidget(QLabel(_('Amount')), 4, 0)
660 grid.addWidget(self.amount_e, 4, 1, 1, 2)
661 grid.addWidget(HelpButton(
662 _('Amount to be sent.') + '\n\n' \
663 + _('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.') \
664 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
666 self.fee_e = AmountEdit(self.base_unit)
667 grid.addWidget(QLabel(_('Fee')), 5, 0)
668 grid.addWidget(self.fee_e, 5, 1, 1, 2)
669 grid.addWidget(HelpButton(
670 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
671 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
672 + _('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)
674 run_hook('exchange_rate_button', grid)
676 self.send_button = EnterButton(_("Send"), self.do_send)
677 grid.addWidget(self.send_button, 6, 1)
679 b = EnterButton(_("Clear"),self.do_clear)
680 grid.addWidget(b, 6, 2)
682 self.payto_sig = QLabel('')
683 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
685 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
686 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
695 def entry_changed( is_fee ):
696 self.funds_error = False
698 if self.amount_e.is_shortcut:
699 self.amount_e.is_shortcut = False
700 sendable = self.get_sendable_balance()
701 # there is only one output because we are completely spending inputs
702 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
703 fee = self.wallet.estimated_fee(inputs, 1)
705 self.amount_e.setText( self.format_amount(amount) )
706 self.fee_e.setText( self.format_amount( fee ) )
709 amount = self.read_amount(str(self.amount_e.text()))
710 fee = self.read_amount(str(self.fee_e.text()))
712 if not is_fee: fee = None
715 # assume that there will be 2 outputs (one for change)
716 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
718 self.fee_e.setText( self.format_amount( fee ) )
721 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
725 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
726 self.funds_error = True
727 text = _( "Not enough funds" )
728 c, u = self.wallet.get_frozen_balance()
729 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
731 self.statusBar().showMessage(text)
732 self.amount_e.setPalette(palette)
733 self.fee_e.setPalette(palette)
735 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
736 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
738 run_hook('create_send_tab', grid)
742 def set_pay_from(self, l):
744 self.from_list.clear()
745 self.from_label.setHidden(len(self.pay_from) == 0)
746 self.from_list.setHidden(len(self.pay_from) == 0)
747 for addr in self.pay_from:
748 c, u = self.wallet.get_addr_balance(addr)
749 balance = self.format_amount(c + u)
750 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
753 def update_completions(self):
755 for addr,label in self.wallet.labels.items():
756 if addr in self.wallet.addressbook:
757 l.append( label + ' <' + addr + '>')
759 run_hook('update_completions', l)
760 self.completions.setStringList(l)
764 return lambda s, *args: s.do_protect(func, args)
769 label = unicode( self.message_e.text() )
770 r = unicode( self.payto_e.text() )
773 # label or alias, with address in brackets
774 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
775 to_address = m.group(2) if m else r
777 if not is_valid(to_address):
778 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
782 amount = self.read_amount(unicode( self.amount_e.text()))
784 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
787 fee = self.read_amount(unicode( self.fee_e.text()))
789 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
792 confirm_amount = self.config.get('confirm_amount', 100000000)
793 if amount >= confirm_amount:
794 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
797 confirm_fee = self.config.get('confirm_fee', 100000)
798 if fee >= confirm_fee:
799 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()}):
802 self.send_tx(to_address, amount, fee, label)
805 def waiting_dialog(self, message):
807 d.setWindowTitle('Please wait')
809 vbox = QVBoxLayout(d)
816 def send_tx(self, to_address, amount, fee, label, password):
818 # first, create an unsigned tx
819 domain = self.get_payment_sources()
820 outputs = [(to_address, amount)]
822 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
824 except Exception as e:
825 traceback.print_exc(file=sys.stdout)
826 self.show_message(str(e))
829 # call hook to see if plugin needs gui interaction
830 run_hook('send_tx', tx)
836 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
837 self.wallet.sign_transaction(tx, keypairs, password)
838 self.signed_tx_data = (tx, fee, label)
839 self.emit(SIGNAL('send_tx2'))
840 self.tx_wait_dialog = self.waiting_dialog('Signing..')
841 threading.Thread(target=sign_thread).start()
843 # add recipient to addressbook
844 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
845 self.wallet.addressbook.append(to_address)
849 tx, fee, label = self.signed_tx_data
850 self.tx_wait_dialog.accept()
853 self.show_message(tx.error)
856 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
857 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
861 self.wallet.set_label(tx.hash(), label)
863 if not tx.is_complete() or self.config.get('show_before_broadcast'):
864 self.show_transaction(tx)
868 def broadcast_thread():
869 self.tx_broadcast_result = self.wallet.sendtx(tx)
870 self.emit(SIGNAL('send_tx3'))
871 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
872 threading.Thread(target=broadcast_thread).start()
876 self.tx_broadcast_dialog.accept()
877 status, msg = self.tx_broadcast_result
879 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
881 self.update_contacts_tab()
883 QMessageBox.warning(self, _('Error'), msg, _('OK'))
891 def set_send(self, address, amount, label, message):
893 if label and self.wallet.labels.get(address) != label:
894 if self.question('Give label "%s" to address %s ?'%(label,address)):
895 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
896 self.wallet.addressbook.append(address)
897 self.wallet.set_label(address, label)
899 self.tabs.setCurrentIndex(1)
900 label = self.wallet.labels.get(address)
901 m_addr = label + ' <'+ address +'>' if label else address
902 self.payto_e.setText(m_addr)
904 self.message_e.setText(message)
906 self.amount_e.setText(amount)
910 self.payto_sig.setVisible(False)
911 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
913 self.set_frozen(e,False)
915 self.set_pay_from([])
918 def set_frozen(self,entry,frozen):
920 entry.setReadOnly(True)
921 entry.setFrame(False)
923 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
924 entry.setPalette(palette)
926 entry.setReadOnly(False)
929 palette.setColor(entry.backgroundRole(), QColor('white'))
930 entry.setPalette(palette)
933 def set_addrs_frozen(self,addrs,freeze):
935 if not addr: continue
936 if addr in self.wallet.frozen_addresses and not freeze:
937 self.wallet.unfreeze(addr)
938 elif addr not in self.wallet.frozen_addresses and freeze:
939 self.wallet.freeze(addr)
940 self.update_receive_tab()
944 def create_list_tab(self, headers):
945 "generic tab creation method"
946 l = MyTreeWidget(self)
947 l.setColumnCount( len(headers) )
948 l.setHeaderLabels( headers )
958 vbox.addWidget(buttons)
963 buttons.setLayout(hbox)
968 def create_receive_tab(self):
969 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
970 l.setContextMenuPolicy(Qt.CustomContextMenu)
971 l.customContextMenuRequested.connect(self.create_receive_menu)
972 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
973 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
974 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
975 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
976 self.receive_list = l
977 self.receive_buttons_hbox = hbox
984 def save_column_widths(self):
985 self.column_widths["receive"] = []
986 for i in range(self.receive_list.columnCount() -1):
987 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
989 self.column_widths["history"] = []
990 for i in range(self.history_list.columnCount() - 1):
991 self.column_widths["history"].append(self.history_list.columnWidth(i))
993 self.column_widths["contacts"] = []
994 for i in range(self.contacts_list.columnCount() - 1):
995 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
997 self.config.set_key("column_widths_2", self.column_widths, True)
1000 def create_contacts_tab(self):
1001 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1002 l.setContextMenuPolicy(Qt.CustomContextMenu)
1003 l.customContextMenuRequested.connect(self.create_contact_menu)
1004 for i,width in enumerate(self.column_widths['contacts']):
1005 l.setColumnWidth(i, width)
1007 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1008 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1009 self.contacts_list = l
1010 self.contacts_buttons_hbox = hbox
1015 def delete_imported_key(self, addr):
1016 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1017 self.wallet.delete_imported_key(addr)
1018 self.update_receive_tab()
1019 self.update_history_tab()
1021 def edit_account_label(self, k):
1022 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1024 label = unicode(text)
1025 self.wallet.set_label(k,label)
1026 self.update_receive_tab()
1028 def account_set_expanded(self, item, k, b):
1030 self.accounts_expanded[k] = b
1032 def create_account_menu(self, position, k, item):
1034 if item.isExpanded():
1035 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1037 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1038 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1039 if self.wallet.seed_version > 4:
1040 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1041 if self.wallet.account_is_pending(k):
1042 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1043 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1045 def delete_pending_account(self, k):
1046 self.wallet.delete_pending_account(k)
1047 self.update_receive_tab()
1049 def create_receive_menu(self, position):
1050 # fixme: this function apparently has a side effect.
1051 # if it is not called the menu pops up several times
1052 #self.receive_list.selectedIndexes()
1054 selected = self.receive_list.selectedItems()
1055 multi_select = len(selected) > 1
1056 addrs = [unicode(item.text(0)) for item in selected]
1057 if not multi_select:
1058 item = self.receive_list.itemAt(position)
1062 if not is_valid(addr):
1063 k = str(item.data(0,32).toString())
1065 self.create_account_menu(position, k, item)
1067 item.setExpanded(not item.isExpanded())
1071 if not multi_select:
1072 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1073 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1074 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1075 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1076 if not self.wallet.is_watching_only():
1077 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1078 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1079 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1080 if addr in self.wallet.imported_keys:
1081 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1083 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1084 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1085 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1086 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1088 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1089 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1091 run_hook('receive_menu', menu, addrs)
1092 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1095 def get_sendable_balance(self):
1096 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1099 def get_payment_sources(self):
1101 return self.pay_from
1103 return self.wallet.get_account_addresses(self.current_account)
1106 def send_from_addresses(self, addrs):
1107 self.set_pay_from( addrs )
1108 self.tabs.setCurrentIndex(1)
1111 def payto(self, addr):
1113 label = self.wallet.labels.get(addr)
1114 m_addr = label + ' <' + addr + '>' if label else addr
1115 self.tabs.setCurrentIndex(1)
1116 self.payto_e.setText(m_addr)
1117 self.amount_e.setFocus()
1120 def delete_contact(self, x):
1121 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1122 self.wallet.delete_contact(x)
1123 self.wallet.set_label(x, None)
1124 self.update_history_tab()
1125 self.update_contacts_tab()
1126 self.update_completions()
1129 def create_contact_menu(self, position):
1130 item = self.contacts_list.itemAt(position)
1133 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1135 addr = unicode(item.text(0))
1136 label = unicode(item.text(1))
1137 is_editable = item.data(0,32).toBool()
1138 payto_addr = item.data(0,33).toString()
1139 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1140 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1141 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1143 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1144 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1146 run_hook('create_contact_menu', menu, item)
1147 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1150 def update_receive_item(self, item):
1151 item.setFont(0, QFont(MONOSPACE_FONT))
1152 address = str(item.data(0,0).toString())
1153 label = self.wallet.labels.get(address,'')
1154 item.setData(1,0,label)
1155 item.setData(0,32, True) # is editable
1157 run_hook('update_receive_item', address, item)
1159 if not self.wallet.is_mine(address): return
1161 c, u = self.wallet.get_addr_balance(address)
1162 balance = self.format_amount(c + u)
1163 item.setData(2,0,balance)
1165 if address in self.wallet.frozen_addresses:
1166 item.setBackgroundColor(0, QColor('lightblue'))
1169 def update_receive_tab(self):
1170 l = self.receive_list
1173 l.setColumnHidden(2, False)
1174 l.setColumnHidden(3, False)
1175 for i,width in enumerate(self.column_widths['receive']):
1176 l.setColumnWidth(i, width)
1178 if self.current_account is None:
1179 account_items = sorted(self.wallet.accounts.items())
1180 elif self.current_account != -1:
1181 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1185 pending_accounts = self.wallet.get_pending_accounts()
1187 for k, account in account_items:
1189 if len(account_items) + len(pending_accounts) > 1:
1190 name = self.wallet.get_account_name(k)
1191 c,u = self.wallet.get_account_balance(k)
1192 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1193 l.addTopLevelItem(account_item)
1194 account_item.setExpanded(self.accounts_expanded.get(k, True))
1195 account_item.setData(0, 32, k)
1199 for is_change in ([0,1]):
1200 name = _("Receiving") if not is_change else _("Change")
1201 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1203 account_item.addChild(seq_item)
1205 l.addTopLevelItem(seq_item)
1207 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1209 if not is_change: seq_item.setExpanded(True)
1214 for address in account.get_addresses(is_change):
1215 h = self.wallet.history.get(address,[])
1219 if gap > self.wallet.gap_limit:
1224 c, u = self.wallet.get_addr_balance(address)
1225 num_tx = '*' if h == ['*'] else "%d"%len(h)
1226 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1227 self.update_receive_item(item)
1229 item.setBackgroundColor(1, QColor('red'))
1230 if len(h) > 0 and c == -u:
1232 seq_item.insertChild(0,used_item)
1234 used_item.addChild(item)
1236 seq_item.addChild(item)
1239 for k, addr in pending_accounts:
1240 name = self.wallet.labels.get(k,'')
1241 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1242 self.update_receive_item(item)
1243 l.addTopLevelItem(account_item)
1244 account_item.setExpanded(True)
1245 account_item.setData(0, 32, k)
1246 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1247 account_item.addChild(item)
1248 self.update_receive_item(item)
1251 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1252 c,u = self.wallet.get_imported_balance()
1253 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1254 l.addTopLevelItem(account_item)
1255 account_item.setExpanded(True)
1256 for address in self.wallet.imported_keys.keys():
1257 item = QTreeWidgetItem( [ address, '', '', ''] )
1258 self.update_receive_item(item)
1259 account_item.addChild(item)
1262 # we use column 1 because column 0 may be hidden
1263 l.setCurrentItem(l.topLevelItem(0),1)
1266 def update_contacts_tab(self):
1267 l = self.contacts_list
1270 for address in self.wallet.addressbook:
1271 label = self.wallet.labels.get(address,'')
1272 n = self.wallet.get_num_tx(address)
1273 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1274 item.setFont(0, QFont(MONOSPACE_FONT))
1275 # 32 = label can be edited (bool)
1276 item.setData(0,32, True)
1278 item.setData(0,33, address)
1279 l.addTopLevelItem(item)
1281 run_hook('update_contacts_tab', l)
1282 l.setCurrentItem(l.topLevelItem(0))
1286 def create_console_tab(self):
1287 from console import Console
1288 self.console = console = Console()
1292 def update_console(self):
1293 console = self.console
1294 console.history = self.config.get("console-history",[])
1295 console.history_index = len(console.history)
1297 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1298 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1300 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1302 def mkfunc(f, method):
1303 return lambda *args: apply( f, (method, args, self.password_dialog ))
1305 if m[0]=='_' or m in ['network','wallet']: continue
1306 methods[m] = mkfunc(c._run, m)
1308 console.updateNamespace(methods)
1311 def change_account(self,s):
1312 if s == _("All accounts"):
1313 self.current_account = None
1315 accounts = self.wallet.get_account_names()
1316 for k, v in accounts.items():
1318 self.current_account = k
1319 self.update_history_tab()
1320 self.update_status()
1321 self.update_receive_tab()
1323 def create_status_bar(self):
1326 sb.setFixedHeight(35)
1327 qtVersion = qVersion()
1329 self.balance_label = QLabel("")
1330 sb.addWidget(self.balance_label)
1332 from version_getter import UpdateLabel
1333 self.updatelabel = UpdateLabel(self.config, sb)
1335 self.account_selector = QComboBox()
1336 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1337 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1338 sb.addPermanentWidget(self.account_selector)
1340 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1341 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1343 self.lock_icon = QIcon()
1344 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1345 sb.addPermanentWidget( self.password_button )
1347 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1348 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1349 sb.addPermanentWidget( self.seed_button )
1350 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1351 sb.addPermanentWidget( self.status_button )
1353 run_hook('create_status_bar', (sb,))
1355 self.setStatusBar(sb)
1358 def update_lock_icon(self):
1359 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1360 self.password_button.setIcon( icon )
1363 def update_buttons_on_seed(self):
1364 if self.wallet.has_seed():
1365 self.seed_button.show()
1367 self.seed_button.hide()
1369 if not self.wallet.is_watching_only():
1370 self.password_button.show()
1371 self.send_button.setText(_("Send"))
1373 self.password_button.hide()
1374 self.send_button.setText(_("Create unsigned transaction"))
1377 def change_password_dialog(self):
1378 from password_dialog import PasswordDialog
1379 d = PasswordDialog(self.wallet, self)
1381 self.update_lock_icon()
1384 def new_contact_dialog(self):
1387 d.setWindowTitle(_("New Contact"))
1388 vbox = QVBoxLayout(d)
1389 vbox.addWidget(QLabel(_('New Contact')+':'))
1391 grid = QGridLayout()
1394 grid.addWidget(QLabel(_("Address")), 1, 0)
1395 grid.addWidget(line1, 1, 1)
1396 grid.addWidget(QLabel(_("Name")), 2, 0)
1397 grid.addWidget(line2, 2, 1)
1399 vbox.addLayout(grid)
1400 vbox.addLayout(ok_cancel_buttons(d))
1405 address = str(line1.text())
1406 label = unicode(line2.text())
1408 if not is_valid(address):
1409 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1412 self.wallet.add_contact(address)
1414 self.wallet.set_label(address, label)
1416 self.update_contacts_tab()
1417 self.update_history_tab()
1418 self.update_completions()
1419 self.tabs.setCurrentIndex(3)
1423 def new_account_dialog(self, password):
1425 dialog = QDialog(self)
1427 dialog.setWindowTitle(_("New Account"))
1429 vbox = QVBoxLayout()
1430 vbox.addWidget(QLabel(_('Account name')+':'))
1433 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1434 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1439 vbox.addLayout(ok_cancel_buttons(dialog))
1440 dialog.setLayout(vbox)
1444 name = str(e.text())
1447 self.wallet.create_pending_account(name, password)
1448 self.update_receive_tab()
1449 self.tabs.setCurrentIndex(2)
1454 def show_master_public_keys(self):
1456 dialog = QDialog(self)
1458 dialog.setWindowTitle(_("Master Public Keys"))
1460 main_layout = QGridLayout()
1461 mpk_dict = self.wallet.get_master_public_keys()
1463 for key, value in mpk_dict.items():
1464 main_layout.addWidget(QLabel(key), i, 0)
1465 mpk_text = QTextEdit()
1466 mpk_text.setReadOnly(True)
1467 mpk_text.setMaximumHeight(170)
1468 mpk_text.setText(value)
1469 main_layout.addWidget(mpk_text, i + 1, 0)
1472 vbox = QVBoxLayout()
1473 vbox.addLayout(main_layout)
1474 vbox.addLayout(close_button(dialog))
1476 dialog.setLayout(vbox)
1481 def show_seed_dialog(self, password):
1482 if not self.wallet.has_seed():
1483 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1487 mnemonic = self.wallet.get_mnemonic(password)
1489 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1491 from seed_dialog import SeedDialog
1492 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1497 def show_qrcode(self, data, title = _("QR code")):
1501 d.setWindowTitle(title)
1502 d.setMinimumSize(270, 300)
1503 vbox = QVBoxLayout()
1504 qrw = QRCodeWidget(data)
1505 vbox.addWidget(qrw, 1)
1506 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1507 hbox = QHBoxLayout()
1510 filename = os.path.join(self.config.path, "qrcode.bmp")
1513 bmp.save_qrcode(qrw.qr, filename)
1514 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1516 def copy_to_clipboard():
1517 bmp.save_qrcode(qrw.qr, filename)
1518 self.app.clipboard().setImage(QImage(filename))
1519 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1521 b = QPushButton(_("Copy"))
1523 b.clicked.connect(copy_to_clipboard)
1525 b = QPushButton(_("Save"))
1527 b.clicked.connect(print_qr)
1529 b = QPushButton(_("Close"))
1531 b.clicked.connect(d.accept)
1534 vbox.addLayout(hbox)
1539 def do_protect(self, func, args):
1540 if self.wallet.use_encryption:
1541 password = self.password_dialog()
1547 if args != (False,):
1548 args = (self,) + args + (password,)
1550 args = (self,password)
1554 def show_public_keys(self, address):
1555 if not address: return
1557 pubkey_list = self.wallet.get_public_keys(address)
1558 except Exception as e:
1559 traceback.print_exc(file=sys.stdout)
1560 self.show_message(str(e))
1564 d.setMinimumSize(600, 200)
1566 vbox = QVBoxLayout()
1567 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1568 vbox.addWidget( QLabel(_("Public key") + ':'))
1570 keys.setReadOnly(True)
1571 keys.setText('\n'.join(pubkey_list))
1572 vbox.addWidget(keys)
1573 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1574 vbox.addLayout(close_button(d))
1579 def show_private_key(self, address, password):
1580 if not address: return
1582 pk_list = self.wallet.get_private_key(address, password)
1583 except Exception as e:
1584 traceback.print_exc(file=sys.stdout)
1585 self.show_message(str(e))
1589 d.setMinimumSize(600, 200)
1591 vbox = QVBoxLayout()
1592 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1593 vbox.addWidget( QLabel(_("Private key") + ':'))
1595 keys.setReadOnly(True)
1596 keys.setText('\n'.join(pk_list))
1597 vbox.addWidget(keys)
1598 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1599 vbox.addLayout(close_button(d))
1605 def do_sign(self, address, message, signature, password):
1606 message = unicode(message.toPlainText())
1607 message = message.encode('utf-8')
1609 sig = self.wallet.sign_message(str(address.text()), message, password)
1610 signature.setText(sig)
1611 except Exception as e:
1612 self.show_message(str(e))
1614 def do_verify(self, address, message, signature):
1615 message = unicode(message.toPlainText())
1616 message = message.encode('utf-8')
1617 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1618 self.show_message(_("Signature verified"))
1620 self.show_message(_("Error: wrong signature"))
1623 def sign_verify_message(self, address=''):
1626 d.setWindowTitle(_('Sign/verify Message'))
1627 d.setMinimumSize(410, 290)
1629 layout = QGridLayout(d)
1631 message_e = QTextEdit()
1632 layout.addWidget(QLabel(_('Message')), 1, 0)
1633 layout.addWidget(message_e, 1, 1)
1634 layout.setRowStretch(2,3)
1636 address_e = QLineEdit()
1637 address_e.setText(address)
1638 layout.addWidget(QLabel(_('Address')), 2, 0)
1639 layout.addWidget(address_e, 2, 1)
1641 signature_e = QTextEdit()
1642 layout.addWidget(QLabel(_('Signature')), 3, 0)
1643 layout.addWidget(signature_e, 3, 1)
1644 layout.setRowStretch(3,1)
1646 hbox = QHBoxLayout()
1648 b = QPushButton(_("Sign"))
1649 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1652 b = QPushButton(_("Verify"))
1653 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1656 b = QPushButton(_("Close"))
1657 b.clicked.connect(d.accept)
1659 layout.addLayout(hbox, 4, 1)
1664 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1666 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1667 message_e.setText(decrypted)
1668 except Exception as e:
1669 self.show_message(str(e))
1672 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1673 message = unicode(message_e.toPlainText())
1674 message = message.encode('utf-8')
1676 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1677 encrypted_e.setText(encrypted)
1678 except Exception as e:
1679 self.show_message(str(e))
1683 def encrypt_message(self, address = ''):
1686 d.setWindowTitle(_('Encrypt/decrypt Message'))
1687 d.setMinimumSize(610, 490)
1689 layout = QGridLayout(d)
1691 message_e = QTextEdit()
1692 layout.addWidget(QLabel(_('Message')), 1, 0)
1693 layout.addWidget(message_e, 1, 1)
1694 layout.setRowStretch(2,3)
1696 pubkey_e = QLineEdit()
1698 pubkey = self.wallet.getpubkeys(address)[0]
1699 pubkey_e.setText(pubkey)
1700 layout.addWidget(QLabel(_('Public key')), 2, 0)
1701 layout.addWidget(pubkey_e, 2, 1)
1703 encrypted_e = QTextEdit()
1704 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1705 layout.addWidget(encrypted_e, 3, 1)
1706 layout.setRowStretch(3,1)
1708 hbox = QHBoxLayout()
1709 b = QPushButton(_("Encrypt"))
1710 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1713 b = QPushButton(_("Decrypt"))
1714 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1717 b = QPushButton(_("Close"))
1718 b.clicked.connect(d.accept)
1721 layout.addLayout(hbox, 4, 1)
1725 def question(self, msg):
1726 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1728 def show_message(self, msg):
1729 QMessageBox.information(self, _('Message'), msg, _('OK'))
1731 def password_dialog(self ):
1734 d.setWindowTitle(_("Enter Password"))
1739 vbox = QVBoxLayout()
1740 msg = _('Please enter your password')
1741 vbox.addWidget(QLabel(msg))
1743 grid = QGridLayout()
1745 grid.addWidget(QLabel(_('Password')), 1, 0)
1746 grid.addWidget(pw, 1, 1)
1747 vbox.addLayout(grid)
1749 vbox.addLayout(ok_cancel_buttons(d))
1752 run_hook('password_dialog', pw, grid, 1)
1753 if not d.exec_(): return
1754 return unicode(pw.text())
1763 def tx_from_text(self, txt):
1764 "json or raw hexadecimal"
1767 tx = Transaction(txt)
1773 tx_dict = json.loads(str(txt))
1774 assert "hex" in tx_dict.keys()
1775 assert "complete" in tx_dict.keys()
1776 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1777 if not tx_dict["complete"]:
1778 assert "input_info" in tx_dict.keys()
1779 input_info = json.loads(tx_dict['input_info'])
1780 tx.add_input_info(input_info)
1785 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1789 def read_tx_from_file(self):
1790 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1794 with open(fileName, "r") as f:
1795 file_content = f.read()
1796 except (ValueError, IOError, os.error), reason:
1797 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1799 return self.tx_from_text(file_content)
1803 def sign_raw_transaction(self, tx, input_info, password):
1804 self.wallet.signrawtransaction(tx, input_info, [], password)
1806 def do_process_from_text(self):
1807 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1810 tx = self.tx_from_text(text)
1812 self.show_transaction(tx)
1814 def do_process_from_file(self):
1815 tx = self.read_tx_from_file()
1817 self.show_transaction(tx)
1819 def do_process_from_txid(self):
1820 from electrum import transaction
1821 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1823 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1825 tx = transaction.Transaction(r)
1827 self.show_transaction(tx)
1829 self.show_message("unknown transaction")
1831 def do_process_from_csvReader(self, csvReader):
1836 for position, row in enumerate(csvReader):
1838 if not is_valid(address):
1839 errors.append((position, address))
1841 amount = Decimal(row[1])
1842 amount = int(100000000*amount)
1843 outputs.append((address, amount))
1844 except (ValueError, IOError, os.error), reason:
1845 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1849 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1850 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1854 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1855 except Exception as e:
1856 self.show_message(str(e))
1859 self.show_transaction(tx)
1861 def do_process_from_csv_file(self):
1862 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1866 with open(fileName, "r") as f:
1867 csvReader = csv.reader(f)
1868 self.do_process_from_csvReader(csvReader)
1869 except (ValueError, IOError, os.error), reason:
1870 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1873 def do_process_from_csv_text(self):
1874 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1875 + _("Format: address, amount. One output per line"), _("Load CSV"))
1878 f = StringIO.StringIO(text)
1879 csvReader = csv.reader(f)
1880 self.do_process_from_csvReader(csvReader)
1885 def export_privkeys_dialog(self, password):
1886 if self.wallet.is_watching_only():
1887 self.show_message(_("This is a watching-only wallet"))
1891 d.setWindowTitle(_('Private keys'))
1892 d.setMinimumSize(850, 300)
1893 vbox = QVBoxLayout(d)
1895 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1896 _("Exposing a single private key can compromise your entire wallet!"),
1897 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1898 vbox.addWidget(QLabel(msg))
1904 hbox = QHBoxLayout()
1905 vbox.addLayout(hbox)
1907 defaultname = 'electrum-private-keys.csv'
1908 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
1909 path = os.path.join( directory, defaultname )
1910 filename_e = QLineEdit()
1911 filename_e.setText(path)
1913 select_export = _('Select file to export your private keys to')
1914 p = self.getSaveFileName(select_export, defaultname, "*.csv")
1916 filename_e.setText(p)
1918 button = QPushButton(_('File'))
1919 button.clicked.connect(func)
1920 hbox.addWidget(button)
1921 hbox.addWidget(filename_e)
1923 h, b = ok_cancel_buttons2(d, _('Export'))
1928 addresses = self.wallet.addresses(True)
1929 def privkeys_thread():
1931 for addr in addresses:
1932 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1933 d.emit(SIGNAL('computing_privkeys'))
1934 d.emit(SIGNAL('show_privkeys'))
1936 def show_privkeys():
1937 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1941 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1942 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1943 threading.Thread(target=privkeys_thread).start()
1948 filename = filename_e.text()
1951 self.do_export_privkeys(filename, private_keys)
1954 def do_export_privkeys(self, fileName, pklist):
1956 with open(fileName, "w+") as csvfile:
1957 transaction = csv.writer(csvfile)
1958 transaction.writerow(["address", "private_key"])
1959 for addr, pk in pklist.items():
1960 transaction.writerow(["%34s"%addr,pk])
1961 except (IOError, os.error), reason:
1962 export_error_label = _("Electrum was unable to produce a private key-export.")
1963 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1965 except Exception as e:
1966 self.show_message(str(e))
1969 self.show_message(_("Private keys exported."))
1972 def do_import_labels(self):
1973 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1974 if not labelsFile: return
1976 f = open(labelsFile, 'r')
1979 for key, value in json.loads(data).items():
1980 self.wallet.set_label(key, value)
1981 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1982 except (IOError, os.error), reason:
1983 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1986 def do_export_labels(self):
1987 labels = self.wallet.labels
1989 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1991 with open(fileName, 'w+') as f:
1992 json.dump(labels, f)
1993 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1994 except (IOError, os.error), reason:
1995 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1998 def do_export_history(self):
1999 wallet = self.wallet
2000 select_export = _('Select file to export your wallet transactions to')
2001 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
2006 with open(fileName, "w+") as csvfile:
2007 transaction = csv.writer(csvfile)
2008 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2009 for item in wallet.get_tx_history():
2010 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2012 if timestamp is not None:
2014 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2015 except [RuntimeError, TypeError, NameError] as reason:
2016 time_string = "unknown"
2019 time_string = "unknown"
2021 time_string = "pending"
2023 if value is not None:
2024 value_string = format_satoshis(value, True)
2029 fee_string = format_satoshis(fee, True)
2034 label, is_default_label = wallet.get_label(tx_hash)
2035 label = label.encode('utf-8')
2039 balance_string = format_satoshis(balance, False)
2040 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2041 QMessageBox.information(None,_("CSV Export created"), _("Your CSV export has been successfully created."))
2043 except (IOError, os.error), reason:
2044 export_error_label = _("Electrum was unable to produce a transaction export.")
2045 QMessageBox.critical(None,_("Unable to create csv"), export_error_label + "\n" + str(reason))
2048 def sweep_key_dialog(self):
2050 d.setWindowTitle(_('Sweep private keys'))
2052 vbox = QVBoxLayout(d)
2053 vbox.addWidget(QLabel(_("Enter private keys")))
2055 keys_e = QTextEdit()
2056 keys_e.setTabChangesFocus(True)
2057 vbox.addWidget(keys_e)
2059 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2060 vbox.addLayout(hbox)
2061 button.setEnabled(False)
2063 keys_e.textChanged.connect(lambda: button.setEnabled(Wallet.is_private_key(str(keys_e.toPlainText()).strip())))
2067 text = str(keys_e.toPlainText()).strip()
2068 privkeys = text.split()
2069 to_address = self.wallet.addresses()[0]
2070 fee = self.wallet.fee
2071 tx = Transaction.sweep(privkeys, self.network, to_address, fee)
2072 self.show_transaction(tx)
2076 def do_import_privkey(self, password):
2077 if not self.wallet.imported_keys:
2078 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2079 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2080 + _('Are you sure you understand what you are doing?'), 3, 4)
2083 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2086 text = str(text).split()
2091 addr = self.wallet.import_key(key, password)
2092 except Exception as e:
2098 addrlist.append(addr)
2100 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2102 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2103 self.update_receive_tab()
2104 self.update_history_tab()
2107 def settings_dialog(self):
2109 d.setWindowTitle(_('Electrum Settings'))
2111 vbox = QVBoxLayout()
2112 grid = QGridLayout()
2113 grid.setColumnStretch(0,1)
2115 nz_label = QLabel(_('Display zeros') + ':')
2116 grid.addWidget(nz_label, 0, 0)
2117 nz_e = AmountEdit(None,True)
2118 nz_e.setText("%d"% self.num_zeros)
2119 grid.addWidget(nz_e, 0, 1)
2120 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2121 grid.addWidget(HelpButton(msg), 0, 2)
2122 if not self.config.is_modifiable('num_zeros'):
2123 for w in [nz_e, nz_label]: w.setEnabled(False)
2125 lang_label=QLabel(_('Language') + ':')
2126 grid.addWidget(lang_label, 1, 0)
2127 lang_combo = QComboBox()
2128 from electrum.i18n import languages
2129 lang_combo.addItems(languages.values())
2131 index = languages.keys().index(self.config.get("language",''))
2134 lang_combo.setCurrentIndex(index)
2135 grid.addWidget(lang_combo, 1, 1)
2136 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2137 if not self.config.is_modifiable('language'):
2138 for w in [lang_combo, lang_label]: w.setEnabled(False)
2141 fee_label = QLabel(_('Transaction fee') + ':')
2142 grid.addWidget(fee_label, 2, 0)
2143 fee_e = AmountEdit(self.base_unit)
2144 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2145 grid.addWidget(fee_e, 2, 1)
2146 msg = _('Fee per kilobyte of transaction.') + ' ' \
2147 + _('Recommended value') + ': ' + self.format_amount(20000)
2148 grid.addWidget(HelpButton(msg), 2, 2)
2149 if not self.config.is_modifiable('fee_per_kb'):
2150 for w in [fee_e, fee_label]: w.setEnabled(False)
2152 units = ['BTC', 'mBTC']
2153 unit_label = QLabel(_('Base unit') + ':')
2154 grid.addWidget(unit_label, 3, 0)
2155 unit_combo = QComboBox()
2156 unit_combo.addItems(units)
2157 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2158 grid.addWidget(unit_combo, 3, 1)
2159 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2160 + '\n1BTC=1000mBTC.\n' \
2161 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2163 usechange_cb = QCheckBox(_('Use change addresses'))
2164 usechange_cb.setChecked(self.wallet.use_change)
2165 grid.addWidget(usechange_cb, 4, 0)
2166 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2167 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2169 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2170 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2171 grid.addWidget(block_ex_label, 5, 0)
2172 block_ex_combo = QComboBox()
2173 block_ex_combo.addItems(block_explorers)
2174 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2175 grid.addWidget(block_ex_combo, 5, 1)
2176 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2178 show_tx = self.config.get('show_before_broadcast', False)
2179 showtx_cb = QCheckBox(_('Show before broadcast'))
2180 showtx_cb.setChecked(show_tx)
2181 grid.addWidget(showtx_cb, 6, 0)
2182 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2184 vbox.addLayout(grid)
2186 vbox.addLayout(ok_cancel_buttons(d))
2190 if not d.exec_(): return
2192 fee = unicode(fee_e.text())
2194 fee = self.read_amount(fee)
2196 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2199 self.wallet.set_fee(fee)
2201 nz = unicode(nz_e.text())
2206 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2209 if self.num_zeros != nz:
2211 self.config.set_key('num_zeros', nz, True)
2212 self.update_history_tab()
2213 self.update_receive_tab()
2215 usechange_result = usechange_cb.isChecked()
2216 if self.wallet.use_change != usechange_result:
2217 self.wallet.use_change = usechange_result
2218 self.wallet.storage.put('use_change', self.wallet.use_change)
2220 if showtx_cb.isChecked() != show_tx:
2221 self.config.set_key('show_before_broadcast', not show_tx)
2223 unit_result = units[unit_combo.currentIndex()]
2224 if self.base_unit() != unit_result:
2225 self.decimal_point = 8 if unit_result == 'BTC' else 5
2226 self.config.set_key('decimal_point', self.decimal_point, True)
2227 self.update_history_tab()
2228 self.update_status()
2230 need_restart = False
2232 lang_request = languages.keys()[lang_combo.currentIndex()]
2233 if lang_request != self.config.get('language'):
2234 self.config.set_key("language", lang_request, True)
2237 be_result = block_explorers[block_ex_combo.currentIndex()]
2238 self.config.set_key('block_explorer', be_result, True)
2240 run_hook('close_settings_dialog')
2243 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2246 def run_network_dialog(self):
2247 if not self.network:
2249 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2251 def closeEvent(self, event):
2254 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2255 self.save_column_widths()
2256 self.config.set_key("console-history", self.console.history[-50:], True)
2257 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2261 def plugins_dialog(self):
2262 from electrum.plugins import plugins
2265 d.setWindowTitle(_('Electrum Plugins'))
2268 vbox = QVBoxLayout(d)
2271 scroll = QScrollArea()
2272 scroll.setEnabled(True)
2273 scroll.setWidgetResizable(True)
2274 scroll.setMinimumSize(400,250)
2275 vbox.addWidget(scroll)
2279 w.setMinimumHeight(len(plugins)*35)
2281 grid = QGridLayout()
2282 grid.setColumnStretch(0,1)
2285 def do_toggle(cb, p, w):
2288 if w: w.setEnabled(r)
2290 def mk_toggle(cb, p, w):
2291 return lambda: do_toggle(cb,p,w)
2293 for i, p in enumerate(plugins):
2295 cb = QCheckBox(p.fullname())
2296 cb.setDisabled(not p.is_available())
2297 cb.setChecked(p.is_enabled())
2298 grid.addWidget(cb, i, 0)
2299 if p.requires_settings():
2300 w = p.settings_widget(self)
2301 w.setEnabled( p.is_enabled() )
2302 grid.addWidget(w, i, 1)
2305 cb.clicked.connect(mk_toggle(cb,p,w))
2306 grid.addWidget(HelpButton(p.description()), i, 2)
2308 print_msg(_("Error: cannot display plugin"), p)
2309 traceback.print_exc(file=sys.stdout)
2310 grid.setRowStretch(i+1,1)
2312 vbox.addLayout(close_button(d))
2317 def show_account_details(self, k):
2318 account = self.wallet.accounts[k]
2321 d.setWindowTitle(_('Account Details'))
2324 vbox = QVBoxLayout(d)
2325 name = self.wallet.get_account_name(k)
2326 label = QLabel('Name: ' + name)
2327 vbox.addWidget(label)
2329 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2331 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2333 vbox.addWidget(QLabel(_('Master Public Key:')))
2336 text.setReadOnly(True)
2337 text.setMaximumHeight(170)
2338 vbox.addWidget(text)
2340 mpk_text = '\n'.join( account.get_master_pubkeys() )
2341 text.setText(mpk_text)
2343 vbox.addLayout(close_button(d))