3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
33 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
34 from electrum.plugins import run_hook
38 from electrum.wallet import format_satoshis
39 from electrum import Transaction
40 from electrum import mnemonic
41 from electrum import util, bitcoin, commands, Interface, Wallet
42 from electrum import SimpleConfig, Wallet, WalletStorage
45 from electrum import bmp, pyqrnative
47 from amountedit import AmountEdit
48 from network_dialog import NetworkDialog
49 from qrcodewidget import QRCodeWidget
51 from decimal import Decimal
59 if platform.system() == 'Windows':
60 MONOSPACE_FONT = 'Lucida Console'
61 elif platform.system() == 'Darwin':
62 MONOSPACE_FONT = 'Monaco'
64 MONOSPACE_FONT = 'monospace'
66 from electrum import ELECTRUM_VERSION
76 class StatusBarButton(QPushButton):
77 def __init__(self, icon, tooltip, func):
78 QPushButton.__init__(self, icon, '')
79 self.setToolTip(tooltip)
81 self.setMaximumWidth(25)
82 self.clicked.connect(func)
84 self.setIconSize(QSize(25,25))
86 def keyPressEvent(self, e):
87 if e.key() == QtCore.Qt.Key_Return:
99 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
101 class ElectrumWindow(QMainWindow):
105 def __init__(self, config, network, gui_object):
106 QMainWindow.__init__(self)
109 self.network = network
110 self.gui_object = gui_object
111 self.tray = gui_object.tray
112 self.go_lite = gui_object.go_lite
115 self.create_status_bar()
116 self.need_update = threading.Event()
118 self.decimal_point = config.get('decimal_point', 5)
119 self.num_zeros = int(config.get('num_zeros',0))
121 set_language(config.get('language'))
123 self.funds_error = False
124 self.completions = QStringListModel()
126 self.tabs = tabs = QTabWidget(self)
127 self.column_widths = self.config.get("column_widths_2", default_column_widths )
128 tabs.addTab(self.create_history_tab(), _('History') )
129 tabs.addTab(self.create_send_tab(), _('Send') )
130 tabs.addTab(self.create_receive_tab(), _('Receive') )
131 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
132 tabs.addTab(self.create_console_tab(), _('Console') )
133 tabs.setMinimumSize(600, 400)
134 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
135 self.setCentralWidget(tabs)
137 g = self.config.get("winpos-qt",[100, 100, 840, 400])
138 self.setGeometry(g[0], g[1], g[2], g[3])
139 if self.config.get("is_maximized"):
142 self.setWindowIcon(QIcon(":icons/electrum.png"))
145 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
146 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
147 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
148 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
149 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
151 for i in range(tabs.count()):
152 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
154 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
155 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
156 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
157 self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
158 self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
160 self.history_list.setFocus(True)
164 self.network.register_callback('updated', lambda: self.need_update.set())
165 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
166 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
167 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
168 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
170 # set initial message
171 self.console.showMessage(self.network.banner)
176 def update_account_selector(self):
178 accounts = self.wallet.get_account_names()
179 self.account_selector.clear()
180 if len(accounts) > 1:
181 self.account_selector.addItems([_("All accounts")] + accounts.values())
182 self.account_selector.setCurrentIndex(0)
183 self.account_selector.show()
185 self.account_selector.hide()
188 def load_wallet(self, wallet):
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 = QLineEdit()
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 style = "QWidget { background-color:none;border:none;}"
886 self.tabs.setCurrentIndex(1)
887 for e in [self.payto_e, self.amount_e, self.message_e]:
889 e.setStyleSheet(style)
890 for h in [self.payto_help, self.amount_help, self.message_help]:
892 self.payto_e.setText(_("please wait..."))
895 def payment_request_ok(self):
896 self.payto_e.setText(self.gui_object.payment_request.domain)
897 self.amount_e.setText(self.format_amount(self.gui_object.payment_request.get_amount()))
898 self.message_e.setText(self.gui_object.payment_request.memo)
900 def payment_request_error(self):
902 self.show_message(self.gui_object.payment_request.error)
905 def set_send(self, address, amount, label, message):
907 if label and self.wallet.labels.get(address) != label:
908 if self.question('Give label "%s" to address %s ?'%(label,address)):
909 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
910 self.wallet.addressbook.append(address)
911 self.wallet.set_label(address, label)
913 self.tabs.setCurrentIndex(1)
914 label = self.wallet.labels.get(address)
915 m_addr = label + ' <'+ address +'>' if label else address
916 self.payto_e.setText(m_addr)
918 self.message_e.setText(message)
920 self.amount_e.setText(amount)
924 self.payto_sig.setVisible(False)
925 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
927 self.set_frozen(e,False)
929 for h in [self.payto_help, self.amount_help, self.message_help]:
932 self.set_pay_from([])
935 def set_frozen(self,entry,frozen):
937 entry.setReadOnly(True)
938 entry.setFrame(False)
940 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
941 entry.setPalette(palette)
943 entry.setReadOnly(False)
946 palette.setColor(entry.backgroundRole(), QColor('white'))
947 entry.setPalette(palette)
950 def set_addrs_frozen(self,addrs,freeze):
952 if not addr: continue
953 if addr in self.wallet.frozen_addresses and not freeze:
954 self.wallet.unfreeze(addr)
955 elif addr not in self.wallet.frozen_addresses and freeze:
956 self.wallet.freeze(addr)
957 self.update_receive_tab()
961 def create_list_tab(self, headers):
962 "generic tab creation method"
963 l = MyTreeWidget(self)
964 l.setColumnCount( len(headers) )
965 l.setHeaderLabels( headers )
975 vbox.addWidget(buttons)
980 buttons.setLayout(hbox)
985 def create_receive_tab(self):
986 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
987 l.setContextMenuPolicy(Qt.CustomContextMenu)
988 l.customContextMenuRequested.connect(self.create_receive_menu)
989 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
990 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
991 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
992 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
993 self.receive_list = l
994 self.receive_buttons_hbox = hbox
1001 def save_column_widths(self):
1002 self.column_widths["receive"] = []
1003 for i in range(self.receive_list.columnCount() -1):
1004 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1006 self.column_widths["history"] = []
1007 for i in range(self.history_list.columnCount() - 1):
1008 self.column_widths["history"].append(self.history_list.columnWidth(i))
1010 self.column_widths["contacts"] = []
1011 for i in range(self.contacts_list.columnCount() - 1):
1012 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1014 self.config.set_key("column_widths_2", self.column_widths, True)
1017 def create_contacts_tab(self):
1018 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1019 l.setContextMenuPolicy(Qt.CustomContextMenu)
1020 l.customContextMenuRequested.connect(self.create_contact_menu)
1021 for i,width in enumerate(self.column_widths['contacts']):
1022 l.setColumnWidth(i, width)
1024 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1025 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1026 self.contacts_list = l
1027 self.contacts_buttons_hbox = hbox
1032 def delete_imported_key(self, addr):
1033 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1034 self.wallet.delete_imported_key(addr)
1035 self.update_receive_tab()
1036 self.update_history_tab()
1038 def edit_account_label(self, k):
1039 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1041 label = unicode(text)
1042 self.wallet.set_label(k,label)
1043 self.update_receive_tab()
1045 def account_set_expanded(self, item, k, b):
1047 self.accounts_expanded[k] = b
1049 def create_account_menu(self, position, k, item):
1051 if item.isExpanded():
1052 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1054 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1055 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1056 if self.wallet.seed_version > 4:
1057 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1058 if self.wallet.account_is_pending(k):
1059 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1060 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1062 def delete_pending_account(self, k):
1063 self.wallet.delete_pending_account(k)
1064 self.update_receive_tab()
1066 def create_receive_menu(self, position):
1067 # fixme: this function apparently has a side effect.
1068 # if it is not called the menu pops up several times
1069 #self.receive_list.selectedIndexes()
1071 selected = self.receive_list.selectedItems()
1072 multi_select = len(selected) > 1
1073 addrs = [unicode(item.text(0)) for item in selected]
1074 if not multi_select:
1075 item = self.receive_list.itemAt(position)
1079 if not is_valid(addr):
1080 k = str(item.data(0,32).toString())
1082 self.create_account_menu(position, k, item)
1084 item.setExpanded(not item.isExpanded())
1088 if not multi_select:
1089 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1090 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1091 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1092 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1093 if not self.wallet.is_watching_only():
1094 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1095 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1096 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1097 if self.wallet.is_imported(addr):
1098 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1100 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1101 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1102 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1103 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1105 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1106 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1108 run_hook('receive_menu', menu, addrs)
1109 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1112 def get_sendable_balance(self):
1113 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1116 def get_payment_sources(self):
1118 return self.pay_from
1120 return self.wallet.get_account_addresses(self.current_account)
1123 def send_from_addresses(self, addrs):
1124 self.set_pay_from( addrs )
1125 self.tabs.setCurrentIndex(1)
1128 def payto(self, addr):
1130 label = self.wallet.labels.get(addr)
1131 m_addr = label + ' <' + addr + '>' if label else addr
1132 self.tabs.setCurrentIndex(1)
1133 self.payto_e.setText(m_addr)
1134 self.amount_e.setFocus()
1137 def delete_contact(self, x):
1138 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1139 self.wallet.delete_contact(x)
1140 self.wallet.set_label(x, None)
1141 self.update_history_tab()
1142 self.update_contacts_tab()
1143 self.update_completions()
1146 def create_contact_menu(self, position):
1147 item = self.contacts_list.itemAt(position)
1150 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1152 addr = unicode(item.text(0))
1153 label = unicode(item.text(1))
1154 is_editable = item.data(0,32).toBool()
1155 payto_addr = item.data(0,33).toString()
1156 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1157 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1158 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1160 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1161 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1163 run_hook('create_contact_menu', menu, item)
1164 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1167 def update_receive_item(self, item):
1168 item.setFont(0, QFont(MONOSPACE_FONT))
1169 address = str(item.data(0,0).toString())
1170 label = self.wallet.labels.get(address,'')
1171 item.setData(1,0,label)
1172 item.setData(0,32, True) # is editable
1174 run_hook('update_receive_item', address, item)
1176 if not self.wallet.is_mine(address): return
1178 c, u = self.wallet.get_addr_balance(address)
1179 balance = self.format_amount(c + u)
1180 item.setData(2,0,balance)
1182 if address in self.wallet.frozen_addresses:
1183 item.setBackgroundColor(0, QColor('lightblue'))
1186 def update_receive_tab(self):
1187 l = self.receive_list
1188 # extend the syntax for consistency
1189 l.addChild = l.addTopLevelItem
1190 l.insertChild = l.insertTopLevelItem
1193 for i,width in enumerate(self.column_widths['receive']):
1194 l.setColumnWidth(i, width)
1196 accounts = self.wallet.get_accounts()
1197 if self.current_account is None:
1198 account_items = sorted(accounts.items())
1200 account_items = [(self.current_account, accounts.get(self.current_account))]
1203 for k, account in account_items:
1205 if len(accounts) > 1:
1206 name = self.wallet.get_account_name(k)
1207 c,u = self.wallet.get_account_balance(k)
1208 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1209 l.addTopLevelItem(account_item)
1210 account_item.setExpanded(self.accounts_expanded.get(k, True))
1211 account_item.setData(0, 32, k)
1215 sequences = [0,1] if account.has_change() else [0]
1216 for is_change in sequences:
1217 if len(sequences) > 1:
1218 name = _("Receiving") if not is_change else _("Change")
1219 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1220 account_item.addChild(seq_item)
1222 seq_item.setExpanded(True)
1224 seq_item = account_item
1226 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1232 for address in account.get_addresses(is_change):
1234 num, is_used = self.wallet.is_used(address)
1237 if gap > self.wallet.gap_limit:
1242 item = QTreeWidgetItem( [ address, '', '', "%d"%num] )
1243 self.update_receive_item(item)
1245 item.setBackgroundColor(1, QColor('red'))
1249 seq_item.insertChild(0,used_item)
1251 used_item.addChild(item)
1253 seq_item.addChild(item)
1255 # we use column 1 because column 0 may be hidden
1256 l.setCurrentItem(l.topLevelItem(0),1)
1259 def update_contacts_tab(self):
1260 l = self.contacts_list
1263 for address in self.wallet.addressbook:
1264 label = self.wallet.labels.get(address,'')
1265 n = self.wallet.get_num_tx(address)
1266 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1267 item.setFont(0, QFont(MONOSPACE_FONT))
1268 # 32 = label can be edited (bool)
1269 item.setData(0,32, True)
1271 item.setData(0,33, address)
1272 l.addTopLevelItem(item)
1274 run_hook('update_contacts_tab', l)
1275 l.setCurrentItem(l.topLevelItem(0))
1279 def create_console_tab(self):
1280 from console import Console
1281 self.console = console = Console()
1285 def update_console(self):
1286 console = self.console
1287 console.history = self.config.get("console-history",[])
1288 console.history_index = len(console.history)
1290 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1291 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1293 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1295 def mkfunc(f, method):
1296 return lambda *args: apply( f, (method, args, self.password_dialog ))
1298 if m[0]=='_' or m in ['network','wallet']: continue
1299 methods[m] = mkfunc(c._run, m)
1301 console.updateNamespace(methods)
1304 def change_account(self,s):
1305 if s == _("All accounts"):
1306 self.current_account = None
1308 accounts = self.wallet.get_account_names()
1309 for k, v in accounts.items():
1311 self.current_account = k
1312 self.update_history_tab()
1313 self.update_status()
1314 self.update_receive_tab()
1316 def create_status_bar(self):
1319 sb.setFixedHeight(35)
1320 qtVersion = qVersion()
1322 self.balance_label = QLabel("")
1323 sb.addWidget(self.balance_label)
1325 from version_getter import UpdateLabel
1326 self.updatelabel = UpdateLabel(self.config, sb)
1328 self.account_selector = QComboBox()
1329 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1330 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1331 sb.addPermanentWidget(self.account_selector)
1333 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1334 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1336 self.lock_icon = QIcon()
1337 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1338 sb.addPermanentWidget( self.password_button )
1340 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1341 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1342 sb.addPermanentWidget( self.seed_button )
1343 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1344 sb.addPermanentWidget( self.status_button )
1346 run_hook('create_status_bar', (sb,))
1348 self.setStatusBar(sb)
1351 def update_lock_icon(self):
1352 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1353 self.password_button.setIcon( icon )
1356 def update_buttons_on_seed(self):
1357 if self.wallet.has_seed():
1358 self.seed_button.show()
1360 self.seed_button.hide()
1362 if not self.wallet.is_watching_only():
1363 self.password_button.show()
1364 self.send_button.setText(_("Send"))
1366 self.password_button.hide()
1367 self.send_button.setText(_("Create unsigned transaction"))
1370 def change_password_dialog(self):
1371 from password_dialog import PasswordDialog
1372 d = PasswordDialog(self.wallet, self)
1374 self.update_lock_icon()
1377 def new_contact_dialog(self):
1380 d.setWindowTitle(_("New Contact"))
1381 vbox = QVBoxLayout(d)
1382 vbox.addWidget(QLabel(_('New Contact')+':'))
1384 grid = QGridLayout()
1387 grid.addWidget(QLabel(_("Address")), 1, 0)
1388 grid.addWidget(line1, 1, 1)
1389 grid.addWidget(QLabel(_("Name")), 2, 0)
1390 grid.addWidget(line2, 2, 1)
1392 vbox.addLayout(grid)
1393 vbox.addLayout(ok_cancel_buttons(d))
1398 address = str(line1.text())
1399 label = unicode(line2.text())
1401 if not is_valid(address):
1402 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1405 self.wallet.add_contact(address)
1407 self.wallet.set_label(address, label)
1409 self.update_contacts_tab()
1410 self.update_history_tab()
1411 self.update_completions()
1412 self.tabs.setCurrentIndex(3)
1416 def new_account_dialog(self, password):
1418 dialog = QDialog(self)
1420 dialog.setWindowTitle(_("New Account"))
1422 vbox = QVBoxLayout()
1423 vbox.addWidget(QLabel(_('Account name')+':'))
1426 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1427 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1432 vbox.addLayout(ok_cancel_buttons(dialog))
1433 dialog.setLayout(vbox)
1437 name = str(e.text())
1440 self.wallet.create_pending_account(name, password)
1441 self.update_receive_tab()
1442 self.tabs.setCurrentIndex(2)
1447 def show_master_public_keys(self):
1449 dialog = QDialog(self)
1451 dialog.setWindowTitle(_("Master Public Keys"))
1453 main_layout = QGridLayout()
1454 mpk_dict = self.wallet.get_master_public_keys()
1456 for key, value in mpk_dict.items():
1457 main_layout.addWidget(QLabel(key), i, 0)
1458 mpk_text = QTextEdit()
1459 mpk_text.setReadOnly(True)
1460 mpk_text.setMaximumHeight(170)
1461 mpk_text.setText(value)
1462 main_layout.addWidget(mpk_text, i + 1, 0)
1465 vbox = QVBoxLayout()
1466 vbox.addLayout(main_layout)
1467 vbox.addLayout(close_button(dialog))
1469 dialog.setLayout(vbox)
1474 def show_seed_dialog(self, password):
1475 if not self.wallet.has_seed():
1476 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1480 mnemonic = self.wallet.get_mnemonic(password)
1482 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1484 from seed_dialog import SeedDialog
1485 d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1490 def show_qrcode(self, data, title = _("QR code")):
1494 d.setWindowTitle(title)
1495 d.setMinimumSize(270, 300)
1496 vbox = QVBoxLayout()
1497 qrw = QRCodeWidget(data)
1498 vbox.addWidget(qrw, 1)
1499 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1500 hbox = QHBoxLayout()
1503 filename = os.path.join(self.config.path, "qrcode.bmp")
1506 bmp.save_qrcode(qrw.qr, filename)
1507 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1509 def copy_to_clipboard():
1510 bmp.save_qrcode(qrw.qr, filename)
1511 self.app.clipboard().setImage(QImage(filename))
1512 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1514 b = QPushButton(_("Copy"))
1516 b.clicked.connect(copy_to_clipboard)
1518 b = QPushButton(_("Save"))
1520 b.clicked.connect(print_qr)
1522 b = QPushButton(_("Close"))
1524 b.clicked.connect(d.accept)
1527 vbox.addLayout(hbox)
1532 def do_protect(self, func, args):
1533 if self.wallet.use_encryption:
1534 password = self.password_dialog()
1540 if args != (False,):
1541 args = (self,) + args + (password,)
1543 args = (self,password)
1547 def show_public_keys(self, address):
1548 if not address: return
1550 pubkey_list = self.wallet.get_public_keys(address)
1551 except Exception as e:
1552 traceback.print_exc(file=sys.stdout)
1553 self.show_message(str(e))
1557 d.setMinimumSize(600, 200)
1559 vbox = QVBoxLayout()
1560 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1561 vbox.addWidget( QLabel(_("Public key") + ':'))
1563 keys.setReadOnly(True)
1564 keys.setText('\n'.join(pubkey_list))
1565 vbox.addWidget(keys)
1566 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1567 vbox.addLayout(close_button(d))
1572 def show_private_key(self, address, password):
1573 if not address: return
1575 pk_list = self.wallet.get_private_key(address, password)
1576 except Exception as e:
1577 traceback.print_exc(file=sys.stdout)
1578 self.show_message(str(e))
1582 d.setMinimumSize(600, 200)
1584 vbox = QVBoxLayout()
1585 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1586 vbox.addWidget( QLabel(_("Private key") + ':'))
1588 keys.setReadOnly(True)
1589 keys.setText('\n'.join(pk_list))
1590 vbox.addWidget(keys)
1591 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1592 vbox.addLayout(close_button(d))
1598 def do_sign(self, address, message, signature, password):
1599 message = unicode(message.toPlainText())
1600 message = message.encode('utf-8')
1602 sig = self.wallet.sign_message(str(address.text()), message, password)
1603 signature.setText(sig)
1604 except Exception as e:
1605 self.show_message(str(e))
1607 def do_verify(self, address, message, signature):
1608 message = unicode(message.toPlainText())
1609 message = message.encode('utf-8')
1610 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1611 self.show_message(_("Signature verified"))
1613 self.show_message(_("Error: wrong signature"))
1616 def sign_verify_message(self, address=''):
1619 d.setWindowTitle(_('Sign/verify Message'))
1620 d.setMinimumSize(410, 290)
1622 layout = QGridLayout(d)
1624 message_e = QTextEdit()
1625 layout.addWidget(QLabel(_('Message')), 1, 0)
1626 layout.addWidget(message_e, 1, 1)
1627 layout.setRowStretch(2,3)
1629 address_e = QLineEdit()
1630 address_e.setText(address)
1631 layout.addWidget(QLabel(_('Address')), 2, 0)
1632 layout.addWidget(address_e, 2, 1)
1634 signature_e = QTextEdit()
1635 layout.addWidget(QLabel(_('Signature')), 3, 0)
1636 layout.addWidget(signature_e, 3, 1)
1637 layout.setRowStretch(3,1)
1639 hbox = QHBoxLayout()
1641 b = QPushButton(_("Sign"))
1642 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1645 b = QPushButton(_("Verify"))
1646 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1649 b = QPushButton(_("Close"))
1650 b.clicked.connect(d.accept)
1652 layout.addLayout(hbox, 4, 1)
1657 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1659 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1660 message_e.setText(decrypted)
1661 except Exception as e:
1662 self.show_message(str(e))
1665 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1666 message = unicode(message_e.toPlainText())
1667 message = message.encode('utf-8')
1669 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1670 encrypted_e.setText(encrypted)
1671 except Exception as e:
1672 self.show_message(str(e))
1676 def encrypt_message(self, address = ''):
1679 d.setWindowTitle(_('Encrypt/decrypt Message'))
1680 d.setMinimumSize(610, 490)
1682 layout = QGridLayout(d)
1684 message_e = QTextEdit()
1685 layout.addWidget(QLabel(_('Message')), 1, 0)
1686 layout.addWidget(message_e, 1, 1)
1687 layout.setRowStretch(2,3)
1689 pubkey_e = QLineEdit()
1691 pubkey = self.wallet.getpubkeys(address)[0]
1692 pubkey_e.setText(pubkey)
1693 layout.addWidget(QLabel(_('Public key')), 2, 0)
1694 layout.addWidget(pubkey_e, 2, 1)
1696 encrypted_e = QTextEdit()
1697 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1698 layout.addWidget(encrypted_e, 3, 1)
1699 layout.setRowStretch(3,1)
1701 hbox = QHBoxLayout()
1702 b = QPushButton(_("Encrypt"))
1703 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1706 b = QPushButton(_("Decrypt"))
1707 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1710 b = QPushButton(_("Close"))
1711 b.clicked.connect(d.accept)
1714 layout.addLayout(hbox, 4, 1)
1718 def question(self, msg):
1719 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1721 def show_message(self, msg):
1722 QMessageBox.information(self, _('Message'), msg, _('OK'))
1724 def password_dialog(self, msg=None):
1727 d.setWindowTitle(_("Enter Password"))
1732 vbox = QVBoxLayout()
1734 msg = _('Please enter your password')
1735 vbox.addWidget(QLabel(msg))
1737 grid = QGridLayout()
1739 grid.addWidget(QLabel(_('Password')), 1, 0)
1740 grid.addWidget(pw, 1, 1)
1741 vbox.addLayout(grid)
1743 vbox.addLayout(ok_cancel_buttons(d))
1746 run_hook('password_dialog', pw, grid, 1)
1747 if not d.exec_(): return
1748 return unicode(pw.text())
1757 def tx_from_text(self, txt):
1758 "json or raw hexadecimal"
1761 tx = Transaction(txt)
1767 tx_dict = json.loads(str(txt))
1768 assert "hex" in tx_dict.keys()
1769 tx = Transaction(tx_dict["hex"])
1770 if tx_dict.has_key("input_info"):
1771 input_info = json.loads(tx_dict['input_info'])
1772 tx.add_input_info(input_info)
1775 traceback.print_exc(file=sys.stdout)
1778 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1782 def read_tx_from_file(self):
1783 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1787 with open(fileName, "r") as f:
1788 file_content = f.read()
1789 except (ValueError, IOError, os.error), reason:
1790 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1792 return self.tx_from_text(file_content)
1796 def sign_raw_transaction(self, tx, input_info, password):
1797 self.wallet.signrawtransaction(tx, input_info, [], password)
1799 def do_process_from_text(self):
1800 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1803 tx = self.tx_from_text(text)
1805 self.show_transaction(tx)
1807 def do_process_from_file(self):
1808 tx = self.read_tx_from_file()
1810 self.show_transaction(tx)
1812 def do_process_from_txid(self):
1813 from electrum import transaction
1814 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1816 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1818 tx = transaction.Transaction(r)
1820 self.show_transaction(tx)
1822 self.show_message("unknown transaction")
1824 def do_process_from_csvReader(self, csvReader):
1829 for position, row in enumerate(csvReader):
1831 if not is_valid(address):
1832 errors.append((position, address))
1834 amount = Decimal(row[1])
1835 amount = int(100000000*amount)
1836 outputs.append((address, amount))
1837 except (ValueError, IOError, os.error), reason:
1838 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1842 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1843 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1847 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1848 except Exception as e:
1849 self.show_message(str(e))
1852 self.show_transaction(tx)
1854 def do_process_from_csv_file(self):
1855 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1859 with open(fileName, "r") as f:
1860 csvReader = csv.reader(f)
1861 self.do_process_from_csvReader(csvReader)
1862 except (ValueError, IOError, os.error), reason:
1863 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1866 def do_process_from_csv_text(self):
1867 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1868 + _("Format: address, amount. One output per line"), _("Load CSV"))
1871 f = StringIO.StringIO(text)
1872 csvReader = csv.reader(f)
1873 self.do_process_from_csvReader(csvReader)
1878 def export_privkeys_dialog(self, password):
1879 if self.wallet.is_watching_only():
1880 self.show_message(_("This is a watching-only wallet"))
1884 d.setWindowTitle(_('Private keys'))
1885 d.setMinimumSize(850, 300)
1886 vbox = QVBoxLayout(d)
1888 msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
1889 _("Exposing a single private key can compromise your entire wallet!"),
1890 _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
1891 vbox.addWidget(QLabel(msg))
1897 defaultname = 'electrum-private-keys.csv'
1898 select_msg = _('Select file to export your private keys to')
1899 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1900 vbox.addLayout(hbox)
1902 h, b = ok_cancel_buttons2(d, _('Export'))
1907 addresses = self.wallet.addresses(True)
1909 def privkeys_thread():
1910 for addr in addresses:
1914 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
1915 d.emit(SIGNAL('computing_privkeys'))
1916 d.emit(SIGNAL('show_privkeys'))
1918 def show_privkeys():
1919 s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
1923 d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
1924 d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
1925 threading.Thread(target=privkeys_thread).start()
1931 filename = filename_e.text()
1936 self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
1937 except (IOError, os.error), reason:
1938 export_error_label = _("Electrum was unable to produce a private key-export.")
1939 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1941 except Exception as e:
1942 self.show_message(str(e))
1945 self.show_message(_("Private keys exported."))
1948 def do_export_privkeys(self, fileName, pklist, is_csv):
1949 with open(fileName, "w+") as f:
1951 transaction = csv.writer(f)
1952 transaction.writerow(["address", "private_key"])
1953 for addr, pk in pklist.items():
1954 transaction.writerow(["%34s"%addr,pk])
1957 f.write(json.dumps(pklist, indent = 4))
1960 def do_import_labels(self):
1961 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1962 if not labelsFile: return
1964 f = open(labelsFile, 'r')
1967 for key, value in json.loads(data).items():
1968 self.wallet.set_label(key, value)
1969 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1970 except (IOError, os.error), reason:
1971 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1974 def do_export_labels(self):
1975 labels = self.wallet.labels
1977 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1979 with open(fileName, 'w+') as f:
1980 json.dump(labels, f)
1981 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1982 except (IOError, os.error), reason:
1983 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1986 def export_history_dialog(self):
1989 d.setWindowTitle(_('Export History'))
1990 d.setMinimumSize(400, 200)
1991 vbox = QVBoxLayout(d)
1993 defaultname = os.path.expanduser('~/electrum-history.csv')
1994 select_msg = _('Select file to export your wallet transactions to')
1996 hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
1997 vbox.addLayout(hbox)
2001 h, b = ok_cancel_buttons2(d, _('Export'))
2006 filename = filename_e.text()
2011 self.do_export_history(self.wallet, filename, csv_button.isChecked())
2012 except (IOError, os.error), reason:
2013 export_error_label = _("Electrum was unable to produce a transaction export.")
2014 QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2017 QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2020 def do_export_history(self, wallet, fileName, is_csv):
2021 history = wallet.get_tx_history()
2023 for item in history:
2024 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2026 if timestamp is not None:
2028 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2029 except [RuntimeError, TypeError, NameError] as reason:
2030 time_string = "unknown"
2033 time_string = "unknown"
2035 time_string = "pending"
2037 if value is not None:
2038 value_string = format_satoshis(value, True)
2043 fee_string = format_satoshis(fee, True)
2048 label, is_default_label = wallet.get_label(tx_hash)
2049 label = label.encode('utf-8')
2053 balance_string = format_satoshis(balance, False)
2055 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2057 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2059 with open(fileName, "w+") as f:
2061 transaction = csv.writer(f)
2062 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2064 transaction.writerow(line)
2067 f.write(json.dumps(lines, indent = 4))
2070 def sweep_key_dialog(self):
2072 d.setWindowTitle(_('Sweep private keys'))
2073 d.setMinimumSize(600, 300)
2075 vbox = QVBoxLayout(d)
2076 vbox.addWidget(QLabel(_("Enter private keys")))
2078 keys_e = QTextEdit()
2079 keys_e.setTabChangesFocus(True)
2080 vbox.addWidget(keys_e)
2082 h, address_e = address_field(self.wallet.addresses())
2086 hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2087 vbox.addLayout(hbox)
2088 button.setEnabled(False)
2091 addr = str(address_e.text())
2092 if bitcoin.is_address(addr):
2096 pk = str(keys_e.toPlainText()).strip()
2097 if Wallet.is_private_key(pk):
2100 f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2101 keys_e.textChanged.connect(f)
2102 address_e.textChanged.connect(f)
2106 fee = self.wallet.fee
2107 tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2108 self.show_transaction(tx)
2112 def do_import_privkey(self, password):
2113 if not self.wallet.has_imported_keys():
2114 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2115 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2116 + _('Are you sure you understand what you are doing?'), 3, 4)
2119 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2122 text = str(text).split()
2127 addr = self.wallet.import_key(key, password)
2128 except Exception as e:
2134 addrlist.append(addr)
2136 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2138 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2139 self.update_receive_tab()
2140 self.update_history_tab()
2143 def settings_dialog(self):
2145 d.setWindowTitle(_('Electrum Settings'))
2147 vbox = QVBoxLayout()
2148 grid = QGridLayout()
2149 grid.setColumnStretch(0,1)
2151 nz_label = QLabel(_('Display zeros') + ':')
2152 grid.addWidget(nz_label, 0, 0)
2153 nz_e = AmountEdit(None,True)
2154 nz_e.setText("%d"% self.num_zeros)
2155 grid.addWidget(nz_e, 0, 1)
2156 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2157 grid.addWidget(HelpButton(msg), 0, 2)
2158 if not self.config.is_modifiable('num_zeros'):
2159 for w in [nz_e, nz_label]: w.setEnabled(False)
2161 lang_label=QLabel(_('Language') + ':')
2162 grid.addWidget(lang_label, 1, 0)
2163 lang_combo = QComboBox()
2164 from electrum.i18n import languages
2165 lang_combo.addItems(languages.values())
2167 index = languages.keys().index(self.config.get("language",''))
2170 lang_combo.setCurrentIndex(index)
2171 grid.addWidget(lang_combo, 1, 1)
2172 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2173 if not self.config.is_modifiable('language'):
2174 for w in [lang_combo, lang_label]: w.setEnabled(False)
2177 fee_label = QLabel(_('Transaction fee') + ':')
2178 grid.addWidget(fee_label, 2, 0)
2179 fee_e = AmountEdit(self.get_decimal_point)
2180 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2181 grid.addWidget(fee_e, 2, 1)
2182 msg = _('Fee per kilobyte of transaction.') + ' ' \
2183 + _('Recommended value') + ': ' + self.format_amount(20000)
2184 grid.addWidget(HelpButton(msg), 2, 2)
2185 if not self.config.is_modifiable('fee_per_kb'):
2186 for w in [fee_e, fee_label]: w.setEnabled(False)
2188 units = ['BTC', 'mBTC']
2189 unit_label = QLabel(_('Base unit') + ':')
2190 grid.addWidget(unit_label, 3, 0)
2191 unit_combo = QComboBox()
2192 unit_combo.addItems(units)
2193 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2194 grid.addWidget(unit_combo, 3, 1)
2195 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2196 + '\n1BTC=1000mBTC.\n' \
2197 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2199 usechange_cb = QCheckBox(_('Use change addresses'))
2200 usechange_cb.setChecked(self.wallet.use_change)
2201 grid.addWidget(usechange_cb, 4, 0)
2202 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2203 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2205 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2206 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2207 grid.addWidget(block_ex_label, 5, 0)
2208 block_ex_combo = QComboBox()
2209 block_ex_combo.addItems(block_explorers)
2210 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2211 grid.addWidget(block_ex_combo, 5, 1)
2212 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2214 show_tx = self.config.get('show_before_broadcast', False)
2215 showtx_cb = QCheckBox(_('Show before broadcast'))
2216 showtx_cb.setChecked(show_tx)
2217 grid.addWidget(showtx_cb, 6, 0)
2218 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2220 vbox.addLayout(grid)
2222 vbox.addLayout(ok_cancel_buttons(d))
2226 if not d.exec_(): return
2229 fee = self.fee_e.get_amount()
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))