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 self.payto_e.setText(self.payment_request.domain)
901 self.payto_e.setReadOnly(True)
902 self.amount_e.setText(self.format_amount(self.payment_request.get_amount()))
903 self.amount_e.setReadOnly(True)
906 def set_send(self, address, amount, label, message):
908 if label and self.wallet.labels.get(address) != label:
909 if self.question('Give label "%s" to address %s ?'%(label,address)):
910 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
911 self.wallet.addressbook.append(address)
912 self.wallet.set_label(address, label)
914 self.tabs.setCurrentIndex(1)
915 label = self.wallet.labels.get(address)
916 m_addr = label + ' <'+ address +'>' if label else address
917 self.payto_e.setText(m_addr)
919 self.message_e.setText(message)
921 self.amount_e.setText(amount)
925 self.payto_sig.setVisible(False)
926 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
928 self.set_frozen(e,False)
930 self.set_pay_from([])
933 def set_frozen(self,entry,frozen):
935 entry.setReadOnly(True)
936 entry.setFrame(False)
938 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
939 entry.setPalette(palette)
941 entry.setReadOnly(False)
944 palette.setColor(entry.backgroundRole(), QColor('white'))
945 entry.setPalette(palette)
948 def set_addrs_frozen(self,addrs,freeze):
950 if not addr: continue
951 if addr in self.wallet.frozen_addresses and not freeze:
952 self.wallet.unfreeze(addr)
953 elif addr not in self.wallet.frozen_addresses and freeze:
954 self.wallet.freeze(addr)
955 self.update_receive_tab()
959 def create_list_tab(self, headers):
960 "generic tab creation method"
961 l = MyTreeWidget(self)
962 l.setColumnCount( len(headers) )
963 l.setHeaderLabels( headers )
973 vbox.addWidget(buttons)
978 buttons.setLayout(hbox)
983 def create_receive_tab(self):
984 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
985 l.setContextMenuPolicy(Qt.CustomContextMenu)
986 l.customContextMenuRequested.connect(self.create_receive_menu)
987 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
988 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
989 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
990 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
991 self.receive_list = l
992 self.receive_buttons_hbox = hbox
999 def save_column_widths(self):
1000 self.column_widths["receive"] = []
1001 for i in range(self.receive_list.columnCount() -1):
1002 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1004 self.column_widths["history"] = []
1005 for i in range(self.history_list.columnCount() - 1):
1006 self.column_widths["history"].append(self.history_list.columnWidth(i))
1008 self.column_widths["contacts"] = []
1009 for i in range(self.contacts_list.columnCount() - 1):
1010 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1012 self.config.set_key("column_widths_2", self.column_widths, True)
1015 def create_contacts_tab(self):
1016 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1017 l.setContextMenuPolicy(Qt.CustomContextMenu)
1018 l.customContextMenuRequested.connect(self.create_contact_menu)
1019 for i,width in enumerate(self.column_widths['contacts']):
1020 l.setColumnWidth(i, width)
1022 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1023 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1024 self.contacts_list = l
1025 self.contacts_buttons_hbox = hbox
1030 def delete_imported_key(self, addr):
1031 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1032 self.wallet.delete_imported_key(addr)
1033 self.update_receive_tab()
1034 self.update_history_tab()
1036 def edit_account_label(self, k):
1037 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1039 label = unicode(text)
1040 self.wallet.set_label(k,label)
1041 self.update_receive_tab()
1043 def account_set_expanded(self, item, k, b):
1045 self.accounts_expanded[k] = b
1047 def create_account_menu(self, position, k, item):
1049 if item.isExpanded():
1050 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1052 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1053 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1054 if self.wallet.seed_version > 4:
1055 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1056 if self.wallet.account_is_pending(k):
1057 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1058 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1060 def delete_pending_account(self, k):
1061 self.wallet.delete_pending_account(k)
1062 self.update_receive_tab()
1064 def create_receive_menu(self, position):
1065 # fixme: this function apparently has a side effect.
1066 # if it is not called the menu pops up several times
1067 #self.receive_list.selectedIndexes()
1069 selected = self.receive_list.selectedItems()
1070 multi_select = len(selected) > 1
1071 addrs = [unicode(item.text(0)) for item in selected]
1072 if not multi_select:
1073 item = self.receive_list.itemAt(position)
1077 if not is_valid(addr):
1078 k = str(item.data(0,32).toString())
1080 self.create_account_menu(position, k, item)
1082 item.setExpanded(not item.isExpanded())
1086 if not multi_select:
1087 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1088 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1089 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1090 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1091 if not self.wallet.is_watching_only():
1092 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1093 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1094 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1095 if self.wallet.is_imported(addr):
1096 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1098 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1099 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1100 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1101 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1103 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1104 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1106 run_hook('receive_menu', menu, addrs)
1107 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1110 def get_sendable_balance(self):
1111 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1114 def get_payment_sources(self):
1116 return self.pay_from
1118 return self.wallet.get_account_addresses(self.current_account)
1121 def send_from_addresses(self, addrs):
1122 self.set_pay_from( addrs )
1123 self.tabs.setCurrentIndex(1)
1126 def payto(self, addr):
1128 label = self.wallet.labels.get(addr)
1129 m_addr = label + ' <' + addr + '>' if label else addr
1130 self.tabs.setCurrentIndex(1)
1131 self.payto_e.setText(m_addr)
1132 self.amount_e.setFocus()
1135 def delete_contact(self, x):
1136 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1137 self.wallet.delete_contact(x)
1138 self.wallet.set_label(x, None)
1139 self.update_history_tab()
1140 self.update_contacts_tab()
1141 self.update_completions()
1144 def create_contact_menu(self, position):
1145 item = self.contacts_list.itemAt(position)
1148 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1150 addr = unicode(item.text(0))
1151 label = unicode(item.text(1))
1152 is_editable = item.data(0,32).toBool()
1153 payto_addr = item.data(0,33).toString()
1154 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1155 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1156 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1158 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1159 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1161 run_hook('create_contact_menu', menu, item)
1162 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1165 def update_receive_item(self, item):
1166 item.setFont(0, QFont(MONOSPACE_FONT))
1167 address = str(item.data(0,0).toString())
1168 label = self.wallet.labels.get(address,'')
1169 item.setData(1,0,label)
1170 item.setData(0,32, True) # is editable
1172 run_hook('update_receive_item', address, item)
1174 if not self.wallet.is_mine(address): return
1176 c, u = self.wallet.get_addr_balance(address)
1177 balance = self.format_amount(c + u)
1178 item.setData(2,0,balance)
1180 if address in self.wallet.frozen_addresses:
1181 item.setBackgroundColor(0, QColor('lightblue'))
1184 def update_receive_tab(self):
1185 l = self.receive_list
1186 # extend the syntax for consistency
1187 l.addChild = l.addTopLevelItem
1190 for i,width in enumerate(self.column_widths['receive']):
1191 l.setColumnWidth(i, width)
1193 accounts = self.wallet.get_accounts()
1194 if self.current_account is None:
1195 account_items = sorted(accounts.items())
1197 account_items = [(self.current_account, accounts.get(self.current_account))]
1200 for k, account in account_items:
1202 if len(accounts) > 1:
1203 name = self.wallet.get_account_name(k)
1204 c,u = self.wallet.get_account_balance(k)
1205 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1206 l.addTopLevelItem(account_item)
1207 account_item.setExpanded(self.accounts_expanded.get(k, True))
1208 account_item.setData(0, 32, k)
1212 sequences = [0,1] if account.has_change() else [0]
1213 for is_change in sequences:
1214 if len(sequences) > 1:
1215 name = _("Receiving") if not is_change else _("Change")
1216 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1217 account_item.addChild(seq_item)
1219 seq_item.setExpanded(True)
1221 seq_item = account_item
1223 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1229 for address in account.get_addresses(is_change):
1230 h = self.wallet.history.get(address,[])
1234 if gap > self.wallet.gap_limit:
1239 c, u = self.wallet.get_addr_balance(address)
1240 num_tx = '*' if h == ['*'] else "%d"%len(h)
1242 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1243 self.update_receive_item(item)
1245 item.setBackgroundColor(1, QColor('red'))
1246 if len(h) > 0 and c == -u:
1248 seq_item.insertChild(0,used_item)
1250 used_item.addChild(item)
1252 seq_item.addChild(item)
1254 # we use column 1 because column 0 may be hidden
1255 l.setCurrentItem(l.topLevelItem(0),1)
1258 def update_contacts_tab(self):
1259 l = self.contacts_list
1262 for address in self.wallet.addressbook:
1263 label = self.wallet.labels.get(address,'')
1264 n = self.wallet.get_num_tx(address)
1265 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1266 item.setFont(0, QFont(MONOSPACE_FONT))
1267 # 32 = label can be edited (bool)
1268 item.setData(0,32, True)
1270 item.setData(0,33, address)
1271 l.addTopLevelItem(item)
1273 run_hook('update_contacts_tab', l)
1274 l.setCurrentItem(l.topLevelItem(0))
1278 def create_console_tab(self):
1279 from console import Console
1280 self.console = console = Console()
1284 def update_console(self):
1285 console = self.console
1286 console.history = self.config.get("console-history",[])
1287 console.history_index = len(console.history)
1289 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1290 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1292 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1294 def mkfunc(f, method):
1295 return lambda *args: apply( f, (method, args, self.password_dialog ))
1297 if m[0]=='_' or m in ['network','wallet']: continue
1298 methods[m] = mkfunc(c._run, m)
1300 console.updateNamespace(methods)
1303 def change_account(self,s):
1304 if s == _("All accounts"):
1305 self.current_account = None
1307 accounts = self.wallet.get_account_names()
1308 for k, v in accounts.items():
1310 self.current_account = k
1311 self.update_history_tab()
1312 self.update_status()
1313 self.update_receive_tab()
1315 def create_status_bar(self):
1318 sb.setFixedHeight(35)
1319 qtVersion = qVersion()
1321 self.balance_label = QLabel("")
1322 sb.addWidget(self.balance_label)
1324 from version_getter import UpdateLabel
1325 self.updatelabel = UpdateLabel(self.config, sb)
1327 self.account_selector = QComboBox()
1328 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1329 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1330 sb.addPermanentWidget(self.account_selector)
1332 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1333 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1335 self.lock_icon = QIcon()
1336 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1337 sb.addPermanentWidget( self.password_button )
1339 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1340 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1341 sb.addPermanentWidget( self.seed_button )
1342 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1343 sb.addPermanentWidget( self.status_button )
1345 run_hook('create_status_bar', (sb,))
1347 self.setStatusBar(sb)
1350 def update_lock_icon(self):
1351 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1352 self.password_button.setIcon( icon )
1355 def update_buttons_on_seed(self):
1356 if self.wallet.has_seed():
1357 self.seed_button.show()
1359 self.seed_button.hide()
1361 if not self.wallet.is_watching_only():
1362 self.password_button.show()
1363 self.send_button.setText(_("Send"))
1365 self.password_button.hide()
1366 self.send_button.setText(_("Create unsigned transaction"))
1369 def change_password_dialog(self):
1370 from password_dialog import PasswordDialog
1371 d = PasswordDialog(self.wallet, self)
1373 self.update_lock_icon()
1376 def new_contact_dialog(self):
1379 d.setWindowTitle(_("New Contact"))
1380 vbox = QVBoxLayout(d)
1381 vbox.addWidget(QLabel(_('New Contact')+':'))
1383 grid = QGridLayout()
1386 grid.addWidget(QLabel(_("Address")), 1, 0)
1387 grid.addWidget(line1, 1, 1)
1388 grid.addWidget(QLabel(_("Name")), 2, 0)
1389 grid.addWidget(line2, 2, 1)
1391 vbox.addLayout(grid)
1392 vbox.addLayout(ok_cancel_buttons(d))
1397 address = str(line1.text())
1398 label = unicode(line2.text())
1400 if not is_valid(address):
1401 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1404 self.wallet.add_contact(address)
1406 self.wallet.set_label(address, label)
1408 self.update_contacts_tab()
1409 self.update_history_tab()
1410 self.update_completions()
1411 self.tabs.setCurrentIndex(3)
1415 def new_account_dialog(self, password):
1417 dialog = QDialog(self)
1419 dialog.setWindowTitle(_("New Account"))
1421 vbox = QVBoxLayout()
1422 vbox.addWidget(QLabel(_('Account name')+':'))
1425 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1426 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1431 vbox.addLayout(ok_cancel_buttons(dialog))
1432 dialog.setLayout(vbox)
1436 name = str(e.text())
1439 self.wallet.create_pending_account(name, password)
1440 self.update_receive_tab()
1441 self.tabs.setCurrentIndex(2)
1446 def show_master_public_keys(self):
1448 dialog = QDialog(self)
1450 dialog.setWindowTitle(_("Master Public Keys"))
1452 main_layout = QGridLayout()
1453 mpk_dict = self.wallet.get_master_public_keys()
1455 for key, value in mpk_dict.items():
1456 main_layout.addWidget(QLabel(key), i, 0)
1457 mpk_text = QTextEdit()
1458 mpk_text.setReadOnly(True)
1459 mpk_text.setMaximumHeight(170)
1460 mpk_text.setText(value)
1461 main_layout.addWidget(mpk_text, i + 1, 0)
1464 vbox = QVBoxLayout()
1465 vbox.addLayout(main_layout)
1466 vbox.addLayout(close_button(dialog))
1468 dialog.setLayout(vbox)
1473 def show_seed_dialog(self, password):
1474 if not self.wallet.has_seed():
1475 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1479 mnemonic = self.wallet.get_mnemonic(password)
1481 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1483 from seed_dialog import SeedDialog
1484 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1489 def show_qrcode(self, data, title = _("QR code")):
1493 d.setWindowTitle(title)
1494 d.setMinimumSize(270, 300)
1495 vbox = QVBoxLayout()
1496 qrw = QRCodeWidget(data)
1497 vbox.addWidget(qrw, 1)
1498 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1499 hbox = QHBoxLayout()
1502 filename = os.path.join(self.config.path, "qrcode.bmp")
1505 bmp.save_qrcode(qrw.qr, filename)
1506 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1508 def copy_to_clipboard():
1509 bmp.save_qrcode(qrw.qr, filename)
1510 self.app.clipboard().setImage(QImage(filename))
1511 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1513 b = QPushButton(_("Copy"))
1515 b.clicked.connect(copy_to_clipboard)
1517 b = QPushButton(_("Save"))
1519 b.clicked.connect(print_qr)
1521 b = QPushButton(_("Close"))
1523 b.clicked.connect(d.accept)
1526 vbox.addLayout(hbox)
1531 def do_protect(self, func, args):
1532 if self.wallet.use_encryption:
1533 password = self.password_dialog()
1539 if args != (False,):
1540 args = (self,) + args + (password,)
1542 args = (self,password)
1546 def show_public_keys(self, address):
1547 if not address: return
1549 pubkey_list = self.wallet.get_public_keys(address)
1550 except Exception as e:
1551 traceback.print_exc(file=sys.stdout)
1552 self.show_message(str(e))
1556 d.setMinimumSize(600, 200)
1558 vbox = QVBoxLayout()
1559 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1560 vbox.addWidget( QLabel(_("Public key") + ':'))
1562 keys.setReadOnly(True)
1563 keys.setText('\n'.join(pubkey_list))
1564 vbox.addWidget(keys)
1565 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1566 vbox.addLayout(close_button(d))
1571 def show_private_key(self, address, password):
1572 if not address: return
1574 pk_list = self.wallet.get_private_key(address, password)
1575 except Exception as e:
1576 traceback.print_exc(file=sys.stdout)
1577 self.show_message(str(e))
1581 d.setMinimumSize(600, 200)
1583 vbox = QVBoxLayout()
1584 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1585 vbox.addWidget( QLabel(_("Private key") + ':'))
1587 keys.setReadOnly(True)
1588 keys.setText('\n'.join(pk_list))
1589 vbox.addWidget(keys)
1590 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1591 vbox.addLayout(close_button(d))
1597 def do_sign(self, address, message, signature, password):
1598 message = unicode(message.toPlainText())
1599 message = message.encode('utf-8')
1601 sig = self.wallet.sign_message(str(address.text()), message, password)
1602 signature.setText(sig)
1603 except Exception as e:
1604 self.show_message(str(e))
1606 def do_verify(self, address, message, signature):
1607 message = unicode(message.toPlainText())
1608 message = message.encode('utf-8')
1609 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1610 self.show_message(_("Signature verified"))
1612 self.show_message(_("Error: wrong signature"))
1615 def sign_verify_message(self, address=''):
1618 d.setWindowTitle(_('Sign/verify Message'))
1619 d.setMinimumSize(410, 290)
1621 layout = QGridLayout(d)
1623 message_e = QTextEdit()
1624 layout.addWidget(QLabel(_('Message')), 1, 0)
1625 layout.addWidget(message_e, 1, 1)
1626 layout.setRowStretch(2,3)
1628 address_e = QLineEdit()
1629 address_e.setText(address)
1630 layout.addWidget(QLabel(_('Address')), 2, 0)
1631 layout.addWidget(address_e, 2, 1)
1633 signature_e = QTextEdit()
1634 layout.addWidget(QLabel(_('Signature')), 3, 0)
1635 layout.addWidget(signature_e, 3, 1)
1636 layout.setRowStretch(3,1)
1638 hbox = QHBoxLayout()
1640 b = QPushButton(_("Sign"))
1641 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1644 b = QPushButton(_("Verify"))
1645 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1648 b = QPushButton(_("Close"))
1649 b.clicked.connect(d.accept)
1651 layout.addLayout(hbox, 4, 1)
1656 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1658 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1659 message_e.setText(decrypted)
1660 except Exception as e:
1661 self.show_message(str(e))
1664 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1665 message = unicode(message_e.toPlainText())
1666 message = message.encode('utf-8')
1668 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1669 encrypted_e.setText(encrypted)
1670 except Exception as e:
1671 self.show_message(str(e))
1675 def encrypt_message(self, address = ''):
1678 d.setWindowTitle(_('Encrypt/decrypt Message'))
1679 d.setMinimumSize(610, 490)
1681 layout = QGridLayout(d)
1683 message_e = QTextEdit()
1684 layout.addWidget(QLabel(_('Message')), 1, 0)
1685 layout.addWidget(message_e, 1, 1)
1686 layout.setRowStretch(2,3)
1688 pubkey_e = QLineEdit()
1690 pubkey = self.wallet.getpubkeys(address)[0]
1691 pubkey_e.setText(pubkey)
1692 layout.addWidget(QLabel(_('Public key')), 2, 0)
1693 layout.addWidget(pubkey_e, 2, 1)
1695 encrypted_e = QTextEdit()
1696 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1697 layout.addWidget(encrypted_e, 3, 1)
1698 layout.setRowStretch(3,1)
1700 hbox = QHBoxLayout()
1701 b = QPushButton(_("Encrypt"))
1702 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1705 b = QPushButton(_("Decrypt"))
1706 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1709 b = QPushButton(_("Close"))
1710 b.clicked.connect(d.accept)
1713 layout.addLayout(hbox, 4, 1)
1717 def question(self, msg):
1718 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1720 def show_message(self, msg):
1721 QMessageBox.information(self, _('Message'), msg, _('OK'))
1723 def password_dialog(self ):
1726 d.setWindowTitle(_("Enter Password"))
1731 vbox = QVBoxLayout()
1732 msg = _('Please enter your password')
1733 vbox.addWidget(QLabel(msg))
1735 grid = QGridLayout()
1737 grid.addWidget(QLabel(_('Password')), 1, 0)
1738 grid.addWidget(pw, 1, 1)
1739 vbox.addLayout(grid)
1741 vbox.addLayout(ok_cancel_buttons(d))
1744 run_hook('password_dialog', pw, grid, 1)
1745 if not d.exec_(): return
1746 return unicode(pw.text())
1755 def tx_from_text(self, txt):
1756 "json or raw hexadecimal"
1759 tx = Transaction(txt)
1765 tx_dict = json.loads(str(txt))
1766 assert "hex" in tx_dict.keys()
1767 assert "complete" in tx_dict.keys()
1768 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1769 if not tx_dict["complete"]:
1770 assert "input_info" in tx_dict.keys()
1771 input_info = json.loads(tx_dict['input_info'])
1772 tx.add_input_info(input_info)
1777 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1781 def read_tx_from_file(self):
1782 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1786 with open(fileName, "r") as f:
1787 file_content = f.read()
1788 except (ValueError, IOError, os.error), reason:
1789 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1791 return self.tx_from_text(file_content)
1795 def sign_raw_transaction(self, tx, input_info, password):
1796 self.wallet.signrawtransaction(tx, input_info, [], password)
1798 def do_process_from_text(self):
1799 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1802 tx = self.tx_from_text(text)
1804 self.show_transaction(tx)
1806 def do_process_from_file(self):
1807 tx = self.read_tx_from_file()
1809 self.show_transaction(tx)
1811 def do_process_from_txid(self):
1812 from electrum import transaction
1813 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1815 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1817 tx = transaction.Transaction(r)
1819 self.show_transaction(tx)
1821 self.show_message("unknown transaction")
1823 def do_process_from_csvReader(self, csvReader):
1828 for position, row in enumerate(csvReader):
1830 if not is_valid(address):
1831 errors.append((position, address))
1833 amount = Decimal(row[1])
1834 amount = int(100000000*amount)
1835 outputs.append((address, amount))
1836 except (ValueError, IOError, os.error), reason:
1837 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1841 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1842 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1846 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1847 except Exception as e:
1848 self.show_message(str(e))
1851 self.show_transaction(tx)
1853 def do_process_from_csv_file(self):
1854 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1858 with open(fileName, "r") as f:
1859 csvReader = csv.reader(f)
1860 self.do_process_from_csvReader(csvReader)
1861 except (ValueError, IOError, os.error), reason:
1862 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1865 def do_process_from_csv_text(self):
1866 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1867 + _("Format: address, amount. One output per line"), _("Load CSV"))
1870 f = StringIO.StringIO(text)
1871 csvReader = csv.reader(f)
1872 self.do_process_from_csvReader(csvReader)
1877 def export_privkeys_dialog(self, password):
1878 if self.wallet.is_watching_only():
1879 self.show_message(_("This is a watching-only wallet"))
1883 d.setWindowTitle(_('Private keys'))
1884 d.setMinimumSize(850, 300)
1885 vbox = QVBoxLayout(d)
1887 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1888 _("Exposing a single private key can compromise your entire wallet!"),
1889 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1890 vbox.addWidget(QLabel(msg))
1896 defaultname = 'electrum-private-keys.csv'
1897 select_msg = _('Select file to export your private keys to')
1898 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1899 vbox.addLayout(hbox)
1901 h, b = ok_cancel_buttons2(d, _('Export'))
1906 addresses = self.wallet.addresses(True)
1908 def privkeys_thread():
1909 for addr in addresses:
1913 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1914 d.emit(SIGNAL('computing_privkeys'))
1915 d.emit(SIGNAL('show_privkeys'))
1917 def show_privkeys():
1918 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1922 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1923 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1924 threading.Thread(target=privkeys_thread).start()
1930 filename = filename_e.text()
1935 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1936 except (IOError, os.error), reason:
1937 export_error_label = _("Electrum was unable to produce a private key-export.")
1938 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1940 except Exception as e:
1941 self.show_message(str(e))
1944 self.show_message(_("Private keys exported."))
1947 def do_export_privkeys(self, fileName, pklist, is_csv):
1948 with open(fileName, "w+") as f:
1950 transaction = csv.writer(f)
1951 transaction.writerow(["address", "private_key"])
1952 for addr, pk in pklist.items():
1953 transaction.writerow(["%34s"%addr,pk])
1956 f.write(json.dumps(pklist, indent = 4))
1959 def do_import_labels(self):
1960 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1961 if not labelsFile: return
1963 f = open(labelsFile, 'r')
1966 for key, value in json.loads(data).items():
1967 self.wallet.set_label(key, value)
1968 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1969 except (IOError, os.error), reason:
1970 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1973 def do_export_labels(self):
1974 labels = self.wallet.labels
1976 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1978 with open(fileName, 'w+') as f:
1979 json.dump(labels, f)
1980 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1981 except (IOError, os.error), reason:
1982 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1985 def export_history_dialog(self):
1988 d.setWindowTitle(_('Export History'))
1989 d.setMinimumSize(400, 200)
1990 vbox = QVBoxLayout(d)
1992 defaultname = os.path.expanduser('~/electrum-history.csv')
1993 select_msg = _('Select file to export your wallet transactions to')
1995 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1996 vbox.addLayout(hbox)
2000 h, b = ok_cancel_buttons2(d, _('Export'))
2005 filename = filename_e.text()
2010 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2011 except (IOError, os.error), reason:
2012 export_error_label = _("Electrum was unable to produce a transaction export.")
2013 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2016 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2019 def do_export_history(self, wallet, fileName, is_csv):
2020 history = wallet.get_tx_history()
2022 for item in history:
2023 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2025 if timestamp is not None:
2027 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2028 except [RuntimeError, TypeError, NameError] as reason:
2029 time_string = "unknown"
2032 time_string = "unknown"
2034 time_string = "pending"
2036 if value is not None:
2037 value_string = format_satoshis(value, True)
2042 fee_string = format_satoshis(fee, True)
2047 label, is_default_label = wallet.get_label(tx_hash)
2048 label = label.encode('utf-8')
2052 balance_string = format_satoshis(balance, False)
2054 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2056 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2058 with open(fileName, "w+") as f:
2060 transaction = csv.writer(f)
2061 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2063 transaction.writerow(line)
2066 f.write(json.dumps(lines, indent = 4))
2069 def sweep_key_dialog(self):
2071 d.setWindowTitle(_('Sweep private keys'))
2072 d.setMinimumSize(600, 300)
2074 vbox = QVBoxLayout(d)
2075 vbox.addWidget(QLabel(_("Enter private keys")))
2077 keys_e = QTextEdit()
2078 keys_e.setTabChangesFocus(True)
2079 vbox.addWidget(keys_e)
2081 h, address_e = address_field(self.wallet.addresses())
2085 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2086 vbox.addLayout(hbox)
2087 button.setEnabled(False)
2090 addr = str(address_e.text())
2091 if bitcoin.is_address(addr):
2095 pk = str(keys_e.toPlainText()).strip()
2096 if Wallet.is_private_key(pk):
2099 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2100 keys_e.textChanged.connect(f)
2101 address_e.textChanged.connect(f)
2105 fee = self.wallet.fee
2106 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2107 self.show_transaction(tx)
2111 def do_import_privkey(self, password):
2112 if not self.wallet.imported_keys:
2113 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2114 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2115 + _('Are you sure you understand what you are doing?'), 3, 4)
2118 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2121 text = str(text).split()
2126 addr = self.wallet.import_key(key, password)
2127 except Exception as e:
2133 addrlist.append(addr)
2135 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2137 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2138 self.update_receive_tab()
2139 self.update_history_tab()
2142 def settings_dialog(self):
2144 d.setWindowTitle(_('Electrum Settings'))
2146 vbox = QVBoxLayout()
2147 grid = QGridLayout()
2148 grid.setColumnStretch(0,1)
2150 nz_label = QLabel(_('Display zeros') + ':')
2151 grid.addWidget(nz_label, 0, 0)
2152 nz_e = AmountEdit(None,True)
2153 nz_e.setText("%d"% self.num_zeros)
2154 grid.addWidget(nz_e, 0, 1)
2155 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2156 grid.addWidget(HelpButton(msg), 0, 2)
2157 if not self.config.is_modifiable('num_zeros'):
2158 for w in [nz_e, nz_label]: w.setEnabled(False)
2160 lang_label=QLabel(_('Language') + ':')
2161 grid.addWidget(lang_label, 1, 0)
2162 lang_combo = QComboBox()
2163 from electrum.i18n import languages
2164 lang_combo.addItems(languages.values())
2166 index = languages.keys().index(self.config.get("language",''))
2169 lang_combo.setCurrentIndex(index)
2170 grid.addWidget(lang_combo, 1, 1)
2171 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2172 if not self.config.is_modifiable('language'):
2173 for w in [lang_combo, lang_label]: w.setEnabled(False)
2176 fee_label = QLabel(_('Transaction fee') + ':')
2177 grid.addWidget(fee_label, 2, 0)
2178 fee_e = AmountEdit(self.base_unit)
2179 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2180 grid.addWidget(fee_e, 2, 1)
2181 msg = _('Fee per kilobyte of transaction.') + ' ' \
2182 + _('Recommended value') + ': ' + self.format_amount(20000)
2183 grid.addWidget(HelpButton(msg), 2, 2)
2184 if not self.config.is_modifiable('fee_per_kb'):
2185 for w in [fee_e, fee_label]: w.setEnabled(False)
2187 units = ['BTC', 'mBTC']
2188 unit_label = QLabel(_('Base unit') + ':')
2189 grid.addWidget(unit_label, 3, 0)
2190 unit_combo = QComboBox()
2191 unit_combo.addItems(units)
2192 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2193 grid.addWidget(unit_combo, 3, 1)
2194 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2195 + '\n1BTC=1000mBTC.\n' \
2196 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2198 usechange_cb = QCheckBox(_('Use change addresses'))
2199 usechange_cb.setChecked(self.wallet.use_change)
2200 grid.addWidget(usechange_cb, 4, 0)
2201 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2202 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2204 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2205 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2206 grid.addWidget(block_ex_label, 5, 0)
2207 block_ex_combo = QComboBox()
2208 block_ex_combo.addItems(block_explorers)
2209 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2210 grid.addWidget(block_ex_combo, 5, 1)
2211 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2213 show_tx = self.config.get('show_before_broadcast', False)
2214 showtx_cb = QCheckBox(_('Show before broadcast'))
2215 showtx_cb.setChecked(show_tx)
2216 grid.addWidget(showtx_cb, 6, 0)
2217 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2219 vbox.addLayout(grid)
2221 vbox.addLayout(ok_cancel_buttons(d))
2225 if not d.exec_(): return
2227 fee = unicode(fee_e.text())
2229 fee = self.read_amount(fee)
2231 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2234 self.wallet.set_fee(fee)
2236 nz = unicode(nz_e.text())
2241 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2244 if self.num_zeros != nz:
2246 self.config.set_key('num_zeros', nz, True)
2247 self.update_history_tab()
2248 self.update_receive_tab()
2250 usechange_result = usechange_cb.isChecked()
2251 if self.wallet.use_change != usechange_result:
2252 self.wallet.use_change = usechange_result
2253 self.wallet.storage.put('use_change', self.wallet.use_change)
2255 if showtx_cb.isChecked() != show_tx:
2256 self.config.set_key('show_before_broadcast', not show_tx)
2258 unit_result = units[unit_combo.currentIndex()]
2259 if self.base_unit() != unit_result:
2260 self.decimal_point = 8 if unit_result == 'BTC' else 5
2261 self.config.set_key('decimal_point', self.decimal_point, True)
2262 self.update_history_tab()
2263 self.update_status()
2265 need_restart = False
2267 lang_request = languages.keys()[lang_combo.currentIndex()]
2268 if lang_request != self.config.get('language'):
2269 self.config.set_key("language", lang_request, True)
2272 be_result = block_explorers[block_ex_combo.currentIndex()]
2273 self.config.set_key('block_explorer', be_result, True)
2275 run_hook('close_settings_dialog')
2278 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2281 def run_network_dialog(self):
2282 if not self.network:
2284 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2286 def closeEvent(self, event):
2288 self.config.set_key("is_maximized", self.isMaximized())
2289 if not self.isMaximized():
2291 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2292 self.save_column_widths()
2293 self.config.set_key("console-history", self.console.history[-50:], True)
2294 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2298 def plugins_dialog(self):
2299 from electrum.plugins import plugins
2302 d.setWindowTitle(_('Electrum Plugins'))
2305 vbox = QVBoxLayout(d)
2308 scroll = QScrollArea()
2309 scroll.setEnabled(True)
2310 scroll.setWidgetResizable(True)
2311 scroll.setMinimumSize(400,250)
2312 vbox.addWidget(scroll)
2316 w.setMinimumHeight(len(plugins)*35)
2318 grid = QGridLayout()
2319 grid.setColumnStretch(0,1)
2322 def do_toggle(cb, p, w):
2325 if w: w.setEnabled(r)
2327 def mk_toggle(cb, p, w):
2328 return lambda: do_toggle(cb,p,w)
2330 for i, p in enumerate(plugins):
2332 cb = QCheckBox(p.fullname())
2333 cb.setDisabled(not p.is_available())
2334 cb.setChecked(p.is_enabled())
2335 grid.addWidget(cb, i, 0)
2336 if p.requires_settings():
2337 w = p.settings_widget(self)
2338 w.setEnabled( p.is_enabled() )
2339 grid.addWidget(w, i, 1)
2342 cb.clicked.connect(mk_toggle(cb,p,w))
2343 grid.addWidget(HelpButton(p.description()), i, 2)
2345 print_msg(_("Error: cannot display plugin"), p)
2346 traceback.print_exc(file=sys.stdout)
2347 grid.setRowStretch(i+1,1)
2349 vbox.addLayout(close_button(d))
2354 def show_account_details(self, k):
2355 account = self.wallet.accounts[k]
2358 d.setWindowTitle(_('Account Details'))
2361 vbox = QVBoxLayout(d)
2362 name = self.wallet.get_account_name(k)
2363 label = QLabel('Name: ' + name)
2364 vbox.addWidget(label)
2366 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2368 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2370 vbox.addWidget(QLabel(_('Master Public Key:')))
2373 text.setReadOnly(True)
2374 text.setMaximumHeight(170)
2375 vbox.addWidget(text)
2377 mpk_text = '\n'.join( account.get_master_pubkeys() )
2378 text.setText(mpk_text)
2380 vbox.addLayout(close_button(d))