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.payment_request = None
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('send_tx2'), self.send_tx2)
158 self.connect(self, QtCore.SIGNAL('send_tx3'), self.send_tx3)
159 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
161 self.history_list.setFocus(True)
165 self.network.register_callback('updated', lambda: self.need_update.set())
166 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
167 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
168 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
169 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
171 # set initial message
172 self.console.showMessage(self.network.banner)
177 def update_account_selector(self):
179 accounts = self.wallet.get_account_names()
180 self.account_selector.clear()
181 if len(accounts) > 1:
182 self.account_selector.addItems([_("All accounts")] + accounts.values())
183 self.account_selector.setCurrentIndex(0)
184 self.account_selector.show()
186 self.account_selector.hide()
189 def load_wallet(self, wallet):
192 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
193 self.current_account = self.wallet.storage.get("current_account", None)
195 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
196 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
197 self.setWindowTitle( title )
199 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
200 self.notify_transactions()
201 self.update_account_selector()
203 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
204 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
205 self.password_menu.setEnabled(not self.wallet.is_watching_only())
206 self.seed_menu.setEnabled(self.wallet.has_seed())
207 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
208 self.import_menu.setEnabled(self.wallet.can_import())
210 self.update_lock_icon()
211 self.update_buttons_on_seed()
212 self.update_console()
214 run_hook('load_wallet', wallet)
217 def open_wallet(self):
218 wallet_folder = self.wallet.storage.path
219 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
223 storage = WalletStorage({'wallet_path': filename})
224 if not storage.file_exists:
225 self.show_message("file not found "+ filename)
228 self.wallet.stop_threads()
231 wallet = Wallet(storage)
232 wallet.start_threads(self.network)
234 self.load_wallet(wallet)
238 def backup_wallet(self):
240 path = self.wallet.storage.path
241 wallet_folder = os.path.dirname(path)
242 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
246 new_path = os.path.join(wallet_folder, filename)
249 shutil.copy2(path, new_path)
250 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
251 except (IOError, os.error), reason:
252 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
255 def new_wallet(self):
258 wallet_folder = os.path.dirname(self.wallet.storage.path)
259 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
262 filename = os.path.join(wallet_folder, filename)
264 storage = WalletStorage({'wallet_path': filename})
265 if storage.file_exists:
266 QMessageBox.critical(None, "Error", _("File exists"))
269 wizard = installwizard.InstallWizard(self.config, self.network, storage)
270 wallet = wizard.run('new')
272 self.load_wallet(wallet)
276 def init_menubar(self):
279 file_menu = menubar.addMenu(_("&File"))
280 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
281 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
282 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
283 file_menu.addAction(_("&Quit"), self.close)
285 wallet_menu = menubar.addMenu(_("&Wallet"))
286 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
287 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
289 wallet_menu.addSeparator()
291 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
292 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
293 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
295 wallet_menu.addSeparator()
296 labels_menu = wallet_menu.addMenu(_("&Labels"))
297 labels_menu.addAction(_("&Import"), self.do_import_labels)
298 labels_menu.addAction(_("&Export"), self.do_export_labels)
300 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
301 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
302 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
303 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
304 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
306 tools_menu = menubar.addMenu(_("&Tools"))
308 # Settings / Preferences are all reserved keywords in OSX using this as work around
309 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
310 tools_menu.addAction(_("&Network"), self.run_network_dialog)
311 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
312 tools_menu.addSeparator()
313 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
314 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
315 tools_menu.addSeparator()
317 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
318 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
319 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
321 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
322 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
323 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
324 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
326 help_menu = menubar.addMenu(_("&Help"))
327 help_menu.addAction(_("&About"), self.show_about)
328 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
329 help_menu.addSeparator()
330 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
331 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
333 self.setMenuBar(menubar)
335 def show_about(self):
336 QMessageBox.about(self, "Electrum",
337 _("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."))
339 def show_report_bug(self):
340 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
341 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
344 def notify_transactions(self):
345 if not self.network or not self.network.is_connected():
348 print_error("Notifying GUI")
349 if len(self.network.pending_transactions_for_notifications) > 0:
350 # Combine the transactions if there are more then three
351 tx_amount = len(self.network.pending_transactions_for_notifications)
354 for tx in self.network.pending_transactions_for_notifications:
355 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
359 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
360 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
362 self.network.pending_transactions_for_notifications = []
364 for tx in self.network.pending_transactions_for_notifications:
366 self.network.pending_transactions_for_notifications.remove(tx)
367 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
369 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
371 def notify(self, message):
372 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
376 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
377 def getOpenFileName(self, title, filter = ""):
378 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
379 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
380 if fileName and directory != os.path.dirname(fileName):
381 self.config.set_key('io_dir', os.path.dirname(fileName), True)
384 def getSaveFileName(self, title, filename, filter = ""):
385 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
386 path = os.path.join( directory, filename )
387 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
388 if fileName and directory != os.path.dirname(fileName):
389 self.config.set_key('io_dir', os.path.dirname(fileName), True)
393 QMainWindow.close(self)
394 run_hook('close_main_window')
396 def connect_slots(self, sender):
397 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
398 self.previous_payto_e=''
400 def timer_actions(self):
401 if self.need_update.is_set():
403 self.need_update.clear()
404 run_hook('timer_actions')
406 def format_amount(self, x, is_diff=False, whitespaces=False):
407 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
409 def read_amount(self, x):
410 if x in['.', '']: return None
411 p = pow(10, self.decimal_point)
412 return int( p * Decimal(x) )
415 assert self.decimal_point in [5,8]
416 return "BTC" if self.decimal_point == 8 else "mBTC"
419 def update_status(self):
420 if self.network is None or not self.network.is_running():
422 icon = QIcon(":icons/status_disconnected.png")
424 elif self.network.is_connected():
425 if not self.wallet.up_to_date:
426 text = _("Synchronizing...")
427 icon = QIcon(":icons/status_waiting.png")
428 elif self.network.server_lag > 1:
429 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
430 icon = QIcon(":icons/status_lagging.png")
432 c, u = self.wallet.get_account_balance(self.current_account)
433 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
434 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
436 # append fiat balance and price from exchange rate plugin
438 run_hook('get_fiat_status_text', c+u, r)
443 self.tray.setToolTip(text)
444 icon = QIcon(":icons/status_connected.png")
446 text = _("Not connected")
447 icon = QIcon(":icons/status_disconnected.png")
449 self.balance_label.setText(text)
450 self.status_button.setIcon( icon )
453 def update_wallet(self):
455 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
456 self.update_history_tab()
457 self.update_receive_tab()
458 self.update_contacts_tab()
459 self.update_completions()
462 def create_history_tab(self):
463 self.history_list = l = MyTreeWidget(self)
465 for i,width in enumerate(self.column_widths['history']):
466 l.setColumnWidth(i, width)
467 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
468 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
469 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
471 l.customContextMenuRequested.connect(self.create_history_menu)
475 def create_history_menu(self, position):
476 self.history_list.selectedIndexes()
477 item = self.history_list.currentItem()
478 be = self.config.get('block_explorer', 'Blockchain.info')
479 if be == 'Blockchain.info':
480 block_explorer = 'https://blockchain.info/tx/'
481 elif be == 'Blockr.io':
482 block_explorer = 'https://blockr.io/tx/info/'
483 elif be == 'Insight.is':
484 block_explorer = 'http://live.insight.is/tx/'
486 tx_hash = str(item.data(0, Qt.UserRole).toString())
487 if not tx_hash: return
489 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
490 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
491 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
492 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
493 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
496 def show_transaction(self, tx):
497 import transaction_dialog
498 d = transaction_dialog.TxDialog(tx, self)
501 def tx_label_clicked(self, item, column):
502 if column==2 and item.isSelected():
504 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
505 self.history_list.editItem( item, column )
506 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
509 def tx_label_changed(self, item, column):
513 tx_hash = str(item.data(0, Qt.UserRole).toString())
514 tx = self.wallet.transactions.get(tx_hash)
515 text = unicode( item.text(2) )
516 self.wallet.set_label(tx_hash, text)
518 item.setForeground(2, QBrush(QColor('black')))
520 text = self.wallet.get_default_label(tx_hash)
521 item.setText(2, text)
522 item.setForeground(2, QBrush(QColor('gray')))
526 def edit_label(self, is_recv):
527 l = self.receive_list if is_recv else self.contacts_list
528 item = l.currentItem()
529 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
530 l.editItem( item, 1 )
531 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
535 def address_label_clicked(self, item, column, l, column_addr, column_label):
536 if column == column_label and item.isSelected():
537 is_editable = item.data(0, 32).toBool()
540 addr = unicode( item.text(column_addr) )
541 label = unicode( item.text(column_label) )
542 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
543 l.editItem( item, column )
544 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
547 def address_label_changed(self, item, column, l, column_addr, column_label):
548 if column == column_label:
549 addr = unicode( item.text(column_addr) )
550 text = unicode( item.text(column_label) )
551 is_editable = item.data(0, 32).toBool()
555 changed = self.wallet.set_label(addr, text)
557 self.update_history_tab()
558 self.update_completions()
560 self.current_item_changed(item)
562 run_hook('item_changed', item, column)
565 def current_item_changed(self, a):
566 run_hook('current_item_changed', a)
570 def update_history_tab(self):
572 self.history_list.clear()
573 for item in self.wallet.get_tx_history(self.current_account):
574 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
575 time_str = _("unknown")
578 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
580 time_str = _("error")
583 time_str = 'unverified'
584 icon = QIcon(":icons/unconfirmed.png")
587 icon = QIcon(":icons/unconfirmed.png")
589 icon = QIcon(":icons/clock%d.png"%conf)
591 icon = QIcon(":icons/confirmed.png")
593 if value is not None:
594 v_str = self.format_amount(value, True, whitespaces=True)
598 balance_str = self.format_amount(balance, whitespaces=True)
601 label, is_default_label = self.wallet.get_label(tx_hash)
603 label = _('Pruned transaction outputs')
604 is_default_label = False
606 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
607 item.setFont(2, QFont(MONOSPACE_FONT))
608 item.setFont(3, QFont(MONOSPACE_FONT))
609 item.setFont(4, QFont(MONOSPACE_FONT))
611 item.setForeground(3, QBrush(QColor("#BC1E1E")))
613 item.setData(0, Qt.UserRole, tx_hash)
614 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
616 item.setForeground(2, QBrush(QColor('grey')))
618 item.setIcon(0, icon)
619 self.history_list.insertTopLevelItem(0,item)
622 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
623 run_hook('history_tab_update')
626 def create_send_tab(self):
631 grid.setColumnMinimumWidth(3,300)
632 grid.setColumnStretch(5,1)
635 self.payto_e = QLineEdit()
636 grid.addWidget(QLabel(_('Pay to')), 1, 0)
637 grid.addWidget(self.payto_e, 1, 1, 1, 3)
639 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)
641 completer = QCompleter()
642 completer.setCaseSensitivity(False)
643 self.payto_e.setCompleter(completer)
644 completer.setModel(self.completions)
646 self.message_e = QLineEdit()
647 grid.addWidget(QLabel(_('Description')), 2, 0)
648 grid.addWidget(self.message_e, 2, 1, 1, 3)
649 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)
651 self.from_label = QLabel(_('From'))
652 grid.addWidget(self.from_label, 3, 0)
653 self.from_list = QTreeWidget(self)
654 self.from_list.setColumnCount(2)
655 self.from_list.setColumnWidth(0, 350)
656 self.from_list.setColumnWidth(1, 50)
657 self.from_list.setHeaderHidden (True)
658 self.from_list.setMaximumHeight(80)
659 grid.addWidget(self.from_list, 3, 1, 1, 3)
660 self.set_pay_from([])
662 self.amount_e = AmountEdit(self.base_unit)
663 grid.addWidget(QLabel(_('Amount')), 4, 0)
664 grid.addWidget(self.amount_e, 4, 1, 1, 2)
665 grid.addWidget(HelpButton(
666 _('Amount to be sent.') + '\n\n' \
667 + _('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.') \
668 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
670 self.fee_e = AmountEdit(self.base_unit)
671 grid.addWidget(QLabel(_('Fee')), 5, 0)
672 grid.addWidget(self.fee_e, 5, 1, 1, 2)
673 grid.addWidget(HelpButton(
674 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
675 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
676 + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 5, 3)
678 run_hook('exchange_rate_button', grid)
680 self.send_button = EnterButton(_("Send"), self.do_send)
681 grid.addWidget(self.send_button, 6, 1)
683 b = EnterButton(_("Clear"),self.do_clear)
684 grid.addWidget(b, 6, 2)
686 self.payto_sig = QLabel('')
687 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
689 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
690 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
699 def entry_changed( is_fee ):
700 self.funds_error = False
702 if self.amount_e.is_shortcut:
703 self.amount_e.is_shortcut = False
704 sendable = self.get_sendable_balance()
705 # there is only one output because we are completely spending inputs
706 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
707 fee = self.wallet.estimated_fee(inputs, 1)
709 self.amount_e.setText( self.format_amount(amount) )
710 self.fee_e.setText( self.format_amount( fee ) )
713 amount = self.read_amount(str(self.amount_e.text()))
714 fee = self.read_amount(str(self.fee_e.text()))
716 if not is_fee: fee = None
719 # assume that there will be 2 outputs (one for change)
720 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
722 self.fee_e.setText( self.format_amount( fee ) )
725 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
729 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
730 self.funds_error = True
731 text = _( "Not enough funds" )
732 c, u = self.wallet.get_frozen_balance()
733 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
735 self.statusBar().showMessage(text)
736 self.amount_e.setPalette(palette)
737 self.fee_e.setPalette(palette)
739 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
740 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
742 run_hook('create_send_tab', grid)
746 def set_pay_from(self, l):
748 self.from_list.clear()
749 self.from_label.setHidden(len(self.pay_from) == 0)
750 self.from_list.setHidden(len(self.pay_from) == 0)
751 for addr in self.pay_from:
752 c, u = self.wallet.get_addr_balance(addr)
753 balance = self.format_amount(c + u)
754 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
757 def update_completions(self):
759 for addr,label in self.wallet.labels.items():
760 if addr in self.wallet.addressbook:
761 l.append( label + ' <' + addr + '>')
763 run_hook('update_completions', l)
764 self.completions.setStringList(l)
768 return lambda s, *args: s.do_protect(func, args)
772 label = unicode( self.message_e.text() )
774 if self.payment_request:
775 outputs = self.payment_request.outputs
776 amount = self.payment_request.get_amount()
779 r = unicode( self.payto_e.text() )
782 # label or alias, with address in brackets
783 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
784 to_address = m.group(2) if m else r
785 if not is_valid(to_address):
786 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
790 amount = self.read_amount(unicode( self.amount_e.text()))
792 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
795 outputs = [(to_address, amount)]
798 fee = self.read_amount(unicode( self.fee_e.text()))
800 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
803 confirm_amount = self.config.get('confirm_amount', 100000000)
804 if amount >= confirm_amount:
805 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
808 confirm_fee = self.config.get('confirm_fee', 100000)
809 if fee >= confirm_fee:
810 if not self.question(_("The fee for this transaction seems unusually high.\nAre you really sure you want to pay %(fee)s in fees?")%{ 'fee' : self.format_amount(fee) + ' '+ self.base_unit()}):
813 self.send_tx(outputs, fee, label)
816 def waiting_dialog(self, message):
818 d.setWindowTitle('Please wait')
820 vbox = QVBoxLayout(d)
827 def send_tx(self, outputs, fee, label, password):
829 # first, create an unsigned tx
830 domain = self.get_payment_sources()
832 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
834 except Exception as e:
835 traceback.print_exc(file=sys.stdout)
836 self.show_message(str(e))
839 # call hook to see if plugin needs gui interaction
840 run_hook('send_tx', tx)
846 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
847 self.wallet.sign_transaction(tx, keypairs, password)
848 self.signed_tx_data = (tx, fee, label)
849 self.emit(SIGNAL('send_tx2'))
850 self.tx_wait_dialog = self.waiting_dialog('Signing..')
851 threading.Thread(target=sign_thread).start()
856 tx, fee, label = self.signed_tx_data
857 self.tx_wait_dialog.accept()
860 self.show_message(tx.error)
863 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
864 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
868 self.wallet.set_label(tx.hash(), label)
870 if not tx.is_complete() or self.config.get('show_before_broadcast'):
871 self.show_transaction(tx)
874 def broadcast_thread():
875 if self.payment_request:
876 refund_address = self.wallet.addresses()[0]
877 self.payment_request.send_ack(str(tx), refund_address)
878 self.payment_request = None
879 # note: BIP 70 recommends not broadcasting the tx to the network and letting the merchant do that
880 self.tx_broadcast_result = self.wallet.sendtx(tx)
881 self.emit(SIGNAL('send_tx3'))
883 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
884 threading.Thread(target=broadcast_thread).start()
889 self.tx_broadcast_dialog.accept()
890 status, msg = self.tx_broadcast_result
892 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
895 QMessageBox.warning(self, _('Error'), msg, _('OK'))
899 def payment_request_ok(self):
900 style = "QWidget { background-color:none;border:none;}"
901 self.payto_e.setText(self.payment_request.domain)
902 self.payto_e.setReadOnly(True)
903 self.payto_e.setStyleSheet(style)
904 self.amount_e.setText(self.format_amount(self.payment_request.get_amount()))
905 self.amount_e.setReadOnly(True)
906 self.amount_e.setStyleSheet(style)
909 def set_send(self, address, amount, label, message):
911 if label and self.wallet.labels.get(address) != label:
912 if self.question('Give label "%s" to address %s ?'%(label,address)):
913 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
914 self.wallet.addressbook.append(address)
915 self.wallet.set_label(address, label)
917 self.tabs.setCurrentIndex(1)
918 label = self.wallet.labels.get(address)
919 m_addr = label + ' <'+ address +'>' if label else address
920 self.payto_e.setText(m_addr)
922 self.message_e.setText(message)
924 self.amount_e.setText(amount)
928 self.payto_sig.setVisible(False)
929 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
931 self.set_frozen(e,False)
934 self.set_pay_from([])
937 def set_frozen(self,entry,frozen):
939 entry.setReadOnly(True)
940 entry.setFrame(False)
942 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
943 entry.setPalette(palette)
945 entry.setReadOnly(False)
948 palette.setColor(entry.backgroundRole(), QColor('white'))
949 entry.setPalette(palette)
952 def set_addrs_frozen(self,addrs,freeze):
954 if not addr: continue
955 if addr in self.wallet.frozen_addresses and not freeze:
956 self.wallet.unfreeze(addr)
957 elif addr not in self.wallet.frozen_addresses and freeze:
958 self.wallet.freeze(addr)
959 self.update_receive_tab()
963 def create_list_tab(self, headers):
964 "generic tab creation method"
965 l = MyTreeWidget(self)
966 l.setColumnCount( len(headers) )
967 l.setHeaderLabels( headers )
977 vbox.addWidget(buttons)
982 buttons.setLayout(hbox)
987 def create_receive_tab(self):
988 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
989 l.setContextMenuPolicy(Qt.CustomContextMenu)
990 l.customContextMenuRequested.connect(self.create_receive_menu)
991 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
992 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
993 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
994 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
995 self.receive_list = l
996 self.receive_buttons_hbox = hbox
1003 def save_column_widths(self):
1004 self.column_widths["receive"] = []
1005 for i in range(self.receive_list.columnCount() -1):
1006 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1008 self.column_widths["history"] = []
1009 for i in range(self.history_list.columnCount() - 1):
1010 self.column_widths["history"].append(self.history_list.columnWidth(i))
1012 self.column_widths["contacts"] = []
1013 for i in range(self.contacts_list.columnCount() - 1):
1014 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1016 self.config.set_key("column_widths_2", self.column_widths, True)
1019 def create_contacts_tab(self):
1020 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1021 l.setContextMenuPolicy(Qt.CustomContextMenu)
1022 l.customContextMenuRequested.connect(self.create_contact_menu)
1023 for i,width in enumerate(self.column_widths['contacts']):
1024 l.setColumnWidth(i, width)
1026 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1027 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1028 self.contacts_list = l
1029 self.contacts_buttons_hbox = hbox
1034 def delete_imported_key(self, addr):
1035 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1036 self.wallet.delete_imported_key(addr)
1037 self.update_receive_tab()
1038 self.update_history_tab()
1040 def edit_account_label(self, k):
1041 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1043 label = unicode(text)
1044 self.wallet.set_label(k,label)
1045 self.update_receive_tab()
1047 def account_set_expanded(self, item, k, b):
1049 self.accounts_expanded[k] = b
1051 def create_account_menu(self, position, k, item):
1053 if item.isExpanded():
1054 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1056 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1057 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1058 if self.wallet.seed_version > 4:
1059 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1060 if self.wallet.account_is_pending(k):
1061 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1062 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1064 def delete_pending_account(self, k):
1065 self.wallet.delete_pending_account(k)
1066 self.update_receive_tab()
1068 def create_receive_menu(self, position):
1069 # fixme: this function apparently has a side effect.
1070 # if it is not called the menu pops up several times
1071 #self.receive_list.selectedIndexes()
1073 selected = self.receive_list.selectedItems()
1074 multi_select = len(selected) > 1
1075 addrs = [unicode(item.text(0)) for item in selected]
1076 if not multi_select:
1077 item = self.receive_list.itemAt(position)
1081 if not is_valid(addr):
1082 k = str(item.data(0,32).toString())
1084 self.create_account_menu(position, k, item)
1086 item.setExpanded(not item.isExpanded())
1090 if not multi_select:
1091 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1092 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1093 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1094 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1095 if not self.wallet.is_watching_only():
1096 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1097 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1098 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1099 if self.wallet.is_imported(addr):
1100 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1102 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1103 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1104 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1105 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1107 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1108 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1110 run_hook('receive_menu', menu, addrs)
1111 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1114 def get_sendable_balance(self):
1115 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1118 def get_payment_sources(self):
1120 return self.pay_from
1122 return self.wallet.get_account_addresses(self.current_account)
1125 def send_from_addresses(self, addrs):
1126 self.set_pay_from( addrs )
1127 self.tabs.setCurrentIndex(1)
1130 def payto(self, addr):
1132 label = self.wallet.labels.get(addr)
1133 m_addr = label + ' <' + addr + '>' if label else addr
1134 self.tabs.setCurrentIndex(1)
1135 self.payto_e.setText(m_addr)
1136 self.amount_e.setFocus()
1139 def delete_contact(self, x):
1140 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1141 self.wallet.delete_contact(x)
1142 self.wallet.set_label(x, None)
1143 self.update_history_tab()
1144 self.update_contacts_tab()
1145 self.update_completions()
1148 def create_contact_menu(self, position):
1149 item = self.contacts_list.itemAt(position)
1152 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1154 addr = unicode(item.text(0))
1155 label = unicode(item.text(1))
1156 is_editable = item.data(0,32).toBool()
1157 payto_addr = item.data(0,33).toString()
1158 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1159 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1160 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1162 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1163 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1165 run_hook('create_contact_menu', menu, item)
1166 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1169 def update_receive_item(self, item):
1170 item.setFont(0, QFont(MONOSPACE_FONT))
1171 address = str(item.data(0,0).toString())
1172 label = self.wallet.labels.get(address,'')
1173 item.setData(1,0,label)
1174 item.setData(0,32, True) # is editable
1176 run_hook('update_receive_item', address, item)
1178 if not self.wallet.is_mine(address): return
1180 c, u = self.wallet.get_addr_balance(address)
1181 balance = self.format_amount(c + u)
1182 item.setData(2,0,balance)
1184 if address in self.wallet.frozen_addresses:
1185 item.setBackgroundColor(0, QColor('lightblue'))
1188 def update_receive_tab(self):
1189 l = self.receive_list
1190 # extend the syntax for consistency
1191 l.addChild = l.addTopLevelItem
1194 for i,width in enumerate(self.column_widths['receive']):
1195 l.setColumnWidth(i, width)
1197 accounts = self.wallet.get_accounts()
1198 if self.current_account is None:
1199 account_items = sorted(accounts.items())
1201 account_items = [(self.current_account, accounts.get(self.current_account))]
1204 for k, account in account_items:
1206 if len(accounts) > 1:
1207 name = self.wallet.get_account_name(k)
1208 c,u = self.wallet.get_account_balance(k)
1209 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1210 l.addTopLevelItem(account_item)
1211 account_item.setExpanded(self.accounts_expanded.get(k, True))
1212 account_item.setData(0, 32, k)
1216 sequences = [0,1] if account.has_change() else [0]
1217 for is_change in sequences:
1218 if len(sequences) > 1:
1219 name = _("Receiving") if not is_change else _("Change")
1220 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1221 account_item.addChild(seq_item)
1223 seq_item.setExpanded(True)
1225 seq_item = account_item
1227 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1233 for address in account.get_addresses(is_change):
1234 h = self.wallet.history.get(address,[])
1238 if gap > self.wallet.gap_limit:
1243 c, u = self.wallet.get_addr_balance(address)
1244 num_tx = '*' if h == ['*'] else "%d"%len(h)
1246 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1247 self.update_receive_item(item)
1249 item.setBackgroundColor(1, QColor('red'))
1250 if len(h) > 0 and c == -u:
1252 seq_item.insertChild(0,used_item)
1254 used_item.addChild(item)
1256 seq_item.addChild(item)
1258 # we use column 1 because column 0 may be hidden
1259 l.setCurrentItem(l.topLevelItem(0),1)
1262 def update_contacts_tab(self):
1263 l = self.contacts_list
1266 for address in self.wallet.addressbook:
1267 label = self.wallet.labels.get(address,'')
1268 n = self.wallet.get_num_tx(address)
1269 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1270 item.setFont(0, QFont(MONOSPACE_FONT))
1271 # 32 = label can be edited (bool)
1272 item.setData(0,32, True)
1274 item.setData(0,33, address)
1275 l.addTopLevelItem(item)
1277 run_hook('update_contacts_tab', l)
1278 l.setCurrentItem(l.topLevelItem(0))
1282 def create_console_tab(self):
1283 from console import Console
1284 self.console = console = Console()
1288 def update_console(self):
1289 console = self.console
1290 console.history = self.config.get("console-history",[])
1291 console.history_index = len(console.history)
1293 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1294 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1296 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1298 def mkfunc(f, method):
1299 return lambda *args: apply( f, (method, args, self.password_dialog ))
1301 if m[0]=='_' or m in ['network','wallet']: continue
1302 methods[m] = mkfunc(c._run, m)
1304 console.updateNamespace(methods)
1307 def change_account(self,s):
1308 if s == _("All accounts"):
1309 self.current_account = None
1311 accounts = self.wallet.get_account_names()
1312 for k, v in accounts.items():
1314 self.current_account = k
1315 self.update_history_tab()
1316 self.update_status()
1317 self.update_receive_tab()
1319 def create_status_bar(self):
1322 sb.setFixedHeight(35)
1323 qtVersion = qVersion()
1325 self.balance_label = QLabel("")
1326 sb.addWidget(self.balance_label)
1328 from version_getter import UpdateLabel
1329 self.updatelabel = UpdateLabel(self.config, sb)
1331 self.account_selector = QComboBox()
1332 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1333 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1334 sb.addPermanentWidget(self.account_selector)
1336 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1337 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1339 self.lock_icon = QIcon()
1340 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1341 sb.addPermanentWidget( self.password_button )
1343 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1344 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1345 sb.addPermanentWidget( self.seed_button )
1346 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1347 sb.addPermanentWidget( self.status_button )
1349 run_hook('create_status_bar', (sb,))
1351 self.setStatusBar(sb)
1354 def update_lock_icon(self):
1355 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1356 self.password_button.setIcon( icon )
1359 def update_buttons_on_seed(self):
1360 if self.wallet.has_seed():
1361 self.seed_button.show()
1363 self.seed_button.hide()
1365 if not self.wallet.is_watching_only():
1366 self.password_button.show()
1367 self.send_button.setText(_("Send"))
1369 self.password_button.hide()
1370 self.send_button.setText(_("Create unsigned transaction"))
1373 def change_password_dialog(self):
1374 from password_dialog import PasswordDialog
1375 d = PasswordDialog(self.wallet, self)
1377 self.update_lock_icon()
1380 def new_contact_dialog(self):
1383 d.setWindowTitle(_("New Contact"))
1384 vbox = QVBoxLayout(d)
1385 vbox.addWidget(QLabel(_('New Contact')+':'))
1387 grid = QGridLayout()
1390 grid.addWidget(QLabel(_("Address")), 1, 0)
1391 grid.addWidget(line1, 1, 1)
1392 grid.addWidget(QLabel(_("Name")), 2, 0)
1393 grid.addWidget(line2, 2, 1)
1395 vbox.addLayout(grid)
1396 vbox.addLayout(ok_cancel_buttons(d))
1401 address = str(line1.text())
1402 label = unicode(line2.text())
1404 if not is_valid(address):
1405 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1408 self.wallet.add_contact(address)
1410 self.wallet.set_label(address, label)
1412 self.update_contacts_tab()
1413 self.update_history_tab()
1414 self.update_completions()
1415 self.tabs.setCurrentIndex(3)
1419 def new_account_dialog(self, password):
1421 dialog = QDialog(self)
1423 dialog.setWindowTitle(_("New Account"))
1425 vbox = QVBoxLayout()
1426 vbox.addWidget(QLabel(_('Account name')+':'))
1429 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1430 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1435 vbox.addLayout(ok_cancel_buttons(dialog))
1436 dialog.setLayout(vbox)
1440 name = str(e.text())
1443 self.wallet.create_pending_account(name, password)
1444 self.update_receive_tab()
1445 self.tabs.setCurrentIndex(2)
1450 def show_master_public_keys(self):
1452 dialog = QDialog(self)
1454 dialog.setWindowTitle(_("Master Public Keys"))
1456 main_layout = QGridLayout()
1457 mpk_dict = self.wallet.get_master_public_keys()
1459 for key, value in mpk_dict.items():
1460 main_layout.addWidget(QLabel(key), i, 0)
1461 mpk_text = QTextEdit()
1462 mpk_text.setReadOnly(True)
1463 mpk_text.setMaximumHeight(170)
1464 mpk_text.setText(value)
1465 main_layout.addWidget(mpk_text, i + 1, 0)
1468 vbox = QVBoxLayout()
1469 vbox.addLayout(main_layout)
1470 vbox.addLayout(close_button(dialog))
1472 dialog.setLayout(vbox)
1477 def show_seed_dialog(self, password):
1478 if not self.wallet.has_seed():
1479 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1483 mnemonic = self.wallet.get_mnemonic(password)
1485 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1487 from seed_dialog import SeedDialog
1488 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1493 def show_qrcode(self, data, title = _("QR code")):
1497 d.setWindowTitle(title)
1498 d.setMinimumSize(270, 300)
1499 vbox = QVBoxLayout()
1500 qrw = QRCodeWidget(data)
1501 vbox.addWidget(qrw, 1)
1502 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1503 hbox = QHBoxLayout()
1506 filename = os.path.join(self.config.path, "qrcode.bmp")
1509 bmp.save_qrcode(qrw.qr, filename)
1510 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1512 def copy_to_clipboard():
1513 bmp.save_qrcode(qrw.qr, filename)
1514 self.app.clipboard().setImage(QImage(filename))
1515 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1517 b = QPushButton(_("Copy"))
1519 b.clicked.connect(copy_to_clipboard)
1521 b = QPushButton(_("Save"))
1523 b.clicked.connect(print_qr)
1525 b = QPushButton(_("Close"))
1527 b.clicked.connect(d.accept)
1530 vbox.addLayout(hbox)
1535 def do_protect(self, func, args):
1536 if self.wallet.use_encryption:
1537 password = self.password_dialog()
1543 if args != (False,):
1544 args = (self,) + args + (password,)
1546 args = (self,password)
1550 def show_public_keys(self, address):
1551 if not address: return
1553 pubkey_list = self.wallet.get_public_keys(address)
1554 except Exception as e:
1555 traceback.print_exc(file=sys.stdout)
1556 self.show_message(str(e))
1560 d.setMinimumSize(600, 200)
1562 vbox = QVBoxLayout()
1563 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1564 vbox.addWidget( QLabel(_("Public key") + ':'))
1566 keys.setReadOnly(True)
1567 keys.setText('\n'.join(pubkey_list))
1568 vbox.addWidget(keys)
1569 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1570 vbox.addLayout(close_button(d))
1575 def show_private_key(self, address, password):
1576 if not address: return
1578 pk_list = self.wallet.get_private_key(address, password)
1579 except Exception as e:
1580 traceback.print_exc(file=sys.stdout)
1581 self.show_message(str(e))
1585 d.setMinimumSize(600, 200)
1587 vbox = QVBoxLayout()
1588 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1589 vbox.addWidget( QLabel(_("Private key") + ':'))
1591 keys.setReadOnly(True)
1592 keys.setText('\n'.join(pk_list))
1593 vbox.addWidget(keys)
1594 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1595 vbox.addLayout(close_button(d))
1601 def do_sign(self, address, message, signature, password):
1602 message = unicode(message.toPlainText())
1603 message = message.encode('utf-8')
1605 sig = self.wallet.sign_message(str(address.text()), message, password)
1606 signature.setText(sig)
1607 except Exception as e:
1608 self.show_message(str(e))
1610 def do_verify(self, address, message, signature):
1611 message = unicode(message.toPlainText())
1612 message = message.encode('utf-8')
1613 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1614 self.show_message(_("Signature verified"))
1616 self.show_message(_("Error: wrong signature"))
1619 def sign_verify_message(self, address=''):
1622 d.setWindowTitle(_('Sign/verify Message'))
1623 d.setMinimumSize(410, 290)
1625 layout = QGridLayout(d)
1627 message_e = QTextEdit()
1628 layout.addWidget(QLabel(_('Message')), 1, 0)
1629 layout.addWidget(message_e, 1, 1)
1630 layout.setRowStretch(2,3)
1632 address_e = QLineEdit()
1633 address_e.setText(address)
1634 layout.addWidget(QLabel(_('Address')), 2, 0)
1635 layout.addWidget(address_e, 2, 1)
1637 signature_e = QTextEdit()
1638 layout.addWidget(QLabel(_('Signature')), 3, 0)
1639 layout.addWidget(signature_e, 3, 1)
1640 layout.setRowStretch(3,1)
1642 hbox = QHBoxLayout()
1644 b = QPushButton(_("Sign"))
1645 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1648 b = QPushButton(_("Verify"))
1649 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1652 b = QPushButton(_("Close"))
1653 b.clicked.connect(d.accept)
1655 layout.addLayout(hbox, 4, 1)
1660 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1662 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1663 message_e.setText(decrypted)
1664 except Exception as e:
1665 self.show_message(str(e))
1668 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1669 message = unicode(message_e.toPlainText())
1670 message = message.encode('utf-8')
1672 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1673 encrypted_e.setText(encrypted)
1674 except Exception as e:
1675 self.show_message(str(e))
1679 def encrypt_message(self, address = ''):
1682 d.setWindowTitle(_('Encrypt/decrypt Message'))
1683 d.setMinimumSize(610, 490)
1685 layout = QGridLayout(d)
1687 message_e = QTextEdit()
1688 layout.addWidget(QLabel(_('Message')), 1, 0)
1689 layout.addWidget(message_e, 1, 1)
1690 layout.setRowStretch(2,3)
1692 pubkey_e = QLineEdit()
1694 pubkey = self.wallet.getpubkeys(address)[0]
1695 pubkey_e.setText(pubkey)
1696 layout.addWidget(QLabel(_('Public key')), 2, 0)
1697 layout.addWidget(pubkey_e, 2, 1)
1699 encrypted_e = QTextEdit()
1700 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1701 layout.addWidget(encrypted_e, 3, 1)
1702 layout.setRowStretch(3,1)
1704 hbox = QHBoxLayout()
1705 b = QPushButton(_("Encrypt"))
1706 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1709 b = QPushButton(_("Decrypt"))
1710 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1713 b = QPushButton(_("Close"))
1714 b.clicked.connect(d.accept)
1717 layout.addLayout(hbox, 4, 1)
1721 def question(self, msg):
1722 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1724 def show_message(self, msg):
1725 QMessageBox.information(self, _('Message'), msg, _('OK'))
1727 def password_dialog(self ):
1730 d.setWindowTitle(_("Enter Password"))
1735 vbox = QVBoxLayout()
1736 msg = _('Please enter your password')
1737 vbox.addWidget(QLabel(msg))
1739 grid = QGridLayout()
1741 grid.addWidget(QLabel(_('Password')), 1, 0)
1742 grid.addWidget(pw, 1, 1)
1743 vbox.addLayout(grid)
1745 vbox.addLayout(ok_cancel_buttons(d))
1748 run_hook('password_dialog', pw, grid, 1)
1749 if not d.exec_(): return
1750 return unicode(pw.text())
1759 def tx_from_text(self, txt):
1760 "json or raw hexadecimal"
1763 tx = Transaction(txt)
1769 tx_dict = json.loads(str(txt))
1770 assert "hex" in tx_dict.keys()
1771 assert "complete" in tx_dict.keys()
1772 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1773 if not tx_dict["complete"]:
1774 assert "input_info" in tx_dict.keys()
1775 input_info = json.loads(tx_dict['input_info'])
1776 tx.add_input_info(input_info)
1781 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1785 def read_tx_from_file(self):
1786 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1790 with open(fileName, "r") as f:
1791 file_content = f.read()
1792 except (ValueError, IOError, os.error), reason:
1793 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1795 return self.tx_from_text(file_content)
1799 def sign_raw_transaction(self, tx, input_info, password):
1800 self.wallet.signrawtransaction(tx, input_info, [], password)
1802 def do_process_from_text(self):
1803 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1806 tx = self.tx_from_text(text)
1808 self.show_transaction(tx)
1810 def do_process_from_file(self):
1811 tx = self.read_tx_from_file()
1813 self.show_transaction(tx)
1815 def do_process_from_txid(self):
1816 from electrum import transaction
1817 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1819 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1821 tx = transaction.Transaction(r)
1823 self.show_transaction(tx)
1825 self.show_message("unknown transaction")
1827 def do_process_from_csvReader(self, csvReader):
1832 for position, row in enumerate(csvReader):
1834 if not is_valid(address):
1835 errors.append((position, address))
1837 amount = Decimal(row[1])
1838 amount = int(100000000*amount)
1839 outputs.append((address, amount))
1840 except (ValueError, IOError, os.error), reason:
1841 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1845 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1846 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1850 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1851 except Exception as e:
1852 self.show_message(str(e))
1855 self.show_transaction(tx)
1857 def do_process_from_csv_file(self):
1858 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1862 with open(fileName, "r") as f:
1863 csvReader = csv.reader(f)
1864 self.do_process_from_csvReader(csvReader)
1865 except (ValueError, IOError, os.error), reason:
1866 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1869 def do_process_from_csv_text(self):
1870 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1871 + _("Format: address, amount. One output per line"), _("Load CSV"))
1874 f = StringIO.StringIO(text)
1875 csvReader = csv.reader(f)
1876 self.do_process_from_csvReader(csvReader)
1881 def export_privkeys_dialog(self, password):
1882 if self.wallet.is_watching_only():
1883 self.show_message(_("This is a watching-only wallet"))
1887 d.setWindowTitle(_('Private keys'))
1888 d.setMinimumSize(850, 300)
1889 vbox = QVBoxLayout(d)
1891 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1892 _("Exposing a single private key can compromise your entire wallet!"),
1893 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1894 vbox.addWidget(QLabel(msg))
1900 defaultname = 'electrum-private-keys.csv'
1901 select_msg = _('Select file to export your private keys to')
1902 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1903 vbox.addLayout(hbox)
1905 h, b = ok_cancel_buttons2(d, _('Export'))
1910 addresses = self.wallet.addresses(True)
1912 def privkeys_thread():
1913 for addr in addresses:
1917 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1918 d.emit(SIGNAL('computing_privkeys'))
1919 d.emit(SIGNAL('show_privkeys'))
1921 def show_privkeys():
1922 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1926 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1927 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1928 threading.Thread(target=privkeys_thread).start()
1934 filename = filename_e.text()
1939 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1940 except (IOError, os.error), reason:
1941 export_error_label = _("Electrum was unable to produce a private key-export.")
1942 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1944 except Exception as e:
1945 self.show_message(str(e))
1948 self.show_message(_("Private keys exported."))
1951 def do_export_privkeys(self, fileName, pklist, is_csv):
1952 with open(fileName, "w+") as f:
1954 transaction = csv.writer(f)
1955 transaction.writerow(["address", "private_key"])
1956 for addr, pk in pklist.items():
1957 transaction.writerow(["%34s"%addr,pk])
1960 f.write(json.dumps(pklist, indent = 4))
1963 def do_import_labels(self):
1964 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1965 if not labelsFile: return
1967 f = open(labelsFile, 'r')
1970 for key, value in json.loads(data).items():
1971 self.wallet.set_label(key, value)
1972 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1973 except (IOError, os.error), reason:
1974 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1977 def do_export_labels(self):
1978 labels = self.wallet.labels
1980 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1982 with open(fileName, 'w+') as f:
1983 json.dump(labels, f)
1984 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1985 except (IOError, os.error), reason:
1986 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1989 def export_history_dialog(self):
1992 d.setWindowTitle(_('Export History'))
1993 d.setMinimumSize(400, 200)
1994 vbox = QVBoxLayout(d)
1996 defaultname = os.path.expanduser('~/electrum-history.csv')
1997 select_msg = _('Select file to export your wallet transactions to')
1999 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2000 vbox.addLayout(hbox)
2004 h, b = ok_cancel_buttons2(d, _('Export'))
2009 filename = filename_e.text()
2014 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2015 except (IOError, os.error), reason:
2016 export_error_label = _("Electrum was unable to produce a transaction export.")
2017 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2020 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2023 def do_export_history(self, wallet, fileName, is_csv):
2024 history = wallet.get_tx_history()
2026 for item in history:
2027 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2029 if timestamp is not None:
2031 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2032 except [RuntimeError, TypeError, NameError] as reason:
2033 time_string = "unknown"
2036 time_string = "unknown"
2038 time_string = "pending"
2040 if value is not None:
2041 value_string = format_satoshis(value, True)
2046 fee_string = format_satoshis(fee, True)
2051 label, is_default_label = wallet.get_label(tx_hash)
2052 label = label.encode('utf-8')
2056 balance_string = format_satoshis(balance, False)
2058 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2060 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2062 with open(fileName, "w+") as f:
2064 transaction = csv.writer(f)
2065 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2067 transaction.writerow(line)
2070 f.write(json.dumps(lines, indent = 4))
2073 def sweep_key_dialog(self):
2075 d.setWindowTitle(_('Sweep private keys'))
2076 d.setMinimumSize(600, 300)
2078 vbox = QVBoxLayout(d)
2079 vbox.addWidget(QLabel(_("Enter private keys")))
2081 keys_e = QTextEdit()
2082 keys_e.setTabChangesFocus(True)
2083 vbox.addWidget(keys_e)
2085 h, address_e = address_field(self.wallet.addresses())
2089 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2090 vbox.addLayout(hbox)
2091 button.setEnabled(False)
2094 addr = str(address_e.text())
2095 if bitcoin.is_address(addr):
2099 pk = str(keys_e.toPlainText()).strip()
2100 if Wallet.is_private_key(pk):
2103 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2104 keys_e.textChanged.connect(f)
2105 address_e.textChanged.connect(f)
2109 fee = self.wallet.fee
2110 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2111 self.show_transaction(tx)
2115 def do_import_privkey(self, password):
2116 if not self.wallet.imported_keys:
2117 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2118 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2119 + _('Are you sure you understand what you are doing?'), 3, 4)
2122 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2125 text = str(text).split()
2130 addr = self.wallet.import_key(key, password)
2131 except Exception as e:
2137 addrlist.append(addr)
2139 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2141 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2142 self.update_receive_tab()
2143 self.update_history_tab()
2146 def settings_dialog(self):
2148 d.setWindowTitle(_('Electrum Settings'))
2150 vbox = QVBoxLayout()
2151 grid = QGridLayout()
2152 grid.setColumnStretch(0,1)
2154 nz_label = QLabel(_('Display zeros') + ':')
2155 grid.addWidget(nz_label, 0, 0)
2156 nz_e = AmountEdit(None,True)
2157 nz_e.setText("%d"% self.num_zeros)
2158 grid.addWidget(nz_e, 0, 1)
2159 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2160 grid.addWidget(HelpButton(msg), 0, 2)
2161 if not self.config.is_modifiable('num_zeros'):
2162 for w in [nz_e, nz_label]: w.setEnabled(False)
2164 lang_label=QLabel(_('Language') + ':')
2165 grid.addWidget(lang_label, 1, 0)
2166 lang_combo = QComboBox()
2167 from electrum.i18n import languages
2168 lang_combo.addItems(languages.values())
2170 index = languages.keys().index(self.config.get("language",''))
2173 lang_combo.setCurrentIndex(index)
2174 grid.addWidget(lang_combo, 1, 1)
2175 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2176 if not self.config.is_modifiable('language'):
2177 for w in [lang_combo, lang_label]: w.setEnabled(False)
2180 fee_label = QLabel(_('Transaction fee') + ':')
2181 grid.addWidget(fee_label, 2, 0)
2182 fee_e = AmountEdit(self.base_unit)
2183 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2184 grid.addWidget(fee_e, 2, 1)
2185 msg = _('Fee per kilobyte of transaction.') + ' ' \
2186 + _('Recommended value') + ': ' + self.format_amount(20000)
2187 grid.addWidget(HelpButton(msg), 2, 2)
2188 if not self.config.is_modifiable('fee_per_kb'):
2189 for w in [fee_e, fee_label]: w.setEnabled(False)
2191 units = ['BTC', 'mBTC']
2192 unit_label = QLabel(_('Base unit') + ':')
2193 grid.addWidget(unit_label, 3, 0)
2194 unit_combo = QComboBox()
2195 unit_combo.addItems(units)
2196 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2197 grid.addWidget(unit_combo, 3, 1)
2198 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2199 + '\n1BTC=1000mBTC.\n' \
2200 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2202 usechange_cb = QCheckBox(_('Use change addresses'))
2203 usechange_cb.setChecked(self.wallet.use_change)
2204 grid.addWidget(usechange_cb, 4, 0)
2205 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2206 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2208 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2209 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2210 grid.addWidget(block_ex_label, 5, 0)
2211 block_ex_combo = QComboBox()
2212 block_ex_combo.addItems(block_explorers)
2213 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2214 grid.addWidget(block_ex_combo, 5, 1)
2215 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2217 show_tx = self.config.get('show_before_broadcast', False)
2218 showtx_cb = QCheckBox(_('Show before broadcast'))
2219 showtx_cb.setChecked(show_tx)
2220 grid.addWidget(showtx_cb, 6, 0)
2221 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2223 vbox.addLayout(grid)
2225 vbox.addLayout(ok_cancel_buttons(d))
2229 if not d.exec_(): return
2231 fee = unicode(fee_e.text())
2233 fee = self.read_amount(fee)
2235 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2238 self.wallet.set_fee(fee)
2240 nz = unicode(nz_e.text())
2245 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2248 if self.num_zeros != nz:
2250 self.config.set_key('num_zeros', nz, True)
2251 self.update_history_tab()
2252 self.update_receive_tab()
2254 usechange_result = usechange_cb.isChecked()
2255 if self.wallet.use_change != usechange_result:
2256 self.wallet.use_change = usechange_result
2257 self.wallet.storage.put('use_change', self.wallet.use_change)
2259 if showtx_cb.isChecked() != show_tx:
2260 self.config.set_key('show_before_broadcast', not show_tx)
2262 unit_result = units[unit_combo.currentIndex()]
2263 if self.base_unit() != unit_result:
2264 self.decimal_point = 8 if unit_result == 'BTC' else 5
2265 self.config.set_key('decimal_point', self.decimal_point, True)
2266 self.update_history_tab()
2267 self.update_status()
2269 need_restart = False
2271 lang_request = languages.keys()[lang_combo.currentIndex()]
2272 if lang_request != self.config.get('language'):
2273 self.config.set_key("language", lang_request, True)
2276 be_result = block_explorers[block_ex_combo.currentIndex()]
2277 self.config.set_key('block_explorer', be_result, True)
2279 run_hook('close_settings_dialog')
2282 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2285 def run_network_dialog(self):
2286 if not self.network:
2288 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2290 def closeEvent(self, event):
2292 self.config.set_key("is_maximized", self.isMaximized())
2293 if not self.isMaximized():
2295 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2296 self.save_column_widths()
2297 self.config.set_key("console-history", self.console.history[-50:], True)
2298 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2302 def plugins_dialog(self):
2303 from electrum.plugins import plugins
2306 d.setWindowTitle(_('Electrum Plugins'))
2309 vbox = QVBoxLayout(d)
2312 scroll = QScrollArea()
2313 scroll.setEnabled(True)
2314 scroll.setWidgetResizable(True)
2315 scroll.setMinimumSize(400,250)
2316 vbox.addWidget(scroll)
2320 w.setMinimumHeight(len(plugins)*35)
2322 grid = QGridLayout()
2323 grid.setColumnStretch(0,1)
2326 def do_toggle(cb, p, w):
2329 if w: w.setEnabled(r)
2331 def mk_toggle(cb, p, w):
2332 return lambda: do_toggle(cb,p,w)
2334 for i, p in enumerate(plugins):
2336 cb = QCheckBox(p.fullname())
2337 cb.setDisabled(not p.is_available())
2338 cb.setChecked(p.is_enabled())
2339 grid.addWidget(cb, i, 0)
2340 if p.requires_settings():
2341 w = p.settings_widget(self)
2342 w.setEnabled( p.is_enabled() )
2343 grid.addWidget(w, i, 1)
2346 cb.clicked.connect(mk_toggle(cb,p,w))
2347 grid.addWidget(HelpButton(p.description()), i, 2)
2349 print_msg(_("Error: cannot display plugin"), p)
2350 traceback.print_exc(file=sys.stdout)
2351 grid.setRowStretch(i+1,1)
2353 vbox.addLayout(close_button(d))
2358 def show_account_details(self, k):
2359 account = self.wallet.accounts[k]
2362 d.setWindowTitle(_('Account Details'))
2365 vbox = QVBoxLayout(d)
2366 name = self.wallet.get_account_name(k)
2367 label = QLabel('Name: ' + name)
2368 vbox.addWidget(label)
2370 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2372 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2374 vbox.addWidget(QLabel(_('Master Public Key:')))
2377 text.setReadOnly(True)
2378 text.setMaximumHeight(170)
2379 vbox.addWidget(text)
2381 mpk_text = '\n'.join( account.get_master_pubkeys() )
2382 text.setText(mpk_text)
2384 vbox.addLayout(close_button(d))