3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
33 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
34 from electrum.plugins import run_hook
38 from electrum.wallet import format_satoshis
39 from electrum import Transaction
40 from electrum import mnemonic
41 from electrum import util, bitcoin, commands, Interface, Wallet
42 from electrum import SimpleConfig, Wallet, WalletStorage
45 from electrum import bmp, pyqrnative
47 from amountedit import AmountEdit
48 from network_dialog import NetworkDialog
49 from qrcodewidget import QRCodeWidget
51 from decimal import Decimal
59 if platform.system() == 'Windows':
60 MONOSPACE_FONT = 'Lucida Console'
61 elif platform.system() == 'Darwin':
62 MONOSPACE_FONT = 'Monaco'
64 MONOSPACE_FONT = 'monospace'
66 from electrum import ELECTRUM_VERSION
76 class StatusBarButton(QPushButton):
77 def __init__(self, icon, tooltip, func):
78 QPushButton.__init__(self, icon, '')
79 self.setToolTip(tooltip)
81 self.setMaximumWidth(25)
82 self.clicked.connect(func)
84 self.setIconSize(QSize(25,25))
86 def keyPressEvent(self, e):
87 if e.key() == QtCore.Qt.Key_Return:
99 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
101 class ElectrumWindow(QMainWindow):
105 def __init__(self, config, network, gui_object):
106 QMainWindow.__init__(self)
109 self.network = network
110 self.tray = gui_object.tray
111 self.go_lite = gui_object.go_lite
114 self.create_status_bar()
115 self.need_update = threading.Event()
117 self.decimal_point = config.get('decimal_point', 5)
118 self.num_zeros = int(config.get('num_zeros',0))
120 set_language(config.get('language'))
122 self.funds_error = False
123 self.completions = QStringListModel()
125 self.tabs = tabs = QTabWidget(self)
126 self.column_widths = self.config.get("column_widths_2", default_column_widths )
127 tabs.addTab(self.create_history_tab(), _('History') )
128 tabs.addTab(self.create_send_tab(), _('Send') )
129 tabs.addTab(self.create_receive_tab(), _('Receive') )
130 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
131 tabs.addTab(self.create_console_tab(), _('Console') )
132 tabs.setMinimumSize(600, 400)
133 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
134 self.setCentralWidget(tabs)
136 g = self.config.get("winpos-qt",[100, 100, 840, 400])
137 self.setGeometry(g[0], g[1], g[2], g[3])
139 self.setWindowIcon(QIcon(":icons/electrum.png"))
142 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
143 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
144 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
145 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
146 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
148 for i in range(tabs.count()):
149 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
151 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
152 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
153 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
154 self.connect(self, QtCore.SIGNAL('send_tx2'), self.send_tx2)
155 self.connect(self, QtCore.SIGNAL('send_tx3'), self.send_tx3)
157 self.history_list.setFocus(True)
161 self.network.register_callback('updated', lambda: self.need_update.set())
162 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
163 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
164 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
165 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
167 # set initial message
168 self.console.showMessage(self.network.banner)
173 def update_account_selector(self):
175 accounts = self.wallet.get_account_names()
176 self.account_selector.clear()
177 if len(accounts) > 1:
178 self.account_selector.addItems([_("All accounts")] + accounts.values())
179 self.account_selector.setCurrentIndex(0)
180 self.account_selector.show()
182 self.account_selector.hide()
185 def load_wallet(self, wallet):
188 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
189 self.current_account = self.wallet.storage.get("current_account", None)
191 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
192 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
193 self.setWindowTitle( title )
195 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
196 self.notify_transactions()
197 self.update_account_selector()
198 self.new_account.setEnabled(self.wallet.can_create_accounts())
199 self.update_lock_icon()
200 self.update_buttons_on_seed()
201 self.update_console()
203 run_hook('load_wallet', wallet)
206 def open_wallet(self):
207 wallet_folder = self.wallet.storage.path
208 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
212 storage = WalletStorage({'wallet_path': filename})
213 if not storage.file_exists:
214 self.show_message("file not found "+ filename)
217 self.wallet.stop_threads()
220 wallet = Wallet(storage)
221 wallet.start_threads(self.network)
223 self.load_wallet(wallet)
227 def backup_wallet(self):
229 path = self.wallet.storage.path
230 wallet_folder = os.path.dirname(path)
231 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
235 new_path = os.path.join(wallet_folder, filename)
238 shutil.copy2(path, new_path)
239 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
240 except (IOError, os.error), reason:
241 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
244 def new_wallet(self):
247 wallet_folder = os.path.dirname(self.wallet.storage.path)
248 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
251 filename = os.path.join(wallet_folder, filename)
253 storage = WalletStorage({'wallet_path': filename})
254 if storage.file_exists:
255 QMessageBox.critical(None, "Error", _("File exists"))
258 wizard = installwizard.InstallWizard(self.config, self.network, storage)
259 wallet = wizard.run()
261 self.load_wallet(wallet)
265 def init_menubar(self):
268 file_menu = menubar.addMenu(_("&File"))
269 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
270 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
271 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
272 file_menu.addAction(_("&Quit"), self.close)
274 wallet_menu = menubar.addMenu(_("&Wallet"))
275 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
276 self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
278 wallet_menu.addSeparator()
280 wallet_menu.addAction(_("&Password"), self.change_password_dialog)
281 wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
282 wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
284 wallet_menu.addSeparator()
285 labels_menu = wallet_menu.addMenu(_("&Labels"))
286 labels_menu.addAction(_("&Import"), self.do_import_labels)
287 labels_menu.addAction(_("&Export"), self.do_export_labels)
289 keys_menu = wallet_menu.addMenu(_("&Private keys"))
290 keys_menu.addAction(_("&Import"), self.do_import_privkey)
291 keys_menu.addAction(_("&Export"), self.do_export_privkeys)
293 wallet_menu.addAction(_("&Export History"), self.do_export_history)
295 tools_menu = menubar.addMenu(_("&Tools"))
297 # Settings / Preferences are all reserved keywords in OSX using this as work around
298 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
299 tools_menu.addAction(_("&Network"), self.run_network_dialog)
300 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
301 tools_menu.addSeparator()
302 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
303 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
304 tools_menu.addSeparator()
306 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
307 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
308 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
310 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
311 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
312 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
313 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
315 help_menu = menubar.addMenu(_("&Help"))
316 help_menu.addAction(_("&About"), self.show_about)
317 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
318 help_menu.addSeparator()
319 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
320 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
322 self.setMenuBar(menubar)
324 def show_about(self):
325 QMessageBox.about(self, "Electrum",
326 _("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."))
328 def show_report_bug(self):
329 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
330 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
333 def notify_transactions(self):
334 if not self.network or not self.network.is_connected():
337 print_error("Notifying GUI")
338 if len(self.network.pending_transactions_for_notifications) > 0:
339 # Combine the transactions if there are more then three
340 tx_amount = len(self.network.pending_transactions_for_notifications)
343 for tx in self.network.pending_transactions_for_notifications:
344 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
348 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
349 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
351 self.network.pending_transactions_for_notifications = []
353 for tx in self.network.pending_transactions_for_notifications:
355 self.network.pending_transactions_for_notifications.remove(tx)
356 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
358 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
360 def notify(self, message):
361 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
365 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
366 def getOpenFileName(self, title, filter = ""):
367 directory = self.config.get('io_dir', os.path.expanduser('~'))
368 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
369 if fileName and directory != os.path.dirname(fileName):
370 self.config.set_key('io_dir', os.path.dirname(fileName), True)
373 def getSaveFileName(self, title, filename, filter = ""):
374 directory = self.config.get('io_dir', os.path.expanduser('~'))
375 path = os.path.join( directory, filename )
376 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
377 if fileName and directory != os.path.dirname(fileName):
378 self.config.set_key('io_dir', os.path.dirname(fileName), True)
382 QMainWindow.close(self)
383 run_hook('close_main_window')
385 def connect_slots(self, sender):
386 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
387 self.previous_payto_e=''
389 def timer_actions(self):
390 if self.need_update.is_set():
392 self.need_update.clear()
393 run_hook('timer_actions')
395 def format_amount(self, x, is_diff=False, whitespaces=False):
396 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
398 def read_amount(self, x):
399 if x in['.', '']: return None
400 p = pow(10, self.decimal_point)
401 return int( p * Decimal(x) )
404 assert self.decimal_point in [5,8]
405 return "BTC" if self.decimal_point == 8 else "mBTC"
408 def update_status(self):
409 if self.network is None or not self.network.is_running():
411 icon = QIcon(":icons/status_disconnected.png")
413 elif self.network.is_connected():
414 if not self.wallet.up_to_date:
415 text = _("Synchronizing...")
416 icon = QIcon(":icons/status_waiting.png")
417 elif self.network.server_lag > 1:
418 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
419 icon = QIcon(":icons/status_lagging.png")
421 c, u = self.wallet.get_account_balance(self.current_account)
422 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
423 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
425 # append fiat balance and price from exchange rate plugin
427 run_hook('get_fiat_status_text', c+u, r)
432 self.tray.setToolTip(text)
433 icon = QIcon(":icons/status_connected.png")
435 text = _("Not connected")
436 icon = QIcon(":icons/status_disconnected.png")
438 self.balance_label.setText(text)
439 self.status_button.setIcon( icon )
442 def update_wallet(self):
444 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
445 self.update_history_tab()
446 self.update_receive_tab()
447 self.update_contacts_tab()
448 self.update_completions()
451 def create_history_tab(self):
452 self.history_list = l = MyTreeWidget(self)
454 for i,width in enumerate(self.column_widths['history']):
455 l.setColumnWidth(i, width)
456 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
457 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
458 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
460 l.customContextMenuRequested.connect(self.create_history_menu)
464 def create_history_menu(self, position):
465 self.history_list.selectedIndexes()
466 item = self.history_list.currentItem()
467 be = self.config.get('block_explorer', 'Blockchain.info')
468 if be == 'Blockchain.info':
469 block_explorer = 'https://blockchain.info/tx/'
470 elif be == 'Blockr.io':
471 block_explorer = 'https://blockr.io/tx/info/'
472 elif be == 'Insight.is':
473 block_explorer = 'http://live.insight.is/tx/'
475 tx_hash = str(item.data(0, Qt.UserRole).toString())
476 if not tx_hash: return
478 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
479 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
480 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
481 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
482 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
485 def show_transaction(self, tx):
486 import transaction_dialog
487 d = transaction_dialog.TxDialog(tx, self)
490 def tx_label_clicked(self, item, column):
491 if column==2 and item.isSelected():
493 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
494 self.history_list.editItem( item, column )
495 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
498 def tx_label_changed(self, item, column):
502 tx_hash = str(item.data(0, Qt.UserRole).toString())
503 tx = self.wallet.transactions.get(tx_hash)
504 text = unicode( item.text(2) )
505 self.wallet.set_label(tx_hash, text)
507 item.setForeground(2, QBrush(QColor('black')))
509 text = self.wallet.get_default_label(tx_hash)
510 item.setText(2, text)
511 item.setForeground(2, QBrush(QColor('gray')))
515 def edit_label(self, is_recv):
516 l = self.receive_list if is_recv else self.contacts_list
517 item = l.currentItem()
518 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
519 l.editItem( item, 1 )
520 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
524 def address_label_clicked(self, item, column, l, column_addr, column_label):
525 if column == column_label and item.isSelected():
526 is_editable = item.data(0, 32).toBool()
529 addr = unicode( item.text(column_addr) )
530 label = unicode( item.text(column_label) )
531 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
532 l.editItem( item, column )
533 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
536 def address_label_changed(self, item, column, l, column_addr, column_label):
537 if column == column_label:
538 addr = unicode( item.text(column_addr) )
539 text = unicode( item.text(column_label) )
540 is_editable = item.data(0, 32).toBool()
544 changed = self.wallet.set_label(addr, text)
546 self.update_history_tab()
547 self.update_completions()
549 self.current_item_changed(item)
551 run_hook('item_changed', item, column)
554 def current_item_changed(self, a):
555 run_hook('current_item_changed', a)
559 def update_history_tab(self):
561 self.history_list.clear()
562 for item in self.wallet.get_tx_history(self.current_account):
563 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
564 time_str = _("unknown")
567 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
569 time_str = _("error")
572 time_str = 'unverified'
573 icon = QIcon(":icons/unconfirmed.png")
576 icon = QIcon(":icons/unconfirmed.png")
578 icon = QIcon(":icons/clock%d.png"%conf)
580 icon = QIcon(":icons/confirmed.png")
582 if value is not None:
583 v_str = self.format_amount(value, True, whitespaces=True)
587 balance_str = self.format_amount(balance, whitespaces=True)
590 label, is_default_label = self.wallet.get_label(tx_hash)
592 label = _('Pruned transaction outputs')
593 is_default_label = False
595 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
596 item.setFont(2, QFont(MONOSPACE_FONT))
597 item.setFont(3, QFont(MONOSPACE_FONT))
598 item.setFont(4, QFont(MONOSPACE_FONT))
600 item.setForeground(3, QBrush(QColor("#BC1E1E")))
602 item.setData(0, Qt.UserRole, tx_hash)
603 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
605 item.setForeground(2, QBrush(QColor('grey')))
607 item.setIcon(0, icon)
608 self.history_list.insertTopLevelItem(0,item)
611 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
612 run_hook('history_tab_update')
615 def create_send_tab(self):
620 grid.setColumnMinimumWidth(3,300)
621 grid.setColumnStretch(5,1)
624 self.payto_e = QLineEdit()
625 grid.addWidget(QLabel(_('Pay to')), 1, 0)
626 grid.addWidget(self.payto_e, 1, 1, 1, 3)
628 grid.addWidget(HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')), 1, 4)
630 completer = QCompleter()
631 completer.setCaseSensitivity(False)
632 self.payto_e.setCompleter(completer)
633 completer.setModel(self.completions)
635 self.message_e = QLineEdit()
636 grid.addWidget(QLabel(_('Description')), 2, 0)
637 grid.addWidget(self.message_e, 2, 1, 1, 3)
638 grid.addWidget(HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')), 2, 4)
640 self.from_label = QLabel(_('From'))
641 grid.addWidget(self.from_label, 3, 0)
642 self.from_list = QTreeWidget(self)
643 self.from_list.setColumnCount(2)
644 self.from_list.setColumnWidth(0, 350)
645 self.from_list.setColumnWidth(1, 50)
646 self.from_list.setHeaderHidden (True)
647 self.from_list.setMaximumHeight(80)
648 grid.addWidget(self.from_list, 3, 1, 1, 3)
649 self.set_pay_from([])
651 self.amount_e = AmountEdit(self.base_unit)
652 grid.addWidget(QLabel(_('Amount')), 4, 0)
653 grid.addWidget(self.amount_e, 4, 1, 1, 2)
654 grid.addWidget(HelpButton(
655 _('Amount to be sent.') + '\n\n' \
656 + _('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.') \
657 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
659 self.fee_e = AmountEdit(self.base_unit)
660 grid.addWidget(QLabel(_('Fee')), 5, 0)
661 grid.addWidget(self.fee_e, 5, 1, 1, 2)
662 grid.addWidget(HelpButton(
663 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
664 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
665 + _('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)
667 run_hook('exchange_rate_button', grid)
669 self.send_button = EnterButton(_("Send"), self.do_send)
670 grid.addWidget(self.send_button, 6, 1)
672 b = EnterButton(_("Clear"),self.do_clear)
673 grid.addWidget(b, 6, 2)
675 self.payto_sig = QLabel('')
676 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
678 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
679 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
688 def entry_changed( is_fee ):
689 self.funds_error = False
691 if self.amount_e.is_shortcut:
692 self.amount_e.is_shortcut = False
693 sendable = self.get_sendable_balance()
694 # there is only one output because we are completely spending inputs
695 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
696 fee = self.wallet.estimated_fee(inputs, 1)
698 self.amount_e.setText( self.format_amount(amount) )
699 self.fee_e.setText( self.format_amount( fee ) )
702 amount = self.read_amount(str(self.amount_e.text()))
703 fee = self.read_amount(str(self.fee_e.text()))
705 if not is_fee: fee = None
708 # assume that there will be 2 outputs (one for change)
709 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
711 self.fee_e.setText( self.format_amount( fee ) )
714 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
718 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
719 self.funds_error = True
720 text = _( "Not enough funds" )
721 c, u = self.wallet.get_frozen_balance()
722 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
724 self.statusBar().showMessage(text)
725 self.amount_e.setPalette(palette)
726 self.fee_e.setPalette(palette)
728 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
729 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
731 run_hook('create_send_tab', grid)
735 def set_pay_from(self, l):
737 self.from_list.clear()
738 self.from_label.setHidden(len(self.pay_from) == 0)
739 self.from_list.setHidden(len(self.pay_from) == 0)
740 for addr in self.pay_from:
741 c, u = self.wallet.get_addr_balance(addr)
742 balance = self.format_amount(c + u)
743 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
746 def update_completions(self):
748 for addr,label in self.wallet.labels.items():
749 if addr in self.wallet.addressbook:
750 l.append( label + ' <' + addr + '>')
752 run_hook('update_completions', l)
753 self.completions.setStringList(l)
757 return lambda s, *args: s.do_protect(func, args)
762 label = unicode( self.message_e.text() )
763 r = unicode( self.payto_e.text() )
766 # label or alias, with address in brackets
767 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
768 to_address = m.group(2) if m else r
770 if not is_valid(to_address):
771 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
775 amount = self.read_amount(unicode( self.amount_e.text()))
777 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
780 fee = self.read_amount(unicode( self.fee_e.text()))
782 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
785 confirm_amount = self.config.get('confirm_amount', 100000000)
786 if amount >= confirm_amount:
787 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
790 confirm_fee = self.config.get('confirm_fee', 100000)
791 if fee >= confirm_fee:
792 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()}):
795 self.send_tx(to_address, amount, fee, label)
798 def waiting_dialog(self, message):
800 d.setWindowTitle('Please wait')
802 vbox = QVBoxLayout(d)
809 def send_tx(self, to_address, amount, fee, label, password):
811 # first, create an unsigned tx
812 domain = self.get_payment_sources()
813 outputs = [(to_address, amount)]
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))
822 # call hook to see if plugin needs gui interaction
823 run_hook('send_tx', tx)
829 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
830 self.wallet.sign_transaction(tx, keypairs, password)
831 self.signed_tx_data = (tx, fee, label)
832 self.emit(SIGNAL('send_tx2'))
833 self.tx_wait_dialog = self.waiting_dialog('Signing..')
834 threading.Thread(target=sign_thread).start()
836 # add recipient to addressbook
837 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
838 self.wallet.addressbook.append(to_address)
842 tx, fee, label = self.signed_tx_data
843 self.tx_wait_dialog.accept()
846 self.show_message(tx.error)
849 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
850 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
854 self.wallet.set_label(tx.hash(), label)
856 if not tx.is_complete() or self.config.get('show_before_broadcast'):
857 self.show_transaction(tx)
861 def broadcast_thread():
862 self.tx_broadcast_result = self.wallet.sendtx(tx)
863 self.emit(SIGNAL('send_tx3'))
864 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
865 threading.Thread(target=broadcast_thread).start()
869 self.tx_broadcast_dialog.accept()
870 status, msg = self.tx_broadcast_result
872 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
874 self.update_contacts_tab()
876 QMessageBox.warning(self, _('Error'), msg, _('OK'))
884 def set_send(self, address, amount, label, message):
886 if label and self.wallet.labels.get(address) != label:
887 if self.question('Give label "%s" to address %s ?'%(label,address)):
888 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
889 self.wallet.addressbook.append(address)
890 self.wallet.set_label(address, label)
892 self.tabs.setCurrentIndex(1)
893 label = self.wallet.labels.get(address)
894 m_addr = label + ' <'+ address +'>' if label else address
895 self.payto_e.setText(m_addr)
897 self.message_e.setText(message)
899 self.amount_e.setText(amount)
903 self.payto_sig.setVisible(False)
904 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
906 self.set_frozen(e,False)
908 self.set_pay_from([])
911 def set_frozen(self,entry,frozen):
913 entry.setReadOnly(True)
914 entry.setFrame(False)
916 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
917 entry.setPalette(palette)
919 entry.setReadOnly(False)
922 palette.setColor(entry.backgroundRole(), QColor('white'))
923 entry.setPalette(palette)
926 def set_addrs_frozen(self,addrs,freeze):
928 if not addr: continue
929 if addr in self.wallet.frozen_addresses and not freeze:
930 self.wallet.unfreeze(addr)
931 elif addr not in self.wallet.frozen_addresses and freeze:
932 self.wallet.freeze(addr)
933 self.update_receive_tab()
937 def create_list_tab(self, headers):
938 "generic tab creation method"
939 l = MyTreeWidget(self)
940 l.setColumnCount( len(headers) )
941 l.setHeaderLabels( headers )
951 vbox.addWidget(buttons)
956 buttons.setLayout(hbox)
961 def create_receive_tab(self):
962 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
963 l.setContextMenuPolicy(Qt.CustomContextMenu)
964 l.customContextMenuRequested.connect(self.create_receive_menu)
965 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
966 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
967 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
968 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
969 self.receive_list = l
970 self.receive_buttons_hbox = hbox
977 def save_column_widths(self):
978 self.column_widths["receive"] = []
979 for i in range(self.receive_list.columnCount() -1):
980 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
982 self.column_widths["history"] = []
983 for i in range(self.history_list.columnCount() - 1):
984 self.column_widths["history"].append(self.history_list.columnWidth(i))
986 self.column_widths["contacts"] = []
987 for i in range(self.contacts_list.columnCount() - 1):
988 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
990 self.config.set_key("column_widths_2", self.column_widths, True)
993 def create_contacts_tab(self):
994 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
995 l.setContextMenuPolicy(Qt.CustomContextMenu)
996 l.customContextMenuRequested.connect(self.create_contact_menu)
997 for i,width in enumerate(self.column_widths['contacts']):
998 l.setColumnWidth(i, width)
1000 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1001 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1002 self.contacts_list = l
1003 self.contacts_buttons_hbox = hbox
1008 def delete_imported_key(self, addr):
1009 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1010 self.wallet.delete_imported_key(addr)
1011 self.update_receive_tab()
1012 self.update_history_tab()
1014 def edit_account_label(self, k):
1015 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1017 label = unicode(text)
1018 self.wallet.set_label(k,label)
1019 self.update_receive_tab()
1021 def account_set_expanded(self, item, k, b):
1023 self.accounts_expanded[k] = b
1025 def create_account_menu(self, position, k, item):
1027 if item.isExpanded():
1028 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1030 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1031 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1032 if self.wallet.seed_version > 4:
1033 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1034 if self.wallet.account_is_pending(k):
1035 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1036 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1038 def delete_pending_account(self, k):
1039 self.wallet.delete_pending_account(k)
1040 self.update_receive_tab()
1042 def create_receive_menu(self, position):
1043 # fixme: this function apparently has a side effect.
1044 # if it is not called the menu pops up several times
1045 #self.receive_list.selectedIndexes()
1047 selected = self.receive_list.selectedItems()
1048 multi_select = len(selected) > 1
1049 addrs = [unicode(item.text(0)) for item in selected]
1050 if not multi_select:
1051 item = self.receive_list.itemAt(position)
1055 if not is_valid(addr):
1056 k = str(item.data(0,32).toString())
1058 self.create_account_menu(position, k, item)
1060 item.setExpanded(not item.isExpanded())
1064 if not multi_select:
1065 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1066 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1067 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1068 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1069 if self.wallet.seed:
1070 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1071 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1072 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1073 if addr in self.wallet.imported_keys:
1074 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1076 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1077 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1078 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1079 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1081 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1082 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1084 run_hook('receive_menu', menu, addrs)
1085 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1088 def get_sendable_balance(self):
1089 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1092 def get_payment_sources(self):
1094 return self.pay_from
1096 return self.wallet.get_account_addresses(self.current_account)
1099 def send_from_addresses(self, addrs):
1100 self.set_pay_from( addrs )
1101 self.tabs.setCurrentIndex(1)
1104 def payto(self, addr):
1106 label = self.wallet.labels.get(addr)
1107 m_addr = label + ' <' + addr + '>' if label else addr
1108 self.tabs.setCurrentIndex(1)
1109 self.payto_e.setText(m_addr)
1110 self.amount_e.setFocus()
1113 def delete_contact(self, x):
1114 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1115 self.wallet.delete_contact(x)
1116 self.wallet.set_label(x, None)
1117 self.update_history_tab()
1118 self.update_contacts_tab()
1119 self.update_completions()
1122 def create_contact_menu(self, position):
1123 item = self.contacts_list.itemAt(position)
1126 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1128 addr = unicode(item.text(0))
1129 label = unicode(item.text(1))
1130 is_editable = item.data(0,32).toBool()
1131 payto_addr = item.data(0,33).toString()
1132 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1133 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1134 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1136 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1137 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1139 run_hook('create_contact_menu', menu, item)
1140 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1143 def update_receive_item(self, item):
1144 item.setFont(0, QFont(MONOSPACE_FONT))
1145 address = str(item.data(0,0).toString())
1146 label = self.wallet.labels.get(address,'')
1147 item.setData(1,0,label)
1148 item.setData(0,32, True) # is editable
1150 run_hook('update_receive_item', address, item)
1152 if not self.wallet.is_mine(address): return
1154 c, u = self.wallet.get_addr_balance(address)
1155 balance = self.format_amount(c + u)
1156 item.setData(2,0,balance)
1158 if address in self.wallet.frozen_addresses:
1159 item.setBackgroundColor(0, QColor('lightblue'))
1162 def update_receive_tab(self):
1163 l = self.receive_list
1166 l.setColumnHidden(2, False)
1167 l.setColumnHidden(3, False)
1168 for i,width in enumerate(self.column_widths['receive']):
1169 l.setColumnWidth(i, width)
1171 if self.current_account is None:
1172 account_items = sorted(self.wallet.accounts.items())
1173 elif self.current_account != -1:
1174 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1178 pending_accounts = self.wallet.get_pending_accounts()
1180 for k, account in account_items:
1182 if len(account_items) + len(pending_accounts) > 1:
1183 name = self.wallet.get_account_name(k)
1184 c,u = self.wallet.get_account_balance(k)
1185 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1186 l.addTopLevelItem(account_item)
1187 account_item.setExpanded(self.accounts_expanded.get(k, True))
1188 account_item.setData(0, 32, k)
1189 if not self.wallet.is_seeded(k):
1190 icon = QIcon(":icons/key.png")
1191 account_item.setIcon(0, icon)
1195 for is_change in ([0,1]):
1196 name = _("Receiving") if not is_change else _("Change")
1197 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1199 account_item.addChild(seq_item)
1201 l.addTopLevelItem(seq_item)
1203 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1205 if not is_change: seq_item.setExpanded(True)
1210 for address in account.get_addresses(is_change):
1211 h = self.wallet.history.get(address,[])
1215 if gap > self.wallet.gap_limit:
1220 c, u = self.wallet.get_addr_balance(address)
1221 num_tx = '*' if h == ['*'] else "%d"%len(h)
1222 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1223 self.update_receive_item(item)
1225 item.setBackgroundColor(1, QColor('red'))
1226 if len(h) > 0 and c == -u:
1228 seq_item.insertChild(0,used_item)
1230 used_item.addChild(item)
1232 seq_item.addChild(item)
1235 for k, addr in pending_accounts:
1236 name = self.wallet.labels.get(k,'')
1237 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1238 self.update_receive_item(item)
1239 l.addTopLevelItem(account_item)
1240 account_item.setExpanded(True)
1241 account_item.setData(0, 32, k)
1242 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1243 account_item.addChild(item)
1244 self.update_receive_item(item)
1247 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1248 c,u = self.wallet.get_imported_balance()
1249 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1250 l.addTopLevelItem(account_item)
1251 account_item.setExpanded(True)
1252 for address in self.wallet.imported_keys.keys():
1253 item = QTreeWidgetItem( [ address, '', '', ''] )
1254 self.update_receive_item(item)
1255 account_item.addChild(item)
1258 # we use column 1 because column 0 may be hidden
1259 l.setCurrentItem(l.topLevelItem(0),1)
1262 def update_contacts_tab(self):
1263 l = self.contacts_list
1266 for address in self.wallet.addressbook:
1267 label = self.wallet.labels.get(address,'')
1268 n = self.wallet.get_num_tx(address)
1269 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1270 item.setFont(0, QFont(MONOSPACE_FONT))
1271 # 32 = label can be edited (bool)
1272 item.setData(0,32, True)
1274 item.setData(0,33, address)
1275 l.addTopLevelItem(item)
1277 run_hook('update_contacts_tab', l)
1278 l.setCurrentItem(l.topLevelItem(0))
1282 def create_console_tab(self):
1283 from console import Console
1284 self.console = console = Console()
1288 def update_console(self):
1289 console = self.console
1290 console.history = self.config.get("console-history",[])
1291 console.history_index = len(console.history)
1293 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1294 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1296 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1298 def mkfunc(f, method):
1299 return lambda *args: apply( f, (method, args, self.password_dialog ))
1301 if m[0]=='_' or m in ['network','wallet']: continue
1302 methods[m] = mkfunc(c._run, m)
1304 console.updateNamespace(methods)
1307 def change_account(self,s):
1308 if s == _("All accounts"):
1309 self.current_account = None
1311 accounts = self.wallet.get_account_names()
1312 for k, v in accounts.items():
1314 self.current_account = k
1315 self.update_history_tab()
1316 self.update_status()
1317 self.update_receive_tab()
1319 def create_status_bar(self):
1322 sb.setFixedHeight(35)
1323 qtVersion = qVersion()
1325 self.balance_label = QLabel("")
1326 sb.addWidget(self.balance_label)
1328 from version_getter import UpdateLabel
1329 self.updatelabel = UpdateLabel(self.config, sb)
1331 self.account_selector = QComboBox()
1332 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1333 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1334 sb.addPermanentWidget(self.account_selector)
1336 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1337 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1339 self.lock_icon = QIcon()
1340 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1341 sb.addPermanentWidget( self.password_button )
1343 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1344 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1345 sb.addPermanentWidget( self.seed_button )
1346 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1347 sb.addPermanentWidget( self.status_button )
1349 run_hook('create_status_bar', (sb,))
1351 self.setStatusBar(sb)
1354 def update_lock_icon(self):
1355 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1356 self.password_button.setIcon( icon )
1359 def update_buttons_on_seed(self):
1360 if not self.wallet.is_watching_only():
1361 self.seed_button.show()
1362 self.password_button.show()
1363 self.send_button.setText(_("Send"))
1365 self.password_button.hide()
1366 self.seed_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 self.wallet.is_watching_only():
1476 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1479 if self.wallet.seed:
1481 mnemonic = self.wallet.get_mnemonic(password)
1483 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1485 from seed_dialog import SeedDialog
1486 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1490 for k in self.wallet.master_private_keys.keys():
1491 pk = self.wallet.get_master_private_key(k, password)
1493 from seed_dialog import PrivateKeysDialog
1494 d = PrivateKeysDialog(self,l)
1501 def show_qrcode(self, data, title = _("QR code")):
1505 d.setWindowTitle(title)
1506 d.setMinimumSize(270, 300)
1507 vbox = QVBoxLayout()
1508 qrw = QRCodeWidget(data)
1509 vbox.addWidget(qrw, 1)
1510 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1511 hbox = QHBoxLayout()
1514 filename = os.path.join(self.config.path, "qrcode.bmp")
1517 bmp.save_qrcode(qrw.qr, filename)
1518 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1520 def copy_to_clipboard():
1521 bmp.save_qrcode(qrw.qr, filename)
1522 self.app.clipboard().setImage(QImage(filename))
1523 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1525 b = QPushButton(_("Copy"))
1527 b.clicked.connect(copy_to_clipboard)
1529 b = QPushButton(_("Save"))
1531 b.clicked.connect(print_qr)
1533 b = QPushButton(_("Close"))
1535 b.clicked.connect(d.accept)
1538 vbox.addLayout(hbox)
1543 def do_protect(self, func, args):
1544 if self.wallet.use_encryption:
1545 password = self.password_dialog()
1551 if args != (False,):
1552 args = (self,) + args + (password,)
1554 args = (self,password)
1558 def show_public_keys(self, address):
1559 if not address: return
1561 pubkey_list = self.wallet.get_public_keys(address)
1562 except Exception as e:
1563 traceback.print_exc(file=sys.stdout)
1564 self.show_message(str(e))
1568 d.setMinimumSize(600, 200)
1570 vbox = QVBoxLayout()
1571 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1572 vbox.addWidget( QLabel(_("Public key") + ':'))
1574 keys.setReadOnly(True)
1575 keys.setText('\n'.join(pubkey_list))
1576 vbox.addWidget(keys)
1577 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1578 vbox.addLayout(close_button(d))
1583 def show_private_key(self, address, password):
1584 if not address: return
1586 pk_list = self.wallet.get_private_key(address, password)
1587 except Exception as e:
1588 traceback.print_exc(file=sys.stdout)
1589 self.show_message(str(e))
1593 d.setMinimumSize(600, 200)
1595 vbox = QVBoxLayout()
1596 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1597 vbox.addWidget( QLabel(_("Private key") + ':'))
1599 keys.setReadOnly(True)
1600 keys.setText('\n'.join(pk_list))
1601 vbox.addWidget(keys)
1602 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1603 vbox.addLayout(close_button(d))
1609 def do_sign(self, address, message, signature, password):
1610 message = unicode(message.toPlainText())
1611 message = message.encode('utf-8')
1613 sig = self.wallet.sign_message(str(address.text()), message, password)
1614 signature.setText(sig)
1615 except Exception as e:
1616 self.show_message(str(e))
1618 def do_verify(self, address, message, signature):
1619 message = unicode(message.toPlainText())
1620 message = message.encode('utf-8')
1621 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1622 self.show_message(_("Signature verified"))
1624 self.show_message(_("Error: wrong signature"))
1627 def sign_verify_message(self, address=''):
1630 d.setWindowTitle(_('Sign/verify Message'))
1631 d.setMinimumSize(410, 290)
1633 layout = QGridLayout(d)
1635 message_e = QTextEdit()
1636 layout.addWidget(QLabel(_('Message')), 1, 0)
1637 layout.addWidget(message_e, 1, 1)
1638 layout.setRowStretch(2,3)
1640 address_e = QLineEdit()
1641 address_e.setText(address)
1642 layout.addWidget(QLabel(_('Address')), 2, 0)
1643 layout.addWidget(address_e, 2, 1)
1645 signature_e = QTextEdit()
1646 layout.addWidget(QLabel(_('Signature')), 3, 0)
1647 layout.addWidget(signature_e, 3, 1)
1648 layout.setRowStretch(3,1)
1650 hbox = QHBoxLayout()
1652 b = QPushButton(_("Sign"))
1653 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1656 b = QPushButton(_("Verify"))
1657 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1660 b = QPushButton(_("Close"))
1661 b.clicked.connect(d.accept)
1663 layout.addLayout(hbox, 4, 1)
1668 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1670 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1671 message_e.setText(decrypted)
1672 except Exception as e:
1673 self.show_message(str(e))
1676 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1677 message = unicode(message_e.toPlainText())
1678 message = message.encode('utf-8')
1680 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1681 encrypted_e.setText(encrypted)
1682 except Exception as e:
1683 self.show_message(str(e))
1687 def encrypt_message(self, address = ''):
1690 d.setWindowTitle(_('Encrypt/decrypt Message'))
1691 d.setMinimumSize(610, 490)
1693 layout = QGridLayout(d)
1695 message_e = QTextEdit()
1696 layout.addWidget(QLabel(_('Message')), 1, 0)
1697 layout.addWidget(message_e, 1, 1)
1698 layout.setRowStretch(2,3)
1700 pubkey_e = QLineEdit()
1702 pubkey = self.wallet.getpubkeys(address)[0]
1703 pubkey_e.setText(pubkey)
1704 layout.addWidget(QLabel(_('Public key')), 2, 0)
1705 layout.addWidget(pubkey_e, 2, 1)
1707 encrypted_e = QTextEdit()
1708 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1709 layout.addWidget(encrypted_e, 3, 1)
1710 layout.setRowStretch(3,1)
1712 hbox = QHBoxLayout()
1713 b = QPushButton(_("Encrypt"))
1714 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1717 b = QPushButton(_("Decrypt"))
1718 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1721 b = QPushButton(_("Close"))
1722 b.clicked.connect(d.accept)
1725 layout.addLayout(hbox, 4, 1)
1729 def question(self, msg):
1730 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1732 def show_message(self, msg):
1733 QMessageBox.information(self, _('Message'), msg, _('OK'))
1735 def password_dialog(self ):
1738 d.setWindowTitle(_("Enter Password"))
1743 vbox = QVBoxLayout()
1744 msg = _('Please enter your password')
1745 vbox.addWidget(QLabel(msg))
1747 grid = QGridLayout()
1749 grid.addWidget(QLabel(_('Password')), 1, 0)
1750 grid.addWidget(pw, 1, 1)
1751 vbox.addLayout(grid)
1753 vbox.addLayout(ok_cancel_buttons(d))
1756 run_hook('password_dialog', pw, grid, 1)
1757 if not d.exec_(): return
1758 return unicode(pw.text())
1767 def tx_from_text(self, txt):
1768 "json or raw hexadecimal"
1771 tx = Transaction(txt)
1777 tx_dict = json.loads(str(txt))
1778 assert "hex" in tx_dict.keys()
1779 assert "complete" in tx_dict.keys()
1780 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1781 if not tx_dict["complete"]:
1782 assert "input_info" in tx_dict.keys()
1783 input_info = json.loads(tx_dict['input_info'])
1784 tx.add_input_info(input_info)
1789 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1793 def read_tx_from_file(self):
1794 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1798 with open(fileName, "r") as f:
1799 file_content = f.read()
1800 except (ValueError, IOError, os.error), reason:
1801 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1803 return self.tx_from_text(file_content)
1807 def sign_raw_transaction(self, tx, input_info, password):
1808 self.wallet.signrawtransaction(tx, input_info, [], password)
1810 def do_process_from_text(self):
1811 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1814 tx = self.tx_from_text(text)
1816 self.show_transaction(tx)
1818 def do_process_from_file(self):
1819 tx = self.read_tx_from_file()
1821 self.show_transaction(tx)
1823 def do_process_from_txid(self):
1824 from electrum import transaction
1825 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1827 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1829 tx = transaction.Transaction(r)
1831 self.show_transaction(tx)
1833 self.show_message("unknown transaction")
1835 def do_process_from_csvReader(self, csvReader):
1840 for position, row in enumerate(csvReader):
1842 if not is_valid(address):
1843 errors.append((position, address))
1845 amount = Decimal(row[1])
1846 amount = int(100000000*amount)
1847 outputs.append((address, amount))
1848 except (ValueError, IOError, os.error), reason:
1849 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1853 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1854 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1858 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1859 except Exception as e:
1860 self.show_message(str(e))
1863 self.show_transaction(tx)
1865 def do_process_from_csv_file(self):
1866 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1870 with open(fileName, "r") as f:
1871 csvReader = csv.reader(f)
1872 self.do_process_from_csvReader(csvReader)
1873 except (ValueError, IOError, os.error), reason:
1874 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1877 def do_process_from_csv_text(self):
1878 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1879 + _("Format: address, amount. One output per line"), _("Load CSV"))
1882 f = StringIO.StringIO(text)
1883 csvReader = csv.reader(f)
1884 self.do_process_from_csvReader(csvReader)
1889 def do_export_privkeys(self, password):
1890 if not self.wallet.seed:
1891 self.show_message(_("This wallet has no seed"))
1894 self.show_message("%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."), _("Exposing a single private key can compromise your entire wallet!"), _("In particular, DO NOT use 'redeem private key' services proposed by third parties.")))
1897 select_export = _('Select file to export your private keys to')
1898 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1900 with open(fileName, "w+") as csvfile:
1901 transaction = csv.writer(csvfile)
1902 transaction.writerow(["address", "private_key"])
1904 addresses = self.wallet.addresses(True)
1906 for addr in addresses:
1907 pk = "".join(self.wallet.get_private_key(addr, password))
1908 transaction.writerow(["%34s"%addr,pk])
1910 self.show_message(_("Private keys exported."))
1912 except (IOError, os.error), reason:
1913 export_error_label = _("Electrum was unable to produce a private key-export.")
1914 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1916 except Exception as e:
1917 self.show_message(str(e))
1921 def do_import_labels(self):
1922 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1923 if not labelsFile: return
1925 f = open(labelsFile, 'r')
1928 for key, value in json.loads(data).items():
1929 self.wallet.set_label(key, value)
1930 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1931 except (IOError, os.error), reason:
1932 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1935 def do_export_labels(self):
1936 labels = self.wallet.labels
1938 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1940 with open(fileName, 'w+') as f:
1941 json.dump(labels, f)
1942 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1943 except (IOError, os.error), reason:
1944 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1947 def do_export_history(self):
1948 wallet = self.wallet
1949 select_export = _('Select file to export your wallet transactions to')
1950 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
1955 with open(fileName, "w+") as csvfile:
1956 transaction = csv.writer(csvfile)
1957 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
1958 for item in wallet.get_tx_history():
1959 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
1961 if timestamp is not None:
1963 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
1964 except [RuntimeError, TypeError, NameError] as reason:
1965 time_string = "unknown"
1968 time_string = "unknown"
1970 time_string = "pending"
1972 if value is not None:
1973 value_string = format_satoshis(value, True)
1978 fee_string = format_satoshis(fee, True)
1983 label, is_default_label = wallet.get_label(tx_hash)
1984 label = label.encode('utf-8')
1988 balance_string = format_satoshis(balance, False)
1989 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
1990 QMessageBox.information(None,_("CSV Export created"), _("Your CSV export has been successfully created."))
1992 except (IOError, os.error), reason:
1993 export_error_label = _("Electrum was unable to produce a transaction export.")
1994 QMessageBox.critical(None,_("Unable to create csv"), export_error_label + "\n" + str(reason))
1999 def do_import_privkey(self, password):
2000 if not self.wallet.imported_keys:
2001 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2002 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2003 + _('Are you sure you understand what you are doing?'), 3, 4)
2006 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2009 text = str(text).split()
2014 addr = self.wallet.import_key(key, password)
2015 except Exception as e:
2021 addrlist.append(addr)
2023 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2025 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2026 self.update_receive_tab()
2027 self.update_history_tab()
2030 def settings_dialog(self):
2032 d.setWindowTitle(_('Electrum Settings'))
2034 vbox = QVBoxLayout()
2035 grid = QGridLayout()
2036 grid.setColumnStretch(0,1)
2038 nz_label = QLabel(_('Display zeros') + ':')
2039 grid.addWidget(nz_label, 0, 0)
2040 nz_e = AmountEdit(None,True)
2041 nz_e.setText("%d"% self.num_zeros)
2042 grid.addWidget(nz_e, 0, 1)
2043 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2044 grid.addWidget(HelpButton(msg), 0, 2)
2045 if not self.config.is_modifiable('num_zeros'):
2046 for w in [nz_e, nz_label]: w.setEnabled(False)
2048 lang_label=QLabel(_('Language') + ':')
2049 grid.addWidget(lang_label, 1, 0)
2050 lang_combo = QComboBox()
2051 from electrum.i18n import languages
2052 lang_combo.addItems(languages.values())
2054 index = languages.keys().index(self.config.get("language",''))
2057 lang_combo.setCurrentIndex(index)
2058 grid.addWidget(lang_combo, 1, 1)
2059 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2060 if not self.config.is_modifiable('language'):
2061 for w in [lang_combo, lang_label]: w.setEnabled(False)
2064 fee_label = QLabel(_('Transaction fee') + ':')
2065 grid.addWidget(fee_label, 2, 0)
2066 fee_e = AmountEdit(self.base_unit)
2067 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2068 grid.addWidget(fee_e, 2, 1)
2069 msg = _('Fee per kilobyte of transaction.') + ' ' \
2070 + _('Recommended value') + ': ' + self.format_amount(20000)
2071 grid.addWidget(HelpButton(msg), 2, 2)
2072 if not self.config.is_modifiable('fee_per_kb'):
2073 for w in [fee_e, fee_label]: w.setEnabled(False)
2075 units = ['BTC', 'mBTC']
2076 unit_label = QLabel(_('Base unit') + ':')
2077 grid.addWidget(unit_label, 3, 0)
2078 unit_combo = QComboBox()
2079 unit_combo.addItems(units)
2080 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2081 grid.addWidget(unit_combo, 3, 1)
2082 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2083 + '\n1BTC=1000mBTC.\n' \
2084 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2086 usechange_cb = QCheckBox(_('Use change addresses'))
2087 usechange_cb.setChecked(self.wallet.use_change)
2088 grid.addWidget(usechange_cb, 4, 0)
2089 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2090 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2092 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2093 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2094 grid.addWidget(block_ex_label, 5, 0)
2095 block_ex_combo = QComboBox()
2096 block_ex_combo.addItems(block_explorers)
2097 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2098 grid.addWidget(block_ex_combo, 5, 1)
2099 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2101 show_tx = self.config.get('show_before_broadcast', False)
2102 showtx_cb = QCheckBox(_('Show before broadcast'))
2103 showtx_cb.setChecked(show_tx)
2104 grid.addWidget(showtx_cb, 6, 0)
2105 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2107 vbox.addLayout(grid)
2109 vbox.addLayout(ok_cancel_buttons(d))
2113 if not d.exec_(): return
2115 fee = unicode(fee_e.text())
2117 fee = self.read_amount(fee)
2119 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2122 self.wallet.set_fee(fee)
2124 nz = unicode(nz_e.text())
2129 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2132 if self.num_zeros != nz:
2134 self.config.set_key('num_zeros', nz, True)
2135 self.update_history_tab()
2136 self.update_receive_tab()
2138 usechange_result = usechange_cb.isChecked()
2139 if self.wallet.use_change != usechange_result:
2140 self.wallet.use_change = usechange_result
2141 self.wallet.storage.put('use_change', self.wallet.use_change)
2143 if showtx_cb.isChecked() != show_tx:
2144 self.config.set_key('show_before_broadcast', not show_tx)
2146 unit_result = units[unit_combo.currentIndex()]
2147 if self.base_unit() != unit_result:
2148 self.decimal_point = 8 if unit_result == 'BTC' else 5
2149 self.config.set_key('decimal_point', self.decimal_point, True)
2150 self.update_history_tab()
2151 self.update_status()
2153 need_restart = False
2155 lang_request = languages.keys()[lang_combo.currentIndex()]
2156 if lang_request != self.config.get('language'):
2157 self.config.set_key("language", lang_request, True)
2160 be_result = block_explorers[block_ex_combo.currentIndex()]
2161 self.config.set_key('block_explorer', be_result, True)
2163 run_hook('close_settings_dialog')
2166 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2169 def run_network_dialog(self):
2170 if not self.network:
2172 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2174 def closeEvent(self, event):
2177 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2178 self.save_column_widths()
2179 self.config.set_key("console-history", self.console.history[-50:], True)
2180 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2184 def plugins_dialog(self):
2185 from electrum.plugins import plugins
2188 d.setWindowTitle(_('Electrum Plugins'))
2191 vbox = QVBoxLayout(d)
2194 scroll = QScrollArea()
2195 scroll.setEnabled(True)
2196 scroll.setWidgetResizable(True)
2197 scroll.setMinimumSize(400,250)
2198 vbox.addWidget(scroll)
2202 w.setMinimumHeight(len(plugins)*35)
2204 grid = QGridLayout()
2205 grid.setColumnStretch(0,1)
2208 def do_toggle(cb, p, w):
2211 if w: w.setEnabled(r)
2213 def mk_toggle(cb, p, w):
2214 return lambda: do_toggle(cb,p,w)
2216 for i, p in enumerate(plugins):
2218 cb = QCheckBox(p.fullname())
2219 cb.setDisabled(not p.is_available())
2220 cb.setChecked(p.is_enabled())
2221 grid.addWidget(cb, i, 0)
2222 if p.requires_settings():
2223 w = p.settings_widget(self)
2224 w.setEnabled( p.is_enabled() )
2225 grid.addWidget(w, i, 1)
2228 cb.clicked.connect(mk_toggle(cb,p,w))
2229 grid.addWidget(HelpButton(p.description()), i, 2)
2231 print_msg(_("Error: cannot display plugin"), p)
2232 traceback.print_exc(file=sys.stdout)
2233 grid.setRowStretch(i+1,1)
2235 vbox.addLayout(close_button(d))
2240 def show_account_details(self, k):
2241 account = self.wallet.accounts[k]
2244 d.setWindowTitle(_('Account Details'))
2247 vbox = QVBoxLayout(d)
2248 name = self.wallet.get_account_name(k)
2249 label = QLabel('Name: ' + name)
2250 vbox.addWidget(label)
2252 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2254 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2256 vbox.addWidget(QLabel(_('Master Public Key:')))
2259 text.setReadOnly(True)
2260 text.setMaximumHeight(170)
2261 vbox.addWidget(text)
2263 mpk_text = '\n'.join( account.get_master_pubkeys() )
2264 text.setText(mpk_text)
2266 vbox.addLayout(close_button(d))