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 grid.addWidget(QLabel(_('Pay to')), 1, 0)
636 grid.addWidget(self.payto_e, 1, 1, 1, 3)
638 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)
640 completer = QCompleter()
641 completer.setCaseSensitivity(False)
642 self.payto_e.setCompleter(completer)
643 completer.setModel(self.completions)
645 self.message_e = QLineEdit()
646 grid.addWidget(QLabel(_('Description')), 2, 0)
647 grid.addWidget(self.message_e, 2, 1, 1, 3)
648 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)
650 self.from_label = QLabel(_('From'))
651 grid.addWidget(self.from_label, 3, 0)
652 self.from_list = QTreeWidget(self)
653 self.from_list.setColumnCount(2)
654 self.from_list.setColumnWidth(0, 350)
655 self.from_list.setColumnWidth(1, 50)
656 self.from_list.setHeaderHidden (True)
657 self.from_list.setMaximumHeight(80)
658 grid.addWidget(self.from_list, 3, 1, 1, 3)
659 self.set_pay_from([])
661 self.amount_e = AmountEdit(self.base_unit)
662 grid.addWidget(QLabel(_('Amount')), 4, 0)
663 grid.addWidget(self.amount_e, 4, 1, 1, 2)
664 grid.addWidget(HelpButton(
665 _('Amount to be sent.') + '\n\n' \
666 + _('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.') \
667 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
669 self.fee_e = AmountEdit(self.base_unit)
670 grid.addWidget(QLabel(_('Fee')), 5, 0)
671 grid.addWidget(self.fee_e, 5, 1, 1, 2)
672 grid.addWidget(HelpButton(
673 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
674 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
675 + _('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)
677 run_hook('exchange_rate_button', grid)
679 self.send_button = EnterButton(_("Send"), self.do_send)
680 grid.addWidget(self.send_button, 6, 1)
682 b = EnterButton(_("Clear"),self.do_clear)
683 grid.addWidget(b, 6, 2)
685 self.payto_sig = QLabel('')
686 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
688 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
689 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
698 def entry_changed( is_fee ):
699 self.funds_error = False
701 if self.amount_e.is_shortcut:
702 self.amount_e.is_shortcut = False
703 sendable = self.get_sendable_balance()
704 # there is only one output because we are completely spending inputs
705 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
706 fee = self.wallet.estimated_fee(inputs, 1)
708 self.amount_e.setText( self.format_amount(amount) )
709 self.fee_e.setText( self.format_amount( fee ) )
712 amount = self.read_amount(str(self.amount_e.text()))
713 fee = self.read_amount(str(self.fee_e.text()))
715 if not is_fee: fee = None
718 # assume that there will be 2 outputs (one for change)
719 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
721 self.fee_e.setText( self.format_amount( fee ) )
724 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
728 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
729 self.funds_error = True
730 text = _( "Not enough funds" )
731 c, u = self.wallet.get_frozen_balance()
732 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
734 self.statusBar().showMessage(text)
735 self.amount_e.setPalette(palette)
736 self.fee_e.setPalette(palette)
738 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
739 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
741 run_hook('create_send_tab', grid)
745 def set_pay_from(self, l):
747 self.from_list.clear()
748 self.from_label.setHidden(len(self.pay_from) == 0)
749 self.from_list.setHidden(len(self.pay_from) == 0)
750 for addr in self.pay_from:
751 c, u = self.wallet.get_addr_balance(addr)
752 balance = self.format_amount(c + u)
753 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
756 def update_completions(self):
758 for addr,label in self.wallet.labels.items():
759 if addr in self.wallet.addressbook:
760 l.append( label + ' <' + addr + '>')
762 run_hook('update_completions', l)
763 self.completions.setStringList(l)
767 return lambda s, *args: s.do_protect(func, args)
771 label = unicode( self.message_e.text() )
773 if self.gui_object.payment_request:
774 outputs = self.gui_object.payment_request.outputs
775 amount = self.gui_object.payment_request.get_amount()
778 r = unicode( self.payto_e.text() )
781 # label or alias, with address in brackets
782 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
783 to_address = m.group(2) if m else r
784 if not is_valid(to_address):
785 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
789 amount = self.read_amount(unicode( self.amount_e.text()))
791 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
794 outputs = [(to_address, amount)]
797 fee = self.read_amount(unicode( self.fee_e.text()))
799 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
802 confirm_amount = self.config.get('confirm_amount', 100000000)
803 if amount >= confirm_amount:
804 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
807 confirm_fee = self.config.get('confirm_fee', 100000)
808 if fee >= confirm_fee:
809 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()}):
812 self.send_tx(outputs, fee, label)
817 def send_tx(self, outputs, fee, label, password):
819 # first, create an unsigned tx
820 domain = self.get_payment_sources()
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 return tx, fee, label
840 def sign_done(tx, fee, label):
842 self.show_message(tx.error)
844 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
845 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
848 self.wallet.set_label(tx.hash(), label)
850 if not self.gui_object.payment_request:
851 if not tx.is_complete() or self.config.get('show_before_broadcast'):
852 self.show_transaction(tx)
855 self.broadcast_transaction(tx)
857 WaitingDialog(self, 'Signing..').start(sign_thread, sign_done)
861 def broadcast_transaction(self, tx):
863 def broadcast_thread():
864 if self.gui_object.payment_request:
865 refund_address = self.wallet.addresses()[0]
866 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
867 self.gui_object.payment_request = None
869 status, msg = self.wallet.sendtx(tx)
872 def broadcast_done(status, msg):
874 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
877 QMessageBox.warning(self, _('Error'), msg, _('OK'))
879 WaitingDialog(self, 'Broadcasting..').start(broadcast_thread, broadcast_done)
883 def prepare_for_payment_request(self):
884 style = "QWidget { background-color:none;border:none;}"
885 self.tabs.setCurrentIndex(1)
886 self.payto_e.setReadOnly(True)
887 self.payto_e.setStyleSheet(style)
888 self.amount_e.setReadOnly(True)
889 self.payto_e.setText(_("please wait..."))
890 self.amount_e.setStyleSheet(style)
893 def payment_request_ok(self):
894 self.payto_e.setText(self.gui_object.payment_request.domain)
895 self.amount_e.setText(self.format_amount(self.gui_object.payment_request.get_amount()))
897 def payment_request_error(self):
898 self.payto_e.setText(self.gui_object.payment_request.error)
901 def set_send(self, address, amount, label, message):
903 if label and self.wallet.labels.get(address) != label:
904 if self.question('Give label "%s" to address %s ?'%(label,address)):
905 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
906 self.wallet.addressbook.append(address)
907 self.wallet.set_label(address, label)
909 self.tabs.setCurrentIndex(1)
910 label = self.wallet.labels.get(address)
911 m_addr = label + ' <'+ address +'>' if label else address
912 self.payto_e.setText(m_addr)
914 self.message_e.setText(message)
916 self.amount_e.setText(amount)
920 self.payto_sig.setVisible(False)
921 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
923 self.set_frozen(e,False)
926 self.set_pay_from([])
929 def set_frozen(self,entry,frozen):
931 entry.setReadOnly(True)
932 entry.setFrame(False)
934 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
935 entry.setPalette(palette)
937 entry.setReadOnly(False)
940 palette.setColor(entry.backgroundRole(), QColor('white'))
941 entry.setPalette(palette)
944 def set_addrs_frozen(self,addrs,freeze):
946 if not addr: continue
947 if addr in self.wallet.frozen_addresses and not freeze:
948 self.wallet.unfreeze(addr)
949 elif addr not in self.wallet.frozen_addresses and freeze:
950 self.wallet.freeze(addr)
951 self.update_receive_tab()
955 def create_list_tab(self, headers):
956 "generic tab creation method"
957 l = MyTreeWidget(self)
958 l.setColumnCount( len(headers) )
959 l.setHeaderLabels( headers )
969 vbox.addWidget(buttons)
974 buttons.setLayout(hbox)
979 def create_receive_tab(self):
980 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
981 l.setContextMenuPolicy(Qt.CustomContextMenu)
982 l.customContextMenuRequested.connect(self.create_receive_menu)
983 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
984 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
985 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
986 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
987 self.receive_list = l
988 self.receive_buttons_hbox = hbox
995 def save_column_widths(self):
996 self.column_widths["receive"] = []
997 for i in range(self.receive_list.columnCount() -1):
998 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1000 self.column_widths["history"] = []
1001 for i in range(self.history_list.columnCount() - 1):
1002 self.column_widths["history"].append(self.history_list.columnWidth(i))
1004 self.column_widths["contacts"] = []
1005 for i in range(self.contacts_list.columnCount() - 1):
1006 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1008 self.config.set_key("column_widths_2", self.column_widths, True)
1011 def create_contacts_tab(self):
1012 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1013 l.setContextMenuPolicy(Qt.CustomContextMenu)
1014 l.customContextMenuRequested.connect(self.create_contact_menu)
1015 for i,width in enumerate(self.column_widths['contacts']):
1016 l.setColumnWidth(i, width)
1018 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1019 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1020 self.contacts_list = l
1021 self.contacts_buttons_hbox = hbox
1026 def delete_imported_key(self, addr):
1027 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1028 self.wallet.delete_imported_key(addr)
1029 self.update_receive_tab()
1030 self.update_history_tab()
1032 def edit_account_label(self, k):
1033 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1035 label = unicode(text)
1036 self.wallet.set_label(k,label)
1037 self.update_receive_tab()
1039 def account_set_expanded(self, item, k, b):
1041 self.accounts_expanded[k] = b
1043 def create_account_menu(self, position, k, item):
1045 if item.isExpanded():
1046 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1048 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1049 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1050 if self.wallet.seed_version > 4:
1051 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1052 if self.wallet.account_is_pending(k):
1053 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1054 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1056 def delete_pending_account(self, k):
1057 self.wallet.delete_pending_account(k)
1058 self.update_receive_tab()
1060 def create_receive_menu(self, position):
1061 # fixme: this function apparently has a side effect.
1062 # if it is not called the menu pops up several times
1063 #self.receive_list.selectedIndexes()
1065 selected = self.receive_list.selectedItems()
1066 multi_select = len(selected) > 1
1067 addrs = [unicode(item.text(0)) for item in selected]
1068 if not multi_select:
1069 item = self.receive_list.itemAt(position)
1073 if not is_valid(addr):
1074 k = str(item.data(0,32).toString())
1076 self.create_account_menu(position, k, item)
1078 item.setExpanded(not item.isExpanded())
1082 if not multi_select:
1083 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1084 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1085 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1086 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1087 if not self.wallet.is_watching_only():
1088 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1089 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1090 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1091 if self.wallet.is_imported(addr):
1092 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1094 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1095 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1096 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1097 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1099 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1100 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1102 run_hook('receive_menu', menu, addrs)
1103 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1106 def get_sendable_balance(self):
1107 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1110 def get_payment_sources(self):
1112 return self.pay_from
1114 return self.wallet.get_account_addresses(self.current_account)
1117 def send_from_addresses(self, addrs):
1118 self.set_pay_from( addrs )
1119 self.tabs.setCurrentIndex(1)
1122 def payto(self, addr):
1124 label = self.wallet.labels.get(addr)
1125 m_addr = label + ' <' + addr + '>' if label else addr
1126 self.tabs.setCurrentIndex(1)
1127 self.payto_e.setText(m_addr)
1128 self.amount_e.setFocus()
1131 def delete_contact(self, x):
1132 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1133 self.wallet.delete_contact(x)
1134 self.wallet.set_label(x, None)
1135 self.update_history_tab()
1136 self.update_contacts_tab()
1137 self.update_completions()
1140 def create_contact_menu(self, position):
1141 item = self.contacts_list.itemAt(position)
1144 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1146 addr = unicode(item.text(0))
1147 label = unicode(item.text(1))
1148 is_editable = item.data(0,32).toBool()
1149 payto_addr = item.data(0,33).toString()
1150 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1151 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1152 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1154 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1155 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1157 run_hook('create_contact_menu', menu, item)
1158 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1161 def update_receive_item(self, item):
1162 item.setFont(0, QFont(MONOSPACE_FONT))
1163 address = str(item.data(0,0).toString())
1164 label = self.wallet.labels.get(address,'')
1165 item.setData(1,0,label)
1166 item.setData(0,32, True) # is editable
1168 run_hook('update_receive_item', address, item)
1170 if not self.wallet.is_mine(address): return
1172 c, u = self.wallet.get_addr_balance(address)
1173 balance = self.format_amount(c + u)
1174 item.setData(2,0,balance)
1176 if address in self.wallet.frozen_addresses:
1177 item.setBackgroundColor(0, QColor('lightblue'))
1180 def update_receive_tab(self):
1181 l = self.receive_list
1182 # extend the syntax for consistency
1183 l.addChild = l.addTopLevelItem
1186 for i,width in enumerate(self.column_widths['receive']):
1187 l.setColumnWidth(i, width)
1189 accounts = self.wallet.get_accounts()
1190 if self.current_account is None:
1191 account_items = sorted(accounts.items())
1193 account_items = [(self.current_account, accounts.get(self.current_account))]
1196 for k, account in account_items:
1198 if len(accounts) > 1:
1199 name = self.wallet.get_account_name(k)
1200 c,u = self.wallet.get_account_balance(k)
1201 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1202 l.addTopLevelItem(account_item)
1203 account_item.setExpanded(self.accounts_expanded.get(k, True))
1204 account_item.setData(0, 32, k)
1208 sequences = [0,1] if account.has_change() else [0]
1209 for is_change in sequences:
1210 if len(sequences) > 1:
1211 name = _("Receiving") if not is_change else _("Change")
1212 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1213 account_item.addChild(seq_item)
1215 seq_item.setExpanded(True)
1217 seq_item = account_item
1219 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1225 for address in account.get_addresses(is_change):
1226 h = self.wallet.history.get(address,[])
1230 if gap > self.wallet.gap_limit:
1235 c, u = self.wallet.get_addr_balance(address)
1236 num_tx = '*' if h == ['*'] else "%d"%len(h)
1238 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1239 self.update_receive_item(item)
1241 item.setBackgroundColor(1, QColor('red'))
1242 if len(h) > 0 and c == -u:
1244 seq_item.insertChild(0,used_item)
1246 used_item.addChild(item)
1248 seq_item.addChild(item)
1250 # we use column 1 because column 0 may be hidden
1251 l.setCurrentItem(l.topLevelItem(0),1)
1254 def update_contacts_tab(self):
1255 l = self.contacts_list
1258 for address in self.wallet.addressbook:
1259 label = self.wallet.labels.get(address,'')
1260 n = self.wallet.get_num_tx(address)
1261 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1262 item.setFont(0, QFont(MONOSPACE_FONT))
1263 # 32 = label can be edited (bool)
1264 item.setData(0,32, True)
1266 item.setData(0,33, address)
1267 l.addTopLevelItem(item)
1269 run_hook('update_contacts_tab', l)
1270 l.setCurrentItem(l.topLevelItem(0))
1274 def create_console_tab(self):
1275 from console import Console
1276 self.console = console = Console()
1280 def update_console(self):
1281 console = self.console
1282 console.history = self.config.get("console-history",[])
1283 console.history_index = len(console.history)
1285 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1286 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1288 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1290 def mkfunc(f, method):
1291 return lambda *args: apply( f, (method, args, self.password_dialog ))
1293 if m[0]=='_' or m in ['network','wallet']: continue
1294 methods[m] = mkfunc(c._run, m)
1296 console.updateNamespace(methods)
1299 def change_account(self,s):
1300 if s == _("All accounts"):
1301 self.current_account = None
1303 accounts = self.wallet.get_account_names()
1304 for k, v in accounts.items():
1306 self.current_account = k
1307 self.update_history_tab()
1308 self.update_status()
1309 self.update_receive_tab()
1311 def create_status_bar(self):
1314 sb.setFixedHeight(35)
1315 qtVersion = qVersion()
1317 self.balance_label = QLabel("")
1318 sb.addWidget(self.balance_label)
1320 from version_getter import UpdateLabel
1321 self.updatelabel = UpdateLabel(self.config, sb)
1323 self.account_selector = QComboBox()
1324 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1325 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1326 sb.addPermanentWidget(self.account_selector)
1328 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1329 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1331 self.lock_icon = QIcon()
1332 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1333 sb.addPermanentWidget( self.password_button )
1335 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1336 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1337 sb.addPermanentWidget( self.seed_button )
1338 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1339 sb.addPermanentWidget( self.status_button )
1341 run_hook('create_status_bar', (sb,))
1343 self.setStatusBar(sb)
1346 def update_lock_icon(self):
1347 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1348 self.password_button.setIcon( icon )
1351 def update_buttons_on_seed(self):
1352 if self.wallet.has_seed():
1353 self.seed_button.show()
1355 self.seed_button.hide()
1357 if not self.wallet.is_watching_only():
1358 self.password_button.show()
1359 self.send_button.setText(_("Send"))
1361 self.password_button.hide()
1362 self.send_button.setText(_("Create unsigned transaction"))
1365 def change_password_dialog(self):
1366 from password_dialog import PasswordDialog
1367 d = PasswordDialog(self.wallet, self)
1369 self.update_lock_icon()
1372 def new_contact_dialog(self):
1375 d.setWindowTitle(_("New Contact"))
1376 vbox = QVBoxLayout(d)
1377 vbox.addWidget(QLabel(_('New Contact')+':'))
1379 grid = QGridLayout()
1382 grid.addWidget(QLabel(_("Address")), 1, 0)
1383 grid.addWidget(line1, 1, 1)
1384 grid.addWidget(QLabel(_("Name")), 2, 0)
1385 grid.addWidget(line2, 2, 1)
1387 vbox.addLayout(grid)
1388 vbox.addLayout(ok_cancel_buttons(d))
1393 address = str(line1.text())
1394 label = unicode(line2.text())
1396 if not is_valid(address):
1397 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1400 self.wallet.add_contact(address)
1402 self.wallet.set_label(address, label)
1404 self.update_contacts_tab()
1405 self.update_history_tab()
1406 self.update_completions()
1407 self.tabs.setCurrentIndex(3)
1411 def new_account_dialog(self, password):
1413 dialog = QDialog(self)
1415 dialog.setWindowTitle(_("New Account"))
1417 vbox = QVBoxLayout()
1418 vbox.addWidget(QLabel(_('Account name')+':'))
1421 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1422 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1427 vbox.addLayout(ok_cancel_buttons(dialog))
1428 dialog.setLayout(vbox)
1432 name = str(e.text())
1435 self.wallet.create_pending_account(name, password)
1436 self.update_receive_tab()
1437 self.tabs.setCurrentIndex(2)
1442 def show_master_public_keys(self):
1444 dialog = QDialog(self)
1446 dialog.setWindowTitle(_("Master Public Keys"))
1448 main_layout = QGridLayout()
1449 mpk_dict = self.wallet.get_master_public_keys()
1451 for key, value in mpk_dict.items():
1452 main_layout.addWidget(QLabel(key), i, 0)
1453 mpk_text = QTextEdit()
1454 mpk_text.setReadOnly(True)
1455 mpk_text.setMaximumHeight(170)
1456 mpk_text.setText(value)
1457 main_layout.addWidget(mpk_text, i + 1, 0)
1460 vbox = QVBoxLayout()
1461 vbox.addLayout(main_layout)
1462 vbox.addLayout(close_button(dialog))
1464 dialog.setLayout(vbox)
1469 def show_seed_dialog(self, password):
1470 if not self.wallet.has_seed():
1471 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1475 mnemonic = self.wallet.get_mnemonic(password)
1477 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1479 from seed_dialog import SeedDialog
1480 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1485 def show_qrcode(self, data, title = _("QR code")):
1489 d.setWindowTitle(title)
1490 d.setMinimumSize(270, 300)
1491 vbox = QVBoxLayout()
1492 qrw = QRCodeWidget(data)
1493 vbox.addWidget(qrw, 1)
1494 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1495 hbox = QHBoxLayout()
1498 filename = os.path.join(self.config.path, "qrcode.bmp")
1501 bmp.save_qrcode(qrw.qr, filename)
1502 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1504 def copy_to_clipboard():
1505 bmp.save_qrcode(qrw.qr, filename)
1506 self.app.clipboard().setImage(QImage(filename))
1507 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1509 b = QPushButton(_("Copy"))
1511 b.clicked.connect(copy_to_clipboard)
1513 b = QPushButton(_("Save"))
1515 b.clicked.connect(print_qr)
1517 b = QPushButton(_("Close"))
1519 b.clicked.connect(d.accept)
1522 vbox.addLayout(hbox)
1527 def do_protect(self, func, args):
1528 if self.wallet.use_encryption:
1529 password = self.password_dialog()
1535 if args != (False,):
1536 args = (self,) + args + (password,)
1538 args = (self,password)
1542 def show_public_keys(self, address):
1543 if not address: return
1545 pubkey_list = self.wallet.get_public_keys(address)
1546 except Exception as e:
1547 traceback.print_exc(file=sys.stdout)
1548 self.show_message(str(e))
1552 d.setMinimumSize(600, 200)
1554 vbox = QVBoxLayout()
1555 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1556 vbox.addWidget( QLabel(_("Public key") + ':'))
1558 keys.setReadOnly(True)
1559 keys.setText('\n'.join(pubkey_list))
1560 vbox.addWidget(keys)
1561 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1562 vbox.addLayout(close_button(d))
1567 def show_private_key(self, address, password):
1568 if not address: return
1570 pk_list = self.wallet.get_private_key(address, password)
1571 except Exception as e:
1572 traceback.print_exc(file=sys.stdout)
1573 self.show_message(str(e))
1577 d.setMinimumSize(600, 200)
1579 vbox = QVBoxLayout()
1580 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1581 vbox.addWidget( QLabel(_("Private key") + ':'))
1583 keys.setReadOnly(True)
1584 keys.setText('\n'.join(pk_list))
1585 vbox.addWidget(keys)
1586 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1587 vbox.addLayout(close_button(d))
1593 def do_sign(self, address, message, signature, password):
1594 message = unicode(message.toPlainText())
1595 message = message.encode('utf-8')
1597 sig = self.wallet.sign_message(str(address.text()), message, password)
1598 signature.setText(sig)
1599 except Exception as e:
1600 self.show_message(str(e))
1602 def do_verify(self, address, message, signature):
1603 message = unicode(message.toPlainText())
1604 message = message.encode('utf-8')
1605 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1606 self.show_message(_("Signature verified"))
1608 self.show_message(_("Error: wrong signature"))
1611 def sign_verify_message(self, address=''):
1614 d.setWindowTitle(_('Sign/verify Message'))
1615 d.setMinimumSize(410, 290)
1617 layout = QGridLayout(d)
1619 message_e = QTextEdit()
1620 layout.addWidget(QLabel(_('Message')), 1, 0)
1621 layout.addWidget(message_e, 1, 1)
1622 layout.setRowStretch(2,3)
1624 address_e = QLineEdit()
1625 address_e.setText(address)
1626 layout.addWidget(QLabel(_('Address')), 2, 0)
1627 layout.addWidget(address_e, 2, 1)
1629 signature_e = QTextEdit()
1630 layout.addWidget(QLabel(_('Signature')), 3, 0)
1631 layout.addWidget(signature_e, 3, 1)
1632 layout.setRowStretch(3,1)
1634 hbox = QHBoxLayout()
1636 b = QPushButton(_("Sign"))
1637 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1640 b = QPushButton(_("Verify"))
1641 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1644 b = QPushButton(_("Close"))
1645 b.clicked.connect(d.accept)
1647 layout.addLayout(hbox, 4, 1)
1652 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1654 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1655 message_e.setText(decrypted)
1656 except Exception as e:
1657 self.show_message(str(e))
1660 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1661 message = unicode(message_e.toPlainText())
1662 message = message.encode('utf-8')
1664 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1665 encrypted_e.setText(encrypted)
1666 except Exception as e:
1667 self.show_message(str(e))
1671 def encrypt_message(self, address = ''):
1674 d.setWindowTitle(_('Encrypt/decrypt Message'))
1675 d.setMinimumSize(610, 490)
1677 layout = QGridLayout(d)
1679 message_e = QTextEdit()
1680 layout.addWidget(QLabel(_('Message')), 1, 0)
1681 layout.addWidget(message_e, 1, 1)
1682 layout.setRowStretch(2,3)
1684 pubkey_e = QLineEdit()
1686 pubkey = self.wallet.getpubkeys(address)[0]
1687 pubkey_e.setText(pubkey)
1688 layout.addWidget(QLabel(_('Public key')), 2, 0)
1689 layout.addWidget(pubkey_e, 2, 1)
1691 encrypted_e = QTextEdit()
1692 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1693 layout.addWidget(encrypted_e, 3, 1)
1694 layout.setRowStretch(3,1)
1696 hbox = QHBoxLayout()
1697 b = QPushButton(_("Encrypt"))
1698 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1701 b = QPushButton(_("Decrypt"))
1702 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1705 b = QPushButton(_("Close"))
1706 b.clicked.connect(d.accept)
1709 layout.addLayout(hbox, 4, 1)
1713 def question(self, msg):
1714 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1716 def show_message(self, msg):
1717 QMessageBox.information(self, _('Message'), msg, _('OK'))
1719 def password_dialog(self ):
1722 d.setWindowTitle(_("Enter Password"))
1727 vbox = QVBoxLayout()
1728 msg = _('Please enter your password')
1729 vbox.addWidget(QLabel(msg))
1731 grid = QGridLayout()
1733 grid.addWidget(QLabel(_('Password')), 1, 0)
1734 grid.addWidget(pw, 1, 1)
1735 vbox.addLayout(grid)
1737 vbox.addLayout(ok_cancel_buttons(d))
1740 run_hook('password_dialog', pw, grid, 1)
1741 if not d.exec_(): return
1742 return unicode(pw.text())
1751 def tx_from_text(self, txt):
1752 "json or raw hexadecimal"
1755 tx = Transaction(txt)
1761 tx_dict = json.loads(str(txt))
1762 assert "hex" in tx_dict.keys()
1763 assert "complete" in tx_dict.keys()
1764 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1765 if not tx_dict["complete"]:
1766 assert "input_info" in tx_dict.keys()
1767 input_info = json.loads(tx_dict['input_info'])
1768 tx.add_input_info(input_info)
1773 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1777 def read_tx_from_file(self):
1778 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1782 with open(fileName, "r") as f:
1783 file_content = f.read()
1784 except (ValueError, IOError, os.error), reason:
1785 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1787 return self.tx_from_text(file_content)
1791 def sign_raw_transaction(self, tx, input_info, password):
1792 self.wallet.signrawtransaction(tx, input_info, [], password)
1794 def do_process_from_text(self):
1795 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1798 tx = self.tx_from_text(text)
1800 self.show_transaction(tx)
1802 def do_process_from_file(self):
1803 tx = self.read_tx_from_file()
1805 self.show_transaction(tx)
1807 def do_process_from_txid(self):
1808 from electrum import transaction
1809 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1811 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1813 tx = transaction.Transaction(r)
1815 self.show_transaction(tx)
1817 self.show_message("unknown transaction")
1819 def do_process_from_csvReader(self, csvReader):
1824 for position, row in enumerate(csvReader):
1826 if not is_valid(address):
1827 errors.append((position, address))
1829 amount = Decimal(row[1])
1830 amount = int(100000000*amount)
1831 outputs.append((address, amount))
1832 except (ValueError, IOError, os.error), reason:
1833 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1837 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1838 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1842 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1843 except Exception as e:
1844 self.show_message(str(e))
1847 self.show_transaction(tx)
1849 def do_process_from_csv_file(self):
1850 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1854 with open(fileName, "r") as f:
1855 csvReader = csv.reader(f)
1856 self.do_process_from_csvReader(csvReader)
1857 except (ValueError, IOError, os.error), reason:
1858 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1861 def do_process_from_csv_text(self):
1862 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1863 + _("Format: address, amount. One output per line"), _("Load CSV"))
1866 f = StringIO.StringIO(text)
1867 csvReader = csv.reader(f)
1868 self.do_process_from_csvReader(csvReader)
1873 def export_privkeys_dialog(self, password):
1874 if self.wallet.is_watching_only():
1875 self.show_message(_("This is a watching-only wallet"))
1879 d.setWindowTitle(_('Private keys'))
1880 d.setMinimumSize(850, 300)
1881 vbox = QVBoxLayout(d)
1883 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1884 _("Exposing a single private key can compromise your entire wallet!"),
1885 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1886 vbox.addWidget(QLabel(msg))
1892 defaultname = 'electrum-private-keys.csv'
1893 select_msg = _('Select file to export your private keys to')
1894 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1895 vbox.addLayout(hbox)
1897 h, b = ok_cancel_buttons2(d, _('Export'))
1902 addresses = self.wallet.addresses(True)
1904 def privkeys_thread():
1905 for addr in addresses:
1909 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1910 d.emit(SIGNAL('computing_privkeys'))
1911 d.emit(SIGNAL('show_privkeys'))
1913 def show_privkeys():
1914 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1918 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1919 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1920 threading.Thread(target=privkeys_thread).start()
1926 filename = filename_e.text()
1931 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1932 except (IOError, os.error), reason:
1933 export_error_label = _("Electrum was unable to produce a private key-export.")
1934 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1936 except Exception as e:
1937 self.show_message(str(e))
1940 self.show_message(_("Private keys exported."))
1943 def do_export_privkeys(self, fileName, pklist, is_csv):
1944 with open(fileName, "w+") as f:
1946 transaction = csv.writer(f)
1947 transaction.writerow(["address", "private_key"])
1948 for addr, pk in pklist.items():
1949 transaction.writerow(["%34s"%addr,pk])
1952 f.write(json.dumps(pklist, indent = 4))
1955 def do_import_labels(self):
1956 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1957 if not labelsFile: return
1959 f = open(labelsFile, 'r')
1962 for key, value in json.loads(data).items():
1963 self.wallet.set_label(key, value)
1964 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1965 except (IOError, os.error), reason:
1966 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1969 def do_export_labels(self):
1970 labels = self.wallet.labels
1972 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1974 with open(fileName, 'w+') as f:
1975 json.dump(labels, f)
1976 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1977 except (IOError, os.error), reason:
1978 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1981 def export_history_dialog(self):
1984 d.setWindowTitle(_('Export History'))
1985 d.setMinimumSize(400, 200)
1986 vbox = QVBoxLayout(d)
1988 defaultname = os.path.expanduser('~/electrum-history.csv')
1989 select_msg = _('Select file to export your wallet transactions to')
1991 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1992 vbox.addLayout(hbox)
1996 h, b = ok_cancel_buttons2(d, _('Export'))
2001 filename = filename_e.text()
2006 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2007 except (IOError, os.error), reason:
2008 export_error_label = _("Electrum was unable to produce a transaction export.")
2009 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2012 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2015 def do_export_history(self, wallet, fileName, is_csv):
2016 history = wallet.get_tx_history()
2018 for item in history:
2019 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2021 if timestamp is not None:
2023 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2024 except [RuntimeError, TypeError, NameError] as reason:
2025 time_string = "unknown"
2028 time_string = "unknown"
2030 time_string = "pending"
2032 if value is not None:
2033 value_string = format_satoshis(value, True)
2038 fee_string = format_satoshis(fee, True)
2043 label, is_default_label = wallet.get_label(tx_hash)
2044 label = label.encode('utf-8')
2048 balance_string = format_satoshis(balance, False)
2050 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2052 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2054 with open(fileName, "w+") as f:
2056 transaction = csv.writer(f)
2057 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2059 transaction.writerow(line)
2062 f.write(json.dumps(lines, indent = 4))
2065 def sweep_key_dialog(self):
2067 d.setWindowTitle(_('Sweep private keys'))
2068 d.setMinimumSize(600, 300)
2070 vbox = QVBoxLayout(d)
2071 vbox.addWidget(QLabel(_("Enter private keys")))
2073 keys_e = QTextEdit()
2074 keys_e.setTabChangesFocus(True)
2075 vbox.addWidget(keys_e)
2077 h, address_e = address_field(self.wallet.addresses())
2081 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2082 vbox.addLayout(hbox)
2083 button.setEnabled(False)
2086 addr = str(address_e.text())
2087 if bitcoin.is_address(addr):
2091 pk = str(keys_e.toPlainText()).strip()
2092 if Wallet.is_private_key(pk):
2095 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2096 keys_e.textChanged.connect(f)
2097 address_e.textChanged.connect(f)
2101 fee = self.wallet.fee
2102 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2103 self.show_transaction(tx)
2107 def do_import_privkey(self, password):
2108 if not self.wallet.imported_keys:
2109 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2110 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2111 + _('Are you sure you understand what you are doing?'), 3, 4)
2114 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2117 text = str(text).split()
2122 addr = self.wallet.import_key(key, password)
2123 except Exception as e:
2129 addrlist.append(addr)
2131 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2133 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2134 self.update_receive_tab()
2135 self.update_history_tab()
2138 def settings_dialog(self):
2140 d.setWindowTitle(_('Electrum Settings'))
2142 vbox = QVBoxLayout()
2143 grid = QGridLayout()
2144 grid.setColumnStretch(0,1)
2146 nz_label = QLabel(_('Display zeros') + ':')
2147 grid.addWidget(nz_label, 0, 0)
2148 nz_e = AmountEdit(None,True)
2149 nz_e.setText("%d"% self.num_zeros)
2150 grid.addWidget(nz_e, 0, 1)
2151 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2152 grid.addWidget(HelpButton(msg), 0, 2)
2153 if not self.config.is_modifiable('num_zeros'):
2154 for w in [nz_e, nz_label]: w.setEnabled(False)
2156 lang_label=QLabel(_('Language') + ':')
2157 grid.addWidget(lang_label, 1, 0)
2158 lang_combo = QComboBox()
2159 from electrum.i18n import languages
2160 lang_combo.addItems(languages.values())
2162 index = languages.keys().index(self.config.get("language",''))
2165 lang_combo.setCurrentIndex(index)
2166 grid.addWidget(lang_combo, 1, 1)
2167 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2168 if not self.config.is_modifiable('language'):
2169 for w in [lang_combo, lang_label]: w.setEnabled(False)
2172 fee_label = QLabel(_('Transaction fee') + ':')
2173 grid.addWidget(fee_label, 2, 0)
2174 fee_e = AmountEdit(self.base_unit)
2175 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2176 grid.addWidget(fee_e, 2, 1)
2177 msg = _('Fee per kilobyte of transaction.') + ' ' \
2178 + _('Recommended value') + ': ' + self.format_amount(20000)
2179 grid.addWidget(HelpButton(msg), 2, 2)
2180 if not self.config.is_modifiable('fee_per_kb'):
2181 for w in [fee_e, fee_label]: w.setEnabled(False)
2183 units = ['BTC', 'mBTC']
2184 unit_label = QLabel(_('Base unit') + ':')
2185 grid.addWidget(unit_label, 3, 0)
2186 unit_combo = QComboBox()
2187 unit_combo.addItems(units)
2188 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2189 grid.addWidget(unit_combo, 3, 1)
2190 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2191 + '\n1BTC=1000mBTC.\n' \
2192 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2194 usechange_cb = QCheckBox(_('Use change addresses'))
2195 usechange_cb.setChecked(self.wallet.use_change)
2196 grid.addWidget(usechange_cb, 4, 0)
2197 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2198 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2200 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2201 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2202 grid.addWidget(block_ex_label, 5, 0)
2203 block_ex_combo = QComboBox()
2204 block_ex_combo.addItems(block_explorers)
2205 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2206 grid.addWidget(block_ex_combo, 5, 1)
2207 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2209 show_tx = self.config.get('show_before_broadcast', False)
2210 showtx_cb = QCheckBox(_('Show before broadcast'))
2211 showtx_cb.setChecked(show_tx)
2212 grid.addWidget(showtx_cb, 6, 0)
2213 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2215 vbox.addLayout(grid)
2217 vbox.addLayout(ok_cancel_buttons(d))
2221 if not d.exec_(): return
2223 fee = unicode(fee_e.text())
2225 fee = self.read_amount(fee)
2227 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2230 self.wallet.set_fee(fee)
2232 nz = unicode(nz_e.text())
2237 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2240 if self.num_zeros != nz:
2242 self.config.set_key('num_zeros', nz, True)
2243 self.update_history_tab()
2244 self.update_receive_tab()
2246 usechange_result = usechange_cb.isChecked()
2247 if self.wallet.use_change != usechange_result:
2248 self.wallet.use_change = usechange_result
2249 self.wallet.storage.put('use_change', self.wallet.use_change)
2251 if showtx_cb.isChecked() != show_tx:
2252 self.config.set_key('show_before_broadcast', not show_tx)
2254 unit_result = units[unit_combo.currentIndex()]
2255 if self.base_unit() != unit_result:
2256 self.decimal_point = 8 if unit_result == 'BTC' else 5
2257 self.config.set_key('decimal_point', self.decimal_point, True)
2258 self.update_history_tab()
2259 self.update_status()
2261 need_restart = False
2263 lang_request = languages.keys()[lang_combo.currentIndex()]
2264 if lang_request != self.config.get('language'):
2265 self.config.set_key("language", lang_request, True)
2268 be_result = block_explorers[block_ex_combo.currentIndex()]
2269 self.config.set_key('block_explorer', be_result, True)
2271 run_hook('close_settings_dialog')
2274 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2277 def run_network_dialog(self):
2278 if not self.network:
2280 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2282 def closeEvent(self, event):
2284 self.config.set_key("is_maximized", self.isMaximized())
2285 if not self.isMaximized():
2287 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2288 self.save_column_widths()
2289 self.config.set_key("console-history", self.console.history[-50:], True)
2290 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2294 def plugins_dialog(self):
2295 from electrum.plugins import plugins
2298 d.setWindowTitle(_('Electrum Plugins'))
2301 vbox = QVBoxLayout(d)
2304 scroll = QScrollArea()
2305 scroll.setEnabled(True)
2306 scroll.setWidgetResizable(True)
2307 scroll.setMinimumSize(400,250)
2308 vbox.addWidget(scroll)
2312 w.setMinimumHeight(len(plugins)*35)
2314 grid = QGridLayout()
2315 grid.setColumnStretch(0,1)
2318 def do_toggle(cb, p, w):
2321 if w: w.setEnabled(r)
2323 def mk_toggle(cb, p, w):
2324 return lambda: do_toggle(cb,p,w)
2326 for i, p in enumerate(plugins):
2328 cb = QCheckBox(p.fullname())
2329 cb.setDisabled(not p.is_available())
2330 cb.setChecked(p.is_enabled())
2331 grid.addWidget(cb, i, 0)
2332 if p.requires_settings():
2333 w = p.settings_widget(self)
2334 w.setEnabled( p.is_enabled() )
2335 grid.addWidget(w, i, 1)
2338 cb.clicked.connect(mk_toggle(cb,p,w))
2339 grid.addWidget(HelpButton(p.description()), i, 2)
2341 print_msg(_("Error: cannot display plugin"), p)
2342 traceback.print_exc(file=sys.stdout)
2343 grid.setRowStretch(i+1,1)
2345 vbox.addLayout(close_button(d))
2350 def show_account_details(self, k):
2351 account = self.wallet.accounts[k]
2354 d.setWindowTitle(_('Account Details'))
2357 vbox = QVBoxLayout(d)
2358 name = self.wallet.get_account_name(k)
2359 label = QLabel('Name: ' + name)
2360 vbox.addWidget(label)
2362 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2364 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2366 vbox.addWidget(QLabel(_('Master Public Key:')))
2369 text.setReadOnly(True)
2370 text.setMaximumHeight(170)
2371 vbox.addWidget(text)
2373 mpk_text = '\n'.join( account.get_master_pubkeys() )
2374 text.setText(mpk_text)
2376 vbox.addLayout(close_button(d))