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, MyLineEdit
48 from network_dialog import NetworkDialog
49 from qrcodewidget import QRCodeWidget
51 from decimal import Decimal
59 if platform.system() == 'Windows':
60 MONOSPACE_FONT = 'Lucida Console'
61 elif platform.system() == 'Darwin':
62 MONOSPACE_FONT = 'Monaco'
64 MONOSPACE_FONT = 'monospace'
66 from electrum import ELECTRUM_VERSION
76 class StatusBarButton(QPushButton):
77 def __init__(self, icon, tooltip, func):
78 QPushButton.__init__(self, icon, '')
79 self.setToolTip(tooltip)
81 self.setMaximumWidth(25)
82 self.clicked.connect(func)
84 self.setIconSize(QSize(25,25))
86 def keyPressEvent(self, e):
87 if e.key() == QtCore.Qt.Key_Return:
99 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
101 class ElectrumWindow(QMainWindow):
105 def __init__(self, config, network, gui_object):
106 QMainWindow.__init__(self)
109 self.network = network
110 self.gui_object = gui_object
111 self.tray = gui_object.tray
112 self.go_lite = gui_object.go_lite
115 self.create_status_bar()
116 self.need_update = threading.Event()
118 self.decimal_point = config.get('decimal_point', 5)
119 self.num_zeros = int(config.get('num_zeros',0))
121 set_language(config.get('language'))
123 self.funds_error = False
124 self.completions = QStringListModel()
126 self.tabs = tabs = QTabWidget(self)
127 self.column_widths = self.config.get("column_widths_2", default_column_widths )
128 tabs.addTab(self.create_history_tab(), _('History') )
129 tabs.addTab(self.create_send_tab(), _('Send') )
130 tabs.addTab(self.create_receive_tab(), _('Receive') )
131 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
132 tabs.addTab(self.create_console_tab(), _('Console') )
133 tabs.setMinimumSize(600, 400)
134 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
135 self.setCentralWidget(tabs)
137 g = self.config.get("winpos-qt",[100, 100, 840, 400])
138 self.setGeometry(g[0], g[1], g[2], g[3])
139 if self.config.get("is_maximized"):
142 self.setWindowIcon(QIcon(":icons/electrum.png"))
145 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
146 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
147 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
148 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
149 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
151 for i in range(tabs.count()):
152 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
154 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
155 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
156 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
157 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
158 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
160 self.history_list.setFocus(True)
164 self.network.register_callback('updated', lambda: self.need_update.set())
165 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
166 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
167 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
168 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
170 # set initial message
171 self.console.showMessage(self.network.banner)
176 def update_account_selector(self):
178 accounts = self.wallet.get_account_names()
179 self.account_selector.clear()
180 if len(accounts) > 1:
181 self.account_selector.addItems([_("All accounts")] + accounts.values())
182 self.account_selector.setCurrentIndex(0)
183 self.account_selector.show()
185 self.account_selector.hide()
188 def load_wallet(self, wallet):
192 self.update_wallet_format()
194 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
195 self.current_account = self.wallet.storage.get("current_account", None)
196 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
197 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
198 self.setWindowTitle( title )
200 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
201 self.notify_transactions()
202 self.update_account_selector()
204 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
205 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
206 self.password_menu.setEnabled(not self.wallet.is_watching_only())
207 self.seed_menu.setEnabled(self.wallet.has_seed())
208 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
209 self.import_menu.setEnabled(self.wallet.can_import())
211 self.update_lock_icon()
212 self.update_buttons_on_seed()
213 self.update_console()
215 run_hook('load_wallet', wallet)
218 def update_wallet_format(self):
219 # convert old-format imported keys
220 if self.wallet.imported_keys:
221 password = self.password_dialog(_("Please enter your password in order to update imported keys"))
223 self.wallet.convert_imported_keys(password)
225 self.show_message("error")
228 def open_wallet(self):
229 wallet_folder = self.wallet.storage.path
230 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
234 storage = WalletStorage({'wallet_path': filename})
235 if not storage.file_exists:
236 self.show_message("file not found "+ filename)
239 self.wallet.stop_threads()
242 wallet = Wallet(storage)
243 wallet.start_threads(self.network)
245 self.load_wallet(wallet)
249 def backup_wallet(self):
251 path = self.wallet.storage.path
252 wallet_folder = os.path.dirname(path)
253 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
257 new_path = os.path.join(wallet_folder, filename)
260 shutil.copy2(path, new_path)
261 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
262 except (IOError, os.error), reason:
263 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
266 def new_wallet(self):
269 wallet_folder = os.path.dirname(self.wallet.storage.path)
270 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
273 filename = os.path.join(wallet_folder, filename)
275 storage = WalletStorage({'wallet_path': filename})
276 if storage.file_exists:
277 QMessageBox.critical(None, "Error", _("File exists"))
280 wizard = installwizard.InstallWizard(self.config, self.network, storage)
281 wallet = wizard.run('new')
283 self.load_wallet(wallet)
287 def init_menubar(self):
290 file_menu = menubar.addMenu(_("&File"))
291 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
292 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
293 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
294 file_menu.addAction(_("&Quit"), self.close)
296 wallet_menu = menubar.addMenu(_("&Wallet"))
297 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
298 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
300 wallet_menu.addSeparator()
302 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
303 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
304 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
306 wallet_menu.addSeparator()
307 labels_menu = wallet_menu.addMenu(_("&Labels"))
308 labels_menu.addAction(_("&Import"), self.do_import_labels)
309 labels_menu.addAction(_("&Export"), self.do_export_labels)
311 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
312 self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
313 self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
314 self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
315 wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
317 tools_menu = menubar.addMenu(_("&Tools"))
319 # Settings / Preferences are all reserved keywords in OSX using this as work around
320 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
321 tools_menu.addAction(_("&Network"), self.run_network_dialog)
322 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
323 tools_menu.addSeparator()
324 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
325 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
326 tools_menu.addSeparator()
328 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
329 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
330 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
332 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
333 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
334 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
335 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
337 help_menu = menubar.addMenu(_("&Help"))
338 help_menu.addAction(_("&About"), self.show_about)
339 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
340 help_menu.addSeparator()
341 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
342 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
344 self.setMenuBar(menubar)
346 def show_about(self):
347 QMessageBox.about(self, "Electrum",
348 _("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."))
350 def show_report_bug(self):
351 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
352 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
355 def notify_transactions(self):
356 if not self.network or not self.network.is_connected():
359 print_error("Notifying GUI")
360 if len(self.network.pending_transactions_for_notifications) > 0:
361 # Combine the transactions if there are more then three
362 tx_amount = len(self.network.pending_transactions_for_notifications)
365 for tx in self.network.pending_transactions_for_notifications:
366 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
370 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
371 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
373 self.network.pending_transactions_for_notifications = []
375 for tx in self.network.pending_transactions_for_notifications:
377 self.network.pending_transactions_for_notifications.remove(tx)
378 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
380 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
382 def notify(self, message):
383 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
387 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
388 def getOpenFileName(self, title, filter = ""):
389 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
390 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
391 if fileName and directory != os.path.dirname(fileName):
392 self.config.set_key('io_dir', os.path.dirname(fileName), True)
395 def getSaveFileName(self, title, filename, filter = ""):
396 directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
397 path = os.path.join( directory, filename )
398 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
399 if fileName and directory != os.path.dirname(fileName):
400 self.config.set_key('io_dir', os.path.dirname(fileName), True)
404 QMainWindow.close(self)
405 run_hook('close_main_window')
407 def connect_slots(self, sender):
408 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
409 self.previous_payto_e=''
411 def timer_actions(self):
412 if self.need_update.is_set():
414 self.need_update.clear()
415 run_hook('timer_actions')
417 def format_amount(self, x, is_diff=False, whitespaces=False):
418 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
421 def get_decimal_point(self):
422 return self.decimal_point
426 assert self.decimal_point in [5,8]
427 return "BTC" if self.decimal_point == 8 else "mBTC"
430 def update_status(self):
431 if self.network is None or not self.network.is_running():
433 icon = QIcon(":icons/status_disconnected.png")
435 elif self.network.is_connected():
436 if not self.wallet.up_to_date:
437 text = _("Synchronizing...")
438 icon = QIcon(":icons/status_waiting.png")
439 elif self.network.server_lag > 1:
440 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
441 icon = QIcon(":icons/status_lagging.png")
443 c, u = self.wallet.get_account_balance(self.current_account)
444 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
445 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
447 # append fiat balance and price from exchange rate plugin
449 run_hook('get_fiat_status_text', c+u, r)
454 self.tray.setToolTip(text)
455 icon = QIcon(":icons/status_connected.png")
457 text = _("Not connected")
458 icon = QIcon(":icons/status_disconnected.png")
460 self.balance_label.setText(text)
461 self.status_button.setIcon( icon )
464 def update_wallet(self):
466 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
467 self.update_history_tab()
468 self.update_receive_tab()
469 self.update_contacts_tab()
470 self.update_completions()
473 def create_history_tab(self):
474 self.history_list = l = MyTreeWidget(self)
476 for i,width in enumerate(self.column_widths['history']):
477 l.setColumnWidth(i, width)
478 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
479 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
480 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
482 l.customContextMenuRequested.connect(self.create_history_menu)
486 def create_history_menu(self, position):
487 self.history_list.selectedIndexes()
488 item = self.history_list.currentItem()
489 be = self.config.get('block_explorer', 'Blockchain.info')
490 if be == 'Blockchain.info':
491 block_explorer = 'https://blockchain.info/tx/'
492 elif be == 'Blockr.io':
493 block_explorer = 'https://blockr.io/tx/info/'
494 elif be == 'Insight.is':
495 block_explorer = 'http://live.insight.is/tx/'
497 tx_hash = str(item.data(0, Qt.UserRole).toString())
498 if not tx_hash: return
500 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
501 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
502 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
503 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
504 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
507 def show_transaction(self, tx):
508 import transaction_dialog
509 d = transaction_dialog.TxDialog(tx, self)
512 def tx_label_clicked(self, item, column):
513 if column==2 and item.isSelected():
515 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
516 self.history_list.editItem( item, column )
517 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
520 def tx_label_changed(self, item, column):
524 tx_hash = str(item.data(0, Qt.UserRole).toString())
525 tx = self.wallet.transactions.get(tx_hash)
526 text = unicode( item.text(2) )
527 self.wallet.set_label(tx_hash, text)
529 item.setForeground(2, QBrush(QColor('black')))
531 text = self.wallet.get_default_label(tx_hash)
532 item.setText(2, text)
533 item.setForeground(2, QBrush(QColor('gray')))
537 def edit_label(self, is_recv):
538 l = self.receive_list if is_recv else self.contacts_list
539 item = l.currentItem()
540 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
541 l.editItem( item, 1 )
542 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
546 def address_label_clicked(self, item, column, l, column_addr, column_label):
547 if column == column_label and item.isSelected():
548 is_editable = item.data(0, 32).toBool()
551 addr = unicode( item.text(column_addr) )
552 label = unicode( item.text(column_label) )
553 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
554 l.editItem( item, column )
555 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
558 def address_label_changed(self, item, column, l, column_addr, column_label):
559 if column == column_label:
560 addr = unicode( item.text(column_addr) )
561 text = unicode( item.text(column_label) )
562 is_editable = item.data(0, 32).toBool()
566 changed = self.wallet.set_label(addr, text)
568 self.update_history_tab()
569 self.update_completions()
571 self.current_item_changed(item)
573 run_hook('item_changed', item, column)
576 def current_item_changed(self, a):
577 run_hook('current_item_changed', a)
581 def update_history_tab(self):
583 self.history_list.clear()
584 for item in self.wallet.get_tx_history(self.current_account):
585 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
586 time_str = _("unknown")
589 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
591 time_str = _("error")
594 time_str = 'unverified'
595 icon = QIcon(":icons/unconfirmed.png")
598 icon = QIcon(":icons/unconfirmed.png")
600 icon = QIcon(":icons/clock%d.png"%conf)
602 icon = QIcon(":icons/confirmed.png")
604 if value is not None:
605 v_str = self.format_amount(value, True, whitespaces=True)
609 balance_str = self.format_amount(balance, whitespaces=True)
612 label, is_default_label = self.wallet.get_label(tx_hash)
614 label = _('Pruned transaction outputs')
615 is_default_label = False
617 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
618 item.setFont(2, QFont(MONOSPACE_FONT))
619 item.setFont(3, QFont(MONOSPACE_FONT))
620 item.setFont(4, QFont(MONOSPACE_FONT))
622 item.setForeground(3, QBrush(QColor("#BC1E1E")))
624 item.setData(0, Qt.UserRole, tx_hash)
625 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
627 item.setForeground(2, QBrush(QColor('grey')))
629 item.setIcon(0, icon)
630 self.history_list.insertTopLevelItem(0,item)
633 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
634 run_hook('history_tab_update')
637 def create_send_tab(self):
640 grid = QGridLayout(w)
642 grid.setColumnMinimumWidth(3,300)
643 grid.setColumnStretch(5,1)
644 grid.setRowStretch(8, 1)
646 from paytoedit import PayToEdit
647 self.amount_e = AmountEdit(self.get_decimal_point)
648 self.payto_e = PayToEdit(self.amount_e)
649 self.payto_help = HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)'))
650 grid.addWidget(QLabel(_('Pay to')), 1, 0)
651 grid.addWidget(self.payto_e, 1, 1, 1, 3)
652 grid.addWidget(self.payto_help, 1, 4)
654 completer = QCompleter()
655 completer.setCaseSensitivity(False)
656 self.payto_e.setCompleter(completer)
657 completer.setModel(self.completions)
659 self.message_e = MyLineEdit()
660 self.message_help = HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.'))
661 grid.addWidget(QLabel(_('Description')), 2, 0)
662 grid.addWidget(self.message_e, 2, 1, 1, 3)
663 grid.addWidget(self.message_help, 2, 4)
665 self.from_label = QLabel(_('From'))
666 grid.addWidget(self.from_label, 3, 0)
667 self.from_list = QTreeWidget(self)
668 self.from_list.setColumnCount(2)
669 self.from_list.setColumnWidth(0, 350)
670 self.from_list.setColumnWidth(1, 50)
671 self.from_list.setHeaderHidden (True)
672 self.from_list.setMaximumHeight(80)
673 grid.addWidget(self.from_list, 3, 1, 1, 3)
674 self.set_pay_from([])
676 self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
677 + _('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.') \
678 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
679 grid.addWidget(QLabel(_('Amount')), 4, 0)
680 grid.addWidget(self.amount_e, 4, 1, 1, 2)
681 grid.addWidget(self.amount_help, 4, 3)
683 self.fee_e = AmountEdit(self.get_decimal_point)
684 grid.addWidget(QLabel(_('Fee')), 5, 0)
685 grid.addWidget(self.fee_e, 5, 1, 1, 2)
686 grid.addWidget(HelpButton(
687 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
688 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
689 + _('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)
691 run_hook('exchange_rate_button', grid)
693 self.send_button = EnterButton(_("Send"), self.do_send)
694 grid.addWidget(self.send_button, 6, 1)
696 b = EnterButton(_("Clear"), self.do_clear)
697 grid.addWidget(b, 6, 2)
699 self.payto_sig = QLabel('')
700 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
702 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
703 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
706 def entry_changed( is_fee ):
707 self.funds_error = False
709 if self.amount_e.is_shortcut:
710 self.amount_e.is_shortcut = False
711 sendable = self.get_sendable_balance()
712 # there is only one output because we are completely spending inputs
713 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
714 fee = self.wallet.estimated_fee(inputs, 1)
716 self.amount_e.setText( self.format_amount(amount) )
717 self.fee_e.setText( self.format_amount( fee ) )
720 amount = self.amount_e.get_amount()
721 fee = self.fee_e.get_amount()
723 if not is_fee: fee = None
726 # assume that there will be 2 outputs (one for change)
727 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
729 self.fee_e.setText( self.format_amount( fee ) )
732 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
736 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
737 self.funds_error = True
738 text = _( "Not enough funds" )
739 c, u = self.wallet.get_frozen_balance()
740 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
742 self.statusBar().showMessage(text)
743 self.amount_e.setPalette(palette)
744 self.fee_e.setPalette(palette)
746 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
747 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
749 run_hook('create_send_tab', grid)
753 def set_pay_from(self, l):
755 self.from_list.clear()
756 self.from_label.setHidden(len(self.pay_from) == 0)
757 self.from_list.setHidden(len(self.pay_from) == 0)
758 for addr in self.pay_from:
759 c, u = self.wallet.get_addr_balance(addr)
760 balance = self.format_amount(c + u)
761 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
764 def update_completions(self):
766 for addr,label in self.wallet.labels.items():
767 if addr in self.wallet.addressbook:
768 l.append( label + ' <' + addr + '>')
770 run_hook('update_completions', l)
771 self.completions.setStringList(l)
775 return lambda s, *args: s.do_protect(func, args)
779 label = unicode( self.message_e.text() )
781 if self.gui_object.payment_request:
782 outputs = self.gui_object.payment_request.outputs
783 amount = self.gui_object.payment_request.get_amount()
785 outputs = self.payto_e.get_outputs()
786 amount = sum(map(lambda x:x[1], outputs))
789 fee = self.fee_e.get_amount()
791 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
794 confirm_amount = self.config.get('confirm_amount', 100000000)
795 if amount >= confirm_amount:
796 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
799 confirm_fee = self.config.get('confirm_fee', 100000)
800 if fee >= confirm_fee:
801 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()}):
804 self.send_tx(outputs, fee, label)
809 def send_tx(self, outputs, fee, label, password):
810 self.send_button.setDisabled(True)
812 # first, create an unsigned tx
813 domain = self.get_payment_sources()
815 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
817 except Exception as e:
818 traceback.print_exc(file=sys.stdout)
819 self.show_message(str(e))
820 self.send_button.setDisabled(False)
823 # call hook to see if plugin needs gui interaction
824 run_hook('send_tx', tx)
830 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
831 self.wallet.sign_transaction(tx, keypairs, password)
832 return tx, fee, label
834 def sign_done(tx, fee, label):
836 self.show_message(tx.error)
837 self.send_button.setDisabled(False)
839 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
840 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
841 self.send_button.setDisabled(False)
844 self.wallet.set_label(tx.hash(), label)
846 if not self.gui_object.payment_request:
847 if not tx.is_complete() or self.config.get('show_before_broadcast'):
848 self.show_transaction(tx)
850 self.send_button.setDisabled(False)
853 self.broadcast_transaction(tx)
855 self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
856 self.waiting_dialog.start()
860 def broadcast_transaction(self, tx):
862 def broadcast_thread():
863 if self.gui_object.payment_request:
864 refund_address = self.wallet.addresses()[0]
865 status, msg = self.gui_object.payment_request.send_ack(str(tx), refund_address)
866 self.gui_object.payment_request = None
868 status, msg = self.wallet.sendtx(tx)
871 def broadcast_done(status, msg):
873 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
876 QMessageBox.warning(self, _('Error'), msg, _('OK'))
877 self.send_button.setDisabled(False)
879 self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
880 self.waiting_dialog.start()
884 def prepare_for_payment_request(self):
885 self.tabs.setCurrentIndex(1)
886 for e in [self.payto_e, self.amount_e, self.message_e]:
888 for h in [self.payto_help, self.amount_help, self.message_help]:
890 self.payto_e.setText(_("please wait..."))
893 def payment_request_ok(self):
894 pr = self.gui_object.payment_request
895 self.payto_help.show()
896 self.payto_help.set_alt(pr.status)
897 self.payto_e.setGreen()
898 self.payto_e.setText(pr.domain)
899 self.amount_e.setText(self.format_amount(pr.get_amount()))
900 self.message_e.setText(pr.memo)
902 def payment_request_error(self):
904 self.show_message(self.gui_object.payment_request.error)
905 self.gui_object.payment_request = None
907 def set_send(self, address, amount, label, message):
909 if label and self.wallet.labels.get(address) != label:
910 if self.question('Give label "%s" to address %s ?'%(label,address)):
911 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
912 self.wallet.addressbook.append(address)
913 self.wallet.set_label(address, label)
915 self.tabs.setCurrentIndex(1)
916 label = self.wallet.labels.get(address)
917 m_addr = label + ' <'+ address +'>' if label else address
918 self.payto_e.setText(m_addr)
920 self.message_e.setText(message)
922 self.amount_e.setText(amount)
926 self.payto_sig.setVisible(False)
927 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
931 for h in [self.payto_help, self.amount_help, self.message_help]:
934 self.payto_help.set_alt(None)
936 self.set_pay_from([])
941 def set_addrs_frozen(self,addrs,freeze):
943 if not addr: continue
944 if addr in self.wallet.frozen_addresses and not freeze:
945 self.wallet.unfreeze(addr)
946 elif addr not in self.wallet.frozen_addresses and freeze:
947 self.wallet.freeze(addr)
948 self.update_receive_tab()
952 def create_list_tab(self, headers):
953 "generic tab creation method"
954 l = MyTreeWidget(self)
955 l.setColumnCount( len(headers) )
956 l.setHeaderLabels( headers )
966 vbox.addWidget(buttons)
971 buttons.setLayout(hbox)
976 def create_receive_tab(self):
977 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
978 l.setContextMenuPolicy(Qt.CustomContextMenu)
979 l.customContextMenuRequested.connect(self.create_receive_menu)
980 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
981 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
982 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
983 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
984 self.receive_list = l
985 self.receive_buttons_hbox = hbox
992 def save_column_widths(self):
993 self.column_widths["receive"] = []
994 for i in range(self.receive_list.columnCount() -1):
995 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
997 self.column_widths["history"] = []
998 for i in range(self.history_list.columnCount() - 1):
999 self.column_widths["history"].append(self.history_list.columnWidth(i))
1001 self.column_widths["contacts"] = []
1002 for i in range(self.contacts_list.columnCount() - 1):
1003 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1005 self.config.set_key("column_widths_2", self.column_widths, True)
1008 def create_contacts_tab(self):
1009 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1010 l.setContextMenuPolicy(Qt.CustomContextMenu)
1011 l.customContextMenuRequested.connect(self.create_contact_menu)
1012 for i,width in enumerate(self.column_widths['contacts']):
1013 l.setColumnWidth(i, width)
1015 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1016 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1017 self.contacts_list = l
1018 self.contacts_buttons_hbox = hbox
1023 def delete_imported_key(self, addr):
1024 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1025 self.wallet.delete_imported_key(addr)
1026 self.update_receive_tab()
1027 self.update_history_tab()
1029 def edit_account_label(self, k):
1030 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1032 label = unicode(text)
1033 self.wallet.set_label(k,label)
1034 self.update_receive_tab()
1036 def account_set_expanded(self, item, k, b):
1038 self.accounts_expanded[k] = b
1040 def create_account_menu(self, position, k, item):
1042 if item.isExpanded():
1043 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1045 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1046 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1047 if self.wallet.seed_version > 4:
1048 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1049 if self.wallet.account_is_pending(k):
1050 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1051 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1053 def delete_pending_account(self, k):
1054 self.wallet.delete_pending_account(k)
1055 self.update_receive_tab()
1057 def create_receive_menu(self, position):
1058 # fixme: this function apparently has a side effect.
1059 # if it is not called the menu pops up several times
1060 #self.receive_list.selectedIndexes()
1062 selected = self.receive_list.selectedItems()
1063 multi_select = len(selected) > 1
1064 addrs = [unicode(item.text(0)) for item in selected]
1065 if not multi_select:
1066 item = self.receive_list.itemAt(position)
1070 if not is_valid(addr):
1071 k = str(item.data(0,32).toString())
1073 self.create_account_menu(position, k, item)
1075 item.setExpanded(not item.isExpanded())
1079 if not multi_select:
1080 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1081 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1082 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1083 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1084 if not self.wallet.is_watching_only():
1085 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1086 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1087 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1088 if self.wallet.is_imported(addr):
1089 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1091 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1092 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1093 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1094 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1096 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1097 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1099 run_hook('receive_menu', menu, addrs)
1100 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1103 def get_sendable_balance(self):
1104 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1107 def get_payment_sources(self):
1109 return self.pay_from
1111 return self.wallet.get_account_addresses(self.current_account)
1114 def send_from_addresses(self, addrs):
1115 self.set_pay_from( addrs )
1116 self.tabs.setCurrentIndex(1)
1119 def payto(self, addr):
1121 label = self.wallet.labels.get(addr)
1122 m_addr = label + ' <' + addr + '>' if label else addr
1123 self.tabs.setCurrentIndex(1)
1124 self.payto_e.setText(m_addr)
1125 self.amount_e.setFocus()
1128 def delete_contact(self, x):
1129 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1130 self.wallet.delete_contact(x)
1131 self.wallet.set_label(x, None)
1132 self.update_history_tab()
1133 self.update_contacts_tab()
1134 self.update_completions()
1137 def create_contact_menu(self, position):
1138 item = self.contacts_list.itemAt(position)
1141 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1143 addr = unicode(item.text(0))
1144 label = unicode(item.text(1))
1145 is_editable = item.data(0,32).toBool()
1146 payto_addr = item.data(0,33).toString()
1147 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1148 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1149 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1151 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1152 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1154 run_hook('create_contact_menu', menu, item)
1155 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1158 def update_receive_item(self, item):
1159 item.setFont(0, QFont(MONOSPACE_FONT))
1160 address = str(item.data(0,0).toString())
1161 label = self.wallet.labels.get(address,'')
1162 item.setData(1,0,label)
1163 item.setData(0,32, True) # is editable
1165 run_hook('update_receive_item', address, item)
1167 if not self.wallet.is_mine(address): return
1169 c, u = self.wallet.get_addr_balance(address)
1170 balance = self.format_amount(c + u)
1171 item.setData(2,0,balance)
1173 if address in self.wallet.frozen_addresses:
1174 item.setBackgroundColor(0, QColor('lightblue'))
1177 def update_receive_tab(self):
1178 l = self.receive_list
1179 # extend the syntax for consistency
1180 l.addChild = l.addTopLevelItem
1181 l.insertChild = l.insertTopLevelItem
1184 for i,width in enumerate(self.column_widths['receive']):
1185 l.setColumnWidth(i, width)
1187 accounts = self.wallet.get_accounts()
1188 if self.current_account is None:
1189 account_items = sorted(accounts.items())
1191 account_items = [(self.current_account, accounts.get(self.current_account))]
1194 for k, account in account_items:
1196 if len(accounts) > 1:
1197 name = self.wallet.get_account_name(k)
1198 c,u = self.wallet.get_account_balance(k)
1199 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1200 l.addTopLevelItem(account_item)
1201 account_item.setExpanded(self.accounts_expanded.get(k, True))
1202 account_item.setData(0, 32, k)
1206 sequences = [0,1] if account.has_change() else [0]
1207 for is_change in sequences:
1208 if len(sequences) > 1:
1209 name = _("Receiving") if not is_change else _("Change")
1210 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1211 account_item.addChild(seq_item)
1213 seq_item.setExpanded(True)
1215 seq_item = account_item
1217 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1223 for address in account.get_addresses(is_change):
1225 num, is_used = self.wallet.is_used(address)
1228 if gap > self.wallet.gap_limit:
1233 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1234 self.update_receive_item(item)
1236 item.setBackgroundColor(1, QColor('red'))
1240 seq_item.insertChild(0,used_item)
1242 used_item.addChild(item)
1244 seq_item.addChild(item)
1246 # we use column 1 because column 0 may be hidden
1247 l.setCurrentItem(l.topLevelItem(0),1)
1250 def update_contacts_tab(self):
1251 l = self.contacts_list
1254 for address in self.wallet.addressbook:
1255 label = self.wallet.labels.get(address,'')
1256 n = self.wallet.get_num_tx(address)
1257 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1258 item.setFont(0, QFont(MONOSPACE_FONT))
1259 # 32 = label can be edited (bool)
1260 item.setData(0,32, True)
1262 item.setData(0,33, address)
1263 l.addTopLevelItem(item)
1265 run_hook('update_contacts_tab', l)
1266 l.setCurrentItem(l.topLevelItem(0))
1270 def create_console_tab(self):
1271 from console import Console
1272 self.console = console = Console()
1276 def update_console(self):
1277 console = self.console
1278 console.history = self.config.get("console-history",[])
1279 console.history_index = len(console.history)
1281 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1282 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1284 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1286 def mkfunc(f, method):
1287 return lambda *args: apply( f, (method, args, self.password_dialog ))
1289 if m[0]=='_' or m in ['network','wallet']: continue
1290 methods[m] = mkfunc(c._run, m)
1292 console.updateNamespace(methods)
1295 def change_account(self,s):
1296 if s == _("All accounts"):
1297 self.current_account = None
1299 accounts = self.wallet.get_account_names()
1300 for k, v in accounts.items():
1302 self.current_account = k
1303 self.update_history_tab()
1304 self.update_status()
1305 self.update_receive_tab()
1307 def create_status_bar(self):
1310 sb.setFixedHeight(35)
1311 qtVersion = qVersion()
1313 self.balance_label = QLabel("")
1314 sb.addWidget(self.balance_label)
1316 from version_getter import UpdateLabel
1317 self.updatelabel = UpdateLabel(self.config, sb)
1319 self.account_selector = QComboBox()
1320 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1321 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1322 sb.addPermanentWidget(self.account_selector)
1324 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1325 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1327 self.lock_icon = QIcon()
1328 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1329 sb.addPermanentWidget( self.password_button )
1331 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1332 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1333 sb.addPermanentWidget( self.seed_button )
1334 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1335 sb.addPermanentWidget( self.status_button )
1337 run_hook('create_status_bar', (sb,))
1339 self.setStatusBar(sb)
1342 def update_lock_icon(self):
1343 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1344 self.password_button.setIcon( icon )
1347 def update_buttons_on_seed(self):
1348 if self.wallet.has_seed():
1349 self.seed_button.show()
1351 self.seed_button.hide()
1353 if not self.wallet.is_watching_only():
1354 self.password_button.show()
1355 self.send_button.setText(_("Send"))
1357 self.password_button.hide()
1358 self.send_button.setText(_("Create unsigned transaction"))
1361 def change_password_dialog(self):
1362 from password_dialog import PasswordDialog
1363 d = PasswordDialog(self.wallet, self)
1365 self.update_lock_icon()
1368 def new_contact_dialog(self):
1371 d.setWindowTitle(_("New Contact"))
1372 vbox = QVBoxLayout(d)
1373 vbox.addWidget(QLabel(_('New Contact')+':'))
1375 grid = QGridLayout()
1378 grid.addWidget(QLabel(_("Address")), 1, 0)
1379 grid.addWidget(line1, 1, 1)
1380 grid.addWidget(QLabel(_("Name")), 2, 0)
1381 grid.addWidget(line2, 2, 1)
1383 vbox.addLayout(grid)
1384 vbox.addLayout(ok_cancel_buttons(d))
1389 address = str(line1.text())
1390 label = unicode(line2.text())
1392 if not is_valid(address):
1393 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1396 self.wallet.add_contact(address)
1398 self.wallet.set_label(address, label)
1400 self.update_contacts_tab()
1401 self.update_history_tab()
1402 self.update_completions()
1403 self.tabs.setCurrentIndex(3)
1407 def new_account_dialog(self, password):
1409 dialog = QDialog(self)
1411 dialog.setWindowTitle(_("New Account"))
1413 vbox = QVBoxLayout()
1414 vbox.addWidget(QLabel(_('Account name')+':'))
1417 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1418 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1423 vbox.addLayout(ok_cancel_buttons(dialog))
1424 dialog.setLayout(vbox)
1428 name = str(e.text())
1431 self.wallet.create_pending_account(name, password)
1432 self.update_receive_tab()
1433 self.tabs.setCurrentIndex(2)
1438 def show_master_public_keys(self):
1440 dialog = QDialog(self)
1442 dialog.setWindowTitle(_("Master Public Keys"))
1444 main_layout = QGridLayout()
1445 mpk_dict = self.wallet.get_master_public_keys()
1447 for key, value in mpk_dict.items():
1448 main_layout.addWidget(QLabel(key), i, 0)
1449 mpk_text = QTextEdit()
1450 mpk_text.setReadOnly(True)
1451 mpk_text.setMaximumHeight(170)
1452 mpk_text.setText(value)
1453 main_layout.addWidget(mpk_text, i + 1, 0)
1456 vbox = QVBoxLayout()
1457 vbox.addLayout(main_layout)
1458 vbox.addLayout(close_button(dialog))
1460 dialog.setLayout(vbox)
1465 def show_seed_dialog(self, password):
1466 if not self.wallet.has_seed():
1467 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1471 mnemonic = self.wallet.get_mnemonic(password)
1473 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1475 from seed_dialog import SeedDialog
1476 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1481 def show_qrcode(self, data, title = _("QR code")):
1485 d.setWindowTitle(title)
1486 d.setMinimumSize(270, 300)
1487 vbox = QVBoxLayout()
1488 qrw = QRCodeWidget(data)
1489 vbox.addWidget(qrw, 1)
1490 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1491 hbox = QHBoxLayout()
1494 filename = os.path.join(self.config.path, "qrcode.bmp")
1497 bmp.save_qrcode(qrw.qr, filename)
1498 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1500 def copy_to_clipboard():
1501 bmp.save_qrcode(qrw.qr, filename)
1502 self.app.clipboard().setImage(QImage(filename))
1503 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1505 b = QPushButton(_("Copy"))
1507 b.clicked.connect(copy_to_clipboard)
1509 b = QPushButton(_("Save"))
1511 b.clicked.connect(print_qr)
1513 b = QPushButton(_("Close"))
1515 b.clicked.connect(d.accept)
1518 vbox.addLayout(hbox)
1523 def do_protect(self, func, args):
1524 if self.wallet.use_encryption:
1525 password = self.password_dialog()
1531 if args != (False,):
1532 args = (self,) + args + (password,)
1534 args = (self,password)
1538 def show_public_keys(self, address):
1539 if not address: return
1541 pubkey_list = self.wallet.get_public_keys(address)
1542 except Exception as e:
1543 traceback.print_exc(file=sys.stdout)
1544 self.show_message(str(e))
1548 d.setMinimumSize(600, 200)
1550 vbox = QVBoxLayout()
1551 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1552 vbox.addWidget( QLabel(_("Public key") + ':'))
1554 keys.setReadOnly(True)
1555 keys.setText('\n'.join(pubkey_list))
1556 vbox.addWidget(keys)
1557 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1558 vbox.addLayout(close_button(d))
1563 def show_private_key(self, address, password):
1564 if not address: return
1566 pk_list = self.wallet.get_private_key(address, password)
1567 except Exception as e:
1568 traceback.print_exc(file=sys.stdout)
1569 self.show_message(str(e))
1573 d.setMinimumSize(600, 200)
1575 vbox = QVBoxLayout()
1576 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1577 vbox.addWidget( QLabel(_("Private key") + ':'))
1579 keys.setReadOnly(True)
1580 keys.setText('\n'.join(pk_list))
1581 vbox.addWidget(keys)
1582 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1583 vbox.addLayout(close_button(d))
1589 def do_sign(self, address, message, signature, password):
1590 message = unicode(message.toPlainText())
1591 message = message.encode('utf-8')
1593 sig = self.wallet.sign_message(str(address.text()), message, password)
1594 signature.setText(sig)
1595 except Exception as e:
1596 self.show_message(str(e))
1598 def do_verify(self, address, message, signature):
1599 message = unicode(message.toPlainText())
1600 message = message.encode('utf-8')
1601 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1602 self.show_message(_("Signature verified"))
1604 self.show_message(_("Error: wrong signature"))
1607 def sign_verify_message(self, address=''):
1610 d.setWindowTitle(_('Sign/verify Message'))
1611 d.setMinimumSize(410, 290)
1613 layout = QGridLayout(d)
1615 message_e = QTextEdit()
1616 layout.addWidget(QLabel(_('Message')), 1, 0)
1617 layout.addWidget(message_e, 1, 1)
1618 layout.setRowStretch(2,3)
1620 address_e = QLineEdit()
1621 address_e.setText(address)
1622 layout.addWidget(QLabel(_('Address')), 2, 0)
1623 layout.addWidget(address_e, 2, 1)
1625 signature_e = QTextEdit()
1626 layout.addWidget(QLabel(_('Signature')), 3, 0)
1627 layout.addWidget(signature_e, 3, 1)
1628 layout.setRowStretch(3,1)
1630 hbox = QHBoxLayout()
1632 b = QPushButton(_("Sign"))
1633 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1636 b = QPushButton(_("Verify"))
1637 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1640 b = QPushButton(_("Close"))
1641 b.clicked.connect(d.accept)
1643 layout.addLayout(hbox, 4, 1)
1648 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1650 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1651 message_e.setText(decrypted)
1652 except Exception as e:
1653 self.show_message(str(e))
1656 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1657 message = unicode(message_e.toPlainText())
1658 message = message.encode('utf-8')
1660 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1661 encrypted_e.setText(encrypted)
1662 except Exception as e:
1663 self.show_message(str(e))
1667 def encrypt_message(self, address = ''):
1670 d.setWindowTitle(_('Encrypt/decrypt Message'))
1671 d.setMinimumSize(610, 490)
1673 layout = QGridLayout(d)
1675 message_e = QTextEdit()
1676 layout.addWidget(QLabel(_('Message')), 1, 0)
1677 layout.addWidget(message_e, 1, 1)
1678 layout.setRowStretch(2,3)
1680 pubkey_e = QLineEdit()
1682 pubkey = self.wallet.getpubkeys(address)[0]
1683 pubkey_e.setText(pubkey)
1684 layout.addWidget(QLabel(_('Public key')), 2, 0)
1685 layout.addWidget(pubkey_e, 2, 1)
1687 encrypted_e = QTextEdit()
1688 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1689 layout.addWidget(encrypted_e, 3, 1)
1690 layout.setRowStretch(3,1)
1692 hbox = QHBoxLayout()
1693 b = QPushButton(_("Encrypt"))
1694 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1697 b = QPushButton(_("Decrypt"))
1698 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1701 b = QPushButton(_("Close"))
1702 b.clicked.connect(d.accept)
1705 layout.addLayout(hbox, 4, 1)
1709 def question(self, msg):
1710 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1712 def show_message(self, msg):
1713 QMessageBox.information(self, _('Message'), msg, _('OK'))
1715 def password_dialog(self, msg=None):
1718 d.setWindowTitle(_("Enter Password"))
1723 vbox = QVBoxLayout()
1725 msg = _('Please enter your password')
1726 vbox.addWidget(QLabel(msg))
1728 grid = QGridLayout()
1730 grid.addWidget(QLabel(_('Password')), 1, 0)
1731 grid.addWidget(pw, 1, 1)
1732 vbox.addLayout(grid)
1734 vbox.addLayout(ok_cancel_buttons(d))
1737 run_hook('password_dialog', pw, grid, 1)
1738 if not d.exec_(): return
1739 return unicode(pw.text())
1748 def tx_from_text(self, txt):
1749 "json or raw hexadecimal"
1752 tx = Transaction(txt)
1758 tx_dict = json.loads(str(txt))
1759 assert "hex" in tx_dict.keys()
1760 tx = Transaction(tx_dict["hex"])
1761 if tx_dict.has_key("input_info"):
1762 input_info = json.loads(tx_dict['input_info'])
1763 tx.add_input_info(input_info)
1766 traceback.print_exc(file=sys.stdout)
1769 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1773 def read_tx_from_file(self):
1774 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1778 with open(fileName, "r") as f:
1779 file_content = f.read()
1780 except (ValueError, IOError, os.error), reason:
1781 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1783 return self.tx_from_text(file_content)
1787 def sign_raw_transaction(self, tx, input_info, password):
1788 self.wallet.signrawtransaction(tx, input_info, [], password)
1790 def do_process_from_text(self):
1791 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1794 tx = self.tx_from_text(text)
1796 self.show_transaction(tx)
1798 def do_process_from_file(self):
1799 tx = self.read_tx_from_file()
1801 self.show_transaction(tx)
1803 def do_process_from_txid(self):
1804 from electrum import transaction
1805 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1807 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1809 tx = transaction.Transaction(r)
1811 self.show_transaction(tx)
1813 self.show_message("unknown transaction")
1815 def do_process_from_csvReader(self, csvReader):
1820 for position, row in enumerate(csvReader):
1822 if not is_valid(address):
1823 errors.append((position, address))
1825 amount = Decimal(row[1])
1826 amount = int(100000000*amount)
1827 outputs.append((address, amount))
1828 except (ValueError, IOError, os.error), reason:
1829 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1833 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1834 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1838 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1839 except Exception as e:
1840 self.show_message(str(e))
1843 self.show_transaction(tx)
1845 def do_process_from_csv_file(self):
1846 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1850 with open(fileName, "r") as f:
1851 csvReader = csv.reader(f)
1852 self.do_process_from_csvReader(csvReader)
1853 except (ValueError, IOError, os.error), reason:
1854 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1857 def do_process_from_csv_text(self):
1858 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1859 + _("Format: address, amount. One output per line"), _("Load CSV"))
1862 f = StringIO.StringIO(text)
1863 csvReader = csv.reader(f)
1864 self.do_process_from_csvReader(csvReader)
1869 def export_privkeys_dialog(self, password):
1870 if self.wallet.is_watching_only():
1871 self.show_message(_("This is a watching-only wallet"))
1875 d.setWindowTitle(_('Private keys'))
1876 d.setMinimumSize(850, 300)
1877 vbox = QVBoxLayout(d)
1879 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1880 _("Exposing a single private key can compromise your entire wallet!"),
1881 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1882 vbox.addWidget(QLabel(msg))
1888 defaultname = 'electrum-private-keys.csv'
1889 select_msg = _('Select file to export your private keys to')
1890 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1891 vbox.addLayout(hbox)
1893 h, b = ok_cancel_buttons2(d, _('Export'))
1898 addresses = self.wallet.addresses(True)
1900 def privkeys_thread():
1901 for addr in addresses:
1905 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1906 d.emit(SIGNAL('computing_privkeys'))
1907 d.emit(SIGNAL('show_privkeys'))
1909 def show_privkeys():
1910 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1914 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1915 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1916 threading.Thread(target=privkeys_thread).start()
1922 filename = filename_e.text()
1927 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1928 except (IOError, os.error), reason:
1929 export_error_label = _("Electrum was unable to produce a private key-export.")
1930 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1932 except Exception as e:
1933 self.show_message(str(e))
1936 self.show_message(_("Private keys exported."))
1939 def do_export_privkeys(self, fileName, pklist, is_csv):
1940 with open(fileName, "w+") as f:
1942 transaction = csv.writer(f)
1943 transaction.writerow(["address", "private_key"])
1944 for addr, pk in pklist.items():
1945 transaction.writerow(["%34s"%addr,pk])
1948 f.write(json.dumps(pklist, indent = 4))
1951 def do_import_labels(self):
1952 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1953 if not labelsFile: return
1955 f = open(labelsFile, 'r')
1958 for key, value in json.loads(data).items():
1959 self.wallet.set_label(key, value)
1960 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1961 except (IOError, os.error), reason:
1962 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1965 def do_export_labels(self):
1966 labels = self.wallet.labels
1968 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1970 with open(fileName, 'w+') as f:
1971 json.dump(labels, f)
1972 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1973 except (IOError, os.error), reason:
1974 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1977 def export_history_dialog(self):
1980 d.setWindowTitle(_('Export History'))
1981 d.setMinimumSize(400, 200)
1982 vbox = QVBoxLayout(d)
1984 defaultname = os.path.expanduser('~/electrum-history.csv')
1985 select_msg = _('Select file to export your wallet transactions to')
1987 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1988 vbox.addLayout(hbox)
1992 h, b = ok_cancel_buttons2(d, _('Export'))
1997 filename = filename_e.text()
2002 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2003 except (IOError, os.error), reason:
2004 export_error_label = _("Electrum was unable to produce a transaction export.")
2005 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2008 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2011 def do_export_history(self, wallet, fileName, is_csv):
2012 history = wallet.get_tx_history()
2014 for item in history:
2015 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2017 if timestamp is not None:
2019 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2020 except [RuntimeError, TypeError, NameError] as reason:
2021 time_string = "unknown"
2024 time_string = "unknown"
2026 time_string = "pending"
2028 if value is not None:
2029 value_string = format_satoshis(value, True)
2034 fee_string = format_satoshis(fee, True)
2039 label, is_default_label = wallet.get_label(tx_hash)
2040 label = label.encode('utf-8')
2044 balance_string = format_satoshis(balance, False)
2046 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2048 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2050 with open(fileName, "w+") as f:
2052 transaction = csv.writer(f)
2053 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2055 transaction.writerow(line)
2058 f.write(json.dumps(lines, indent = 4))
2061 def sweep_key_dialog(self):
2063 d.setWindowTitle(_('Sweep private keys'))
2064 d.setMinimumSize(600, 300)
2066 vbox = QVBoxLayout(d)
2067 vbox.addWidget(QLabel(_("Enter private keys")))
2069 keys_e = QTextEdit()
2070 keys_e.setTabChangesFocus(True)
2071 vbox.addWidget(keys_e)
2073 h, address_e = address_field(self.wallet.addresses())
2077 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2078 vbox.addLayout(hbox)
2079 button.setEnabled(False)
2082 addr = str(address_e.text())
2083 if bitcoin.is_address(addr):
2087 pk = str(keys_e.toPlainText()).strip()
2088 if Wallet.is_private_key(pk):
2091 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2092 keys_e.textChanged.connect(f)
2093 address_e.textChanged.connect(f)
2097 fee = self.wallet.fee
2098 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2099 self.show_transaction(tx)
2103 def do_import_privkey(self, password):
2104 if not self.wallet.has_imported_keys():
2105 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2106 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2107 + _('Are you sure you understand what you are doing?'), 3, 4)
2110 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2113 text = str(text).split()
2118 addr = self.wallet.import_key(key, password)
2119 except Exception as e:
2125 addrlist.append(addr)
2127 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2129 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2130 self.update_receive_tab()
2131 self.update_history_tab()
2134 def settings_dialog(self):
2136 d.setWindowTitle(_('Electrum Settings'))
2138 vbox = QVBoxLayout()
2139 grid = QGridLayout()
2140 grid.setColumnStretch(0,1)
2142 nz_label = QLabel(_('Display zeros') + ':')
2143 grid.addWidget(nz_label, 0, 0)
2144 nz_e = AmountEdit(None,True)
2145 nz_e.setText("%d"% self.num_zeros)
2146 grid.addWidget(nz_e, 0, 1)
2147 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2148 grid.addWidget(HelpButton(msg), 0, 2)
2149 if not self.config.is_modifiable('num_zeros'):
2150 for w in [nz_e, nz_label]: w.setEnabled(False)
2152 lang_label=QLabel(_('Language') + ':')
2153 grid.addWidget(lang_label, 1, 0)
2154 lang_combo = QComboBox()
2155 from electrum.i18n import languages
2156 lang_combo.addItems(languages.values())
2158 index = languages.keys().index(self.config.get("language",''))
2161 lang_combo.setCurrentIndex(index)
2162 grid.addWidget(lang_combo, 1, 1)
2163 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2164 if not self.config.is_modifiable('language'):
2165 for w in [lang_combo, lang_label]: w.setEnabled(False)
2168 fee_label = QLabel(_('Transaction fee') + ':')
2169 grid.addWidget(fee_label, 2, 0)
2170 fee_e = AmountEdit(self.get_decimal_point)
2171 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2172 grid.addWidget(fee_e, 2, 1)
2173 msg = _('Fee per kilobyte of transaction.') + ' ' \
2174 + _('Recommended value') + ': ' + self.format_amount(20000)
2175 grid.addWidget(HelpButton(msg), 2, 2)
2176 if not self.config.is_modifiable('fee_per_kb'):
2177 for w in [fee_e, fee_label]: w.setEnabled(False)
2179 units = ['BTC', 'mBTC']
2180 unit_label = QLabel(_('Base unit') + ':')
2181 grid.addWidget(unit_label, 3, 0)
2182 unit_combo = QComboBox()
2183 unit_combo.addItems(units)
2184 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2185 grid.addWidget(unit_combo, 3, 1)
2186 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2187 + '\n1BTC=1000mBTC.\n' \
2188 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2190 usechange_cb = QCheckBox(_('Use change addresses'))
2191 usechange_cb.setChecked(self.wallet.use_change)
2192 grid.addWidget(usechange_cb, 4, 0)
2193 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2194 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2196 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2197 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2198 grid.addWidget(block_ex_label, 5, 0)
2199 block_ex_combo = QComboBox()
2200 block_ex_combo.addItems(block_explorers)
2201 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2202 grid.addWidget(block_ex_combo, 5, 1)
2203 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2205 show_tx = self.config.get('show_before_broadcast', False)
2206 showtx_cb = QCheckBox(_('Show before broadcast'))
2207 showtx_cb.setChecked(show_tx)
2208 grid.addWidget(showtx_cb, 6, 0)
2209 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2211 vbox.addLayout(grid)
2213 vbox.addLayout(ok_cancel_buttons(d))
2217 if not d.exec_(): return
2220 fee = self.fee_e.get_amount()
2222 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2225 self.wallet.set_fee(fee)
2227 nz = unicode(nz_e.text())
2232 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2235 if self.num_zeros != nz:
2237 self.config.set_key('num_zeros', nz, True)
2238 self.update_history_tab()
2239 self.update_receive_tab()
2241 usechange_result = usechange_cb.isChecked()
2242 if self.wallet.use_change != usechange_result:
2243 self.wallet.use_change = usechange_result
2244 self.wallet.storage.put('use_change', self.wallet.use_change)
2246 if showtx_cb.isChecked() != show_tx:
2247 self.config.set_key('show_before_broadcast', not show_tx)
2249 unit_result = units[unit_combo.currentIndex()]
2250 if self.base_unit() != unit_result:
2251 self.decimal_point = 8 if unit_result == 'BTC' else 5
2252 self.config.set_key('decimal_point', self.decimal_point, True)
2253 self.update_history_tab()
2254 self.update_status()
2256 need_restart = False
2258 lang_request = languages.keys()[lang_combo.currentIndex()]
2259 if lang_request != self.config.get('language'):
2260 self.config.set_key("language", lang_request, True)
2263 be_result = block_explorers[block_ex_combo.currentIndex()]
2264 self.config.set_key('block_explorer', be_result, True)
2266 run_hook('close_settings_dialog')
2269 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2272 def run_network_dialog(self):
2273 if not self.network:
2275 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2277 def closeEvent(self, event):
2279 self.config.set_key("is_maximized", self.isMaximized())
2280 if not self.isMaximized():
2282 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2283 self.save_column_widths()
2284 self.config.set_key("console-history", self.console.history[-50:], True)
2285 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2289 def plugins_dialog(self):
2290 from electrum.plugins import plugins
2293 d.setWindowTitle(_('Electrum Plugins'))
2296 vbox = QVBoxLayout(d)
2299 scroll = QScrollArea()
2300 scroll.setEnabled(True)
2301 scroll.setWidgetResizable(True)
2302 scroll.setMinimumSize(400,250)
2303 vbox.addWidget(scroll)
2307 w.setMinimumHeight(len(plugins)*35)
2309 grid = QGridLayout()
2310 grid.setColumnStretch(0,1)
2313 def do_toggle(cb, p, w):
2316 if w: w.setEnabled(r)
2318 def mk_toggle(cb, p, w):
2319 return lambda: do_toggle(cb,p,w)
2321 for i, p in enumerate(plugins):
2323 cb = QCheckBox(p.fullname())
2324 cb.setDisabled(not p.is_available())
2325 cb.setChecked(p.is_enabled())
2326 grid.addWidget(cb, i, 0)
2327 if p.requires_settings():
2328 w = p.settings_widget(self)
2329 w.setEnabled( p.is_enabled() )
2330 grid.addWidget(w, i, 1)
2333 cb.clicked.connect(mk_toggle(cb,p,w))
2334 grid.addWidget(HelpButton(p.description()), i, 2)
2336 print_msg(_("Error: cannot display plugin"), p)
2337 traceback.print_exc(file=sys.stdout)
2338 grid.setRowStretch(i+1,1)
2340 vbox.addLayout(close_button(d))
2345 def show_account_details(self, k):
2346 account = self.wallet.accounts[k]
2349 d.setWindowTitle(_('Account Details'))
2352 vbox = QVBoxLayout(d)
2353 name = self.wallet.get_account_name(k)
2354 label = QLabel('Name: ' + name)
2355 vbox.addWidget(label)
2357 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2359 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2361 vbox.addWidget(QLabel(_('Master Public Key:')))
2364 text.setReadOnly(True)
2365 text.setMaximumHeight(170)
2366 vbox.addWidget(text)
2368 mpk_text = '\n'.join( account.get_master_pubkeys() )
2369 text.setText(mpk_text)
2371 vbox.addLayout(close_button(d))