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()
199 self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
200 self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
201 self.password_menu.setEnabled(not self.wallet.is_watching_only())
202 self.seed_menu.setEnabled(self.wallet.has_seed())
203 self.mpk_menu.setEnabled(self.wallet.is_deterministic())
205 self.update_lock_icon()
206 self.update_buttons_on_seed()
207 self.update_console()
209 run_hook('load_wallet', wallet)
212 def open_wallet(self):
213 wallet_folder = self.wallet.storage.path
214 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
218 storage = WalletStorage({'wallet_path': filename})
219 if not storage.file_exists:
220 self.show_message("file not found "+ filename)
223 self.wallet.stop_threads()
226 wallet = Wallet(storage)
227 wallet.start_threads(self.network)
229 self.load_wallet(wallet)
233 def backup_wallet(self):
235 path = self.wallet.storage.path
236 wallet_folder = os.path.dirname(path)
237 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
241 new_path = os.path.join(wallet_folder, filename)
244 shutil.copy2(path, new_path)
245 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
246 except (IOError, os.error), reason:
247 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
250 def new_wallet(self):
253 wallet_folder = os.path.dirname(self.wallet.storage.path)
254 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
257 filename = os.path.join(wallet_folder, filename)
259 storage = WalletStorage({'wallet_path': filename})
260 if storage.file_exists:
261 QMessageBox.critical(None, "Error", _("File exists"))
264 wizard = installwizard.InstallWizard(self.config, self.network, storage)
265 wallet = wizard.run('new')
267 self.load_wallet(wallet)
271 def init_menubar(self):
274 file_menu = menubar.addMenu(_("&File"))
275 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
276 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
277 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
278 file_menu.addAction(_("&Quit"), self.close)
280 wallet_menu = menubar.addMenu(_("&Wallet"))
281 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
282 self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
284 wallet_menu.addSeparator()
286 self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
287 self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
288 self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
290 wallet_menu.addSeparator()
291 labels_menu = wallet_menu.addMenu(_("&Labels"))
292 labels_menu.addAction(_("&Import"), self.do_import_labels)
293 labels_menu.addAction(_("&Export"), self.do_export_labels)
295 self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
296 self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
297 self.private_keys_menu.addAction(_("&Export"), self.do_export_privkeys)
299 wallet_menu.addAction(_("&Export History"), self.do_export_history)
301 tools_menu = menubar.addMenu(_("&Tools"))
303 # Settings / Preferences are all reserved keywords in OSX using this as work around
304 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
305 tools_menu.addAction(_("&Network"), self.run_network_dialog)
306 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
307 tools_menu.addSeparator()
308 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
309 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
310 tools_menu.addSeparator()
312 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
313 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
314 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
316 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
317 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
318 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
319 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
321 help_menu = menubar.addMenu(_("&Help"))
322 help_menu.addAction(_("&About"), self.show_about)
323 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
324 help_menu.addSeparator()
325 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
326 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
328 self.setMenuBar(menubar)
330 def show_about(self):
331 QMessageBox.about(self, "Electrum",
332 _("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."))
334 def show_report_bug(self):
335 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
336 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
339 def notify_transactions(self):
340 if not self.network or not self.network.is_connected():
343 print_error("Notifying GUI")
344 if len(self.network.pending_transactions_for_notifications) > 0:
345 # Combine the transactions if there are more then three
346 tx_amount = len(self.network.pending_transactions_for_notifications)
349 for tx in self.network.pending_transactions_for_notifications:
350 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
354 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
355 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
357 self.network.pending_transactions_for_notifications = []
359 for tx in self.network.pending_transactions_for_notifications:
361 self.network.pending_transactions_for_notifications.remove(tx)
362 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
364 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
366 def notify(self, message):
367 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
371 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
372 def getOpenFileName(self, title, filter = ""):
373 directory = self.config.get('io_dir', os.path.expanduser('~'))
374 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
375 if fileName and directory != os.path.dirname(fileName):
376 self.config.set_key('io_dir', os.path.dirname(fileName), True)
379 def getSaveFileName(self, title, filename, filter = ""):
380 directory = self.config.get('io_dir', os.path.expanduser('~'))
381 path = os.path.join( directory, filename )
382 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
383 if fileName and directory != os.path.dirname(fileName):
384 self.config.set_key('io_dir', os.path.dirname(fileName), True)
388 QMainWindow.close(self)
389 run_hook('close_main_window')
391 def connect_slots(self, sender):
392 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
393 self.previous_payto_e=''
395 def timer_actions(self):
396 if self.need_update.is_set():
398 self.need_update.clear()
399 run_hook('timer_actions')
401 def format_amount(self, x, is_diff=False, whitespaces=False):
402 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
404 def read_amount(self, x):
405 if x in['.', '']: return None
406 p = pow(10, self.decimal_point)
407 return int( p * Decimal(x) )
410 assert self.decimal_point in [5,8]
411 return "BTC" if self.decimal_point == 8 else "mBTC"
414 def update_status(self):
415 if self.network is None or not self.network.is_running():
417 icon = QIcon(":icons/status_disconnected.png")
419 elif self.network.is_connected():
420 if not self.wallet.up_to_date:
421 text = _("Synchronizing...")
422 icon = QIcon(":icons/status_waiting.png")
423 elif self.network.server_lag > 1:
424 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
425 icon = QIcon(":icons/status_lagging.png")
427 c, u = self.wallet.get_account_balance(self.current_account)
428 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
429 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
431 # append fiat balance and price from exchange rate plugin
433 run_hook('get_fiat_status_text', c+u, r)
438 self.tray.setToolTip(text)
439 icon = QIcon(":icons/status_connected.png")
441 text = _("Not connected")
442 icon = QIcon(":icons/status_disconnected.png")
444 self.balance_label.setText(text)
445 self.status_button.setIcon( icon )
448 def update_wallet(self):
450 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
451 self.update_history_tab()
452 self.update_receive_tab()
453 self.update_contacts_tab()
454 self.update_completions()
457 def create_history_tab(self):
458 self.history_list = l = MyTreeWidget(self)
460 for i,width in enumerate(self.column_widths['history']):
461 l.setColumnWidth(i, width)
462 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
463 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
464 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
466 l.customContextMenuRequested.connect(self.create_history_menu)
470 def create_history_menu(self, position):
471 self.history_list.selectedIndexes()
472 item = self.history_list.currentItem()
473 be = self.config.get('block_explorer', 'Blockchain.info')
474 if be == 'Blockchain.info':
475 block_explorer = 'https://blockchain.info/tx/'
476 elif be == 'Blockr.io':
477 block_explorer = 'https://blockr.io/tx/info/'
478 elif be == 'Insight.is':
479 block_explorer = 'http://live.insight.is/tx/'
481 tx_hash = str(item.data(0, Qt.UserRole).toString())
482 if not tx_hash: return
484 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
485 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
486 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
487 menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
488 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
491 def show_transaction(self, tx):
492 import transaction_dialog
493 d = transaction_dialog.TxDialog(tx, self)
496 def tx_label_clicked(self, item, column):
497 if column==2 and item.isSelected():
499 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
500 self.history_list.editItem( item, column )
501 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
504 def tx_label_changed(self, item, column):
508 tx_hash = str(item.data(0, Qt.UserRole).toString())
509 tx = self.wallet.transactions.get(tx_hash)
510 text = unicode( item.text(2) )
511 self.wallet.set_label(tx_hash, text)
513 item.setForeground(2, QBrush(QColor('black')))
515 text = self.wallet.get_default_label(tx_hash)
516 item.setText(2, text)
517 item.setForeground(2, QBrush(QColor('gray')))
521 def edit_label(self, is_recv):
522 l = self.receive_list if is_recv else self.contacts_list
523 item = l.currentItem()
524 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
525 l.editItem( item, 1 )
526 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
530 def address_label_clicked(self, item, column, l, column_addr, column_label):
531 if column == column_label and item.isSelected():
532 is_editable = item.data(0, 32).toBool()
535 addr = unicode( item.text(column_addr) )
536 label = unicode( item.text(column_label) )
537 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
538 l.editItem( item, column )
539 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
542 def address_label_changed(self, item, column, l, column_addr, column_label):
543 if column == column_label:
544 addr = unicode( item.text(column_addr) )
545 text = unicode( item.text(column_label) )
546 is_editable = item.data(0, 32).toBool()
550 changed = self.wallet.set_label(addr, text)
552 self.update_history_tab()
553 self.update_completions()
555 self.current_item_changed(item)
557 run_hook('item_changed', item, column)
560 def current_item_changed(self, a):
561 run_hook('current_item_changed', a)
565 def update_history_tab(self):
567 self.history_list.clear()
568 for item in self.wallet.get_tx_history(self.current_account):
569 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
570 time_str = _("unknown")
573 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
575 time_str = _("error")
578 time_str = 'unverified'
579 icon = QIcon(":icons/unconfirmed.png")
582 icon = QIcon(":icons/unconfirmed.png")
584 icon = QIcon(":icons/clock%d.png"%conf)
586 icon = QIcon(":icons/confirmed.png")
588 if value is not None:
589 v_str = self.format_amount(value, True, whitespaces=True)
593 balance_str = self.format_amount(balance, whitespaces=True)
596 label, is_default_label = self.wallet.get_label(tx_hash)
598 label = _('Pruned transaction outputs')
599 is_default_label = False
601 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
602 item.setFont(2, QFont(MONOSPACE_FONT))
603 item.setFont(3, QFont(MONOSPACE_FONT))
604 item.setFont(4, QFont(MONOSPACE_FONT))
606 item.setForeground(3, QBrush(QColor("#BC1E1E")))
608 item.setData(0, Qt.UserRole, tx_hash)
609 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
611 item.setForeground(2, QBrush(QColor('grey')))
613 item.setIcon(0, icon)
614 self.history_list.insertTopLevelItem(0,item)
617 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
618 run_hook('history_tab_update')
621 def create_send_tab(self):
626 grid.setColumnMinimumWidth(3,300)
627 grid.setColumnStretch(5,1)
630 self.payto_e = QLineEdit()
631 grid.addWidget(QLabel(_('Pay to')), 1, 0)
632 grid.addWidget(self.payto_e, 1, 1, 1, 3)
634 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)
636 completer = QCompleter()
637 completer.setCaseSensitivity(False)
638 self.payto_e.setCompleter(completer)
639 completer.setModel(self.completions)
641 self.message_e = QLineEdit()
642 grid.addWidget(QLabel(_('Description')), 2, 0)
643 grid.addWidget(self.message_e, 2, 1, 1, 3)
644 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)
646 self.from_label = QLabel(_('From'))
647 grid.addWidget(self.from_label, 3, 0)
648 self.from_list = QTreeWidget(self)
649 self.from_list.setColumnCount(2)
650 self.from_list.setColumnWidth(0, 350)
651 self.from_list.setColumnWidth(1, 50)
652 self.from_list.setHeaderHidden (True)
653 self.from_list.setMaximumHeight(80)
654 grid.addWidget(self.from_list, 3, 1, 1, 3)
655 self.set_pay_from([])
657 self.amount_e = AmountEdit(self.base_unit)
658 grid.addWidget(QLabel(_('Amount')), 4, 0)
659 grid.addWidget(self.amount_e, 4, 1, 1, 2)
660 grid.addWidget(HelpButton(
661 _('Amount to be sent.') + '\n\n' \
662 + _('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.') \
663 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
665 self.fee_e = AmountEdit(self.base_unit)
666 grid.addWidget(QLabel(_('Fee')), 5, 0)
667 grid.addWidget(self.fee_e, 5, 1, 1, 2)
668 grid.addWidget(HelpButton(
669 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
670 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
671 + _('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)
673 run_hook('exchange_rate_button', grid)
675 self.send_button = EnterButton(_("Send"), self.do_send)
676 grid.addWidget(self.send_button, 6, 1)
678 b = EnterButton(_("Clear"),self.do_clear)
679 grid.addWidget(b, 6, 2)
681 self.payto_sig = QLabel('')
682 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
684 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
685 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
694 def entry_changed( is_fee ):
695 self.funds_error = False
697 if self.amount_e.is_shortcut:
698 self.amount_e.is_shortcut = False
699 sendable = self.get_sendable_balance()
700 # there is only one output because we are completely spending inputs
701 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
702 fee = self.wallet.estimated_fee(inputs, 1)
704 self.amount_e.setText( self.format_amount(amount) )
705 self.fee_e.setText( self.format_amount( fee ) )
708 amount = self.read_amount(str(self.amount_e.text()))
709 fee = self.read_amount(str(self.fee_e.text()))
711 if not is_fee: fee = None
714 # assume that there will be 2 outputs (one for change)
715 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
717 self.fee_e.setText( self.format_amount( fee ) )
720 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
724 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
725 self.funds_error = True
726 text = _( "Not enough funds" )
727 c, u = self.wallet.get_frozen_balance()
728 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
730 self.statusBar().showMessage(text)
731 self.amount_e.setPalette(palette)
732 self.fee_e.setPalette(palette)
734 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
735 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
737 run_hook('create_send_tab', grid)
741 def set_pay_from(self, l):
743 self.from_list.clear()
744 self.from_label.setHidden(len(self.pay_from) == 0)
745 self.from_list.setHidden(len(self.pay_from) == 0)
746 for addr in self.pay_from:
747 c, u = self.wallet.get_addr_balance(addr)
748 balance = self.format_amount(c + u)
749 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
752 def update_completions(self):
754 for addr,label in self.wallet.labels.items():
755 if addr in self.wallet.addressbook:
756 l.append( label + ' <' + addr + '>')
758 run_hook('update_completions', l)
759 self.completions.setStringList(l)
763 return lambda s, *args: s.do_protect(func, args)
768 label = unicode( self.message_e.text() )
769 r = unicode( self.payto_e.text() )
772 # label or alias, with address in brackets
773 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
774 to_address = m.group(2) if m else r
776 if not is_valid(to_address):
777 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
781 amount = self.read_amount(unicode( self.amount_e.text()))
783 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
786 fee = self.read_amount(unicode( self.fee_e.text()))
788 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
791 confirm_amount = self.config.get('confirm_amount', 100000000)
792 if amount >= confirm_amount:
793 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
796 confirm_fee = self.config.get('confirm_fee', 100000)
797 if fee >= confirm_fee:
798 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()}):
801 self.send_tx(to_address, amount, fee, label)
804 def waiting_dialog(self, message):
806 d.setWindowTitle('Please wait')
808 vbox = QVBoxLayout(d)
815 def send_tx(self, to_address, amount, fee, label, password):
817 # first, create an unsigned tx
818 domain = self.get_payment_sources()
819 outputs = [(to_address, amount)]
821 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
823 except Exception as e:
824 traceback.print_exc(file=sys.stdout)
825 self.show_message(str(e))
828 # call hook to see if plugin needs gui interaction
829 run_hook('send_tx', tx)
835 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
836 self.wallet.sign_transaction(tx, keypairs, password)
837 self.signed_tx_data = (tx, fee, label)
838 self.emit(SIGNAL('send_tx2'))
839 self.tx_wait_dialog = self.waiting_dialog('Signing..')
840 threading.Thread(target=sign_thread).start()
842 # add recipient to addressbook
843 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
844 self.wallet.addressbook.append(to_address)
848 tx, fee, label = self.signed_tx_data
849 self.tx_wait_dialog.accept()
852 self.show_message(tx.error)
855 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
856 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
860 self.wallet.set_label(tx.hash(), label)
862 if not tx.is_complete() or self.config.get('show_before_broadcast'):
863 self.show_transaction(tx)
867 def broadcast_thread():
868 self.tx_broadcast_result = self.wallet.sendtx(tx)
869 self.emit(SIGNAL('send_tx3'))
870 self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
871 threading.Thread(target=broadcast_thread).start()
875 self.tx_broadcast_dialog.accept()
876 status, msg = self.tx_broadcast_result
878 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
880 self.update_contacts_tab()
882 QMessageBox.warning(self, _('Error'), msg, _('OK'))
890 def set_send(self, address, amount, label, message):
892 if label and self.wallet.labels.get(address) != label:
893 if self.question('Give label "%s" to address %s ?'%(label,address)):
894 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
895 self.wallet.addressbook.append(address)
896 self.wallet.set_label(address, label)
898 self.tabs.setCurrentIndex(1)
899 label = self.wallet.labels.get(address)
900 m_addr = label + ' <'+ address +'>' if label else address
901 self.payto_e.setText(m_addr)
903 self.message_e.setText(message)
905 self.amount_e.setText(amount)
909 self.payto_sig.setVisible(False)
910 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
912 self.set_frozen(e,False)
914 self.set_pay_from([])
917 def set_frozen(self,entry,frozen):
919 entry.setReadOnly(True)
920 entry.setFrame(False)
922 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
923 entry.setPalette(palette)
925 entry.setReadOnly(False)
928 palette.setColor(entry.backgroundRole(), QColor('white'))
929 entry.setPalette(palette)
932 def set_addrs_frozen(self,addrs,freeze):
934 if not addr: continue
935 if addr in self.wallet.frozen_addresses and not freeze:
936 self.wallet.unfreeze(addr)
937 elif addr not in self.wallet.frozen_addresses and freeze:
938 self.wallet.freeze(addr)
939 self.update_receive_tab()
943 def create_list_tab(self, headers):
944 "generic tab creation method"
945 l = MyTreeWidget(self)
946 l.setColumnCount( len(headers) )
947 l.setHeaderLabels( headers )
957 vbox.addWidget(buttons)
962 buttons.setLayout(hbox)
967 def create_receive_tab(self):
968 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
969 l.setContextMenuPolicy(Qt.CustomContextMenu)
970 l.customContextMenuRequested.connect(self.create_receive_menu)
971 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
972 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
973 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
974 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
975 self.receive_list = l
976 self.receive_buttons_hbox = hbox
983 def save_column_widths(self):
984 self.column_widths["receive"] = []
985 for i in range(self.receive_list.columnCount() -1):
986 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
988 self.column_widths["history"] = []
989 for i in range(self.history_list.columnCount() - 1):
990 self.column_widths["history"].append(self.history_list.columnWidth(i))
992 self.column_widths["contacts"] = []
993 for i in range(self.contacts_list.columnCount() - 1):
994 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
996 self.config.set_key("column_widths_2", self.column_widths, True)
999 def create_contacts_tab(self):
1000 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1001 l.setContextMenuPolicy(Qt.CustomContextMenu)
1002 l.customContextMenuRequested.connect(self.create_contact_menu)
1003 for i,width in enumerate(self.column_widths['contacts']):
1004 l.setColumnWidth(i, width)
1006 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1007 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1008 self.contacts_list = l
1009 self.contacts_buttons_hbox = hbox
1014 def delete_imported_key(self, addr):
1015 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1016 self.wallet.delete_imported_key(addr)
1017 self.update_receive_tab()
1018 self.update_history_tab()
1020 def edit_account_label(self, k):
1021 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1023 label = unicode(text)
1024 self.wallet.set_label(k,label)
1025 self.update_receive_tab()
1027 def account_set_expanded(self, item, k, b):
1029 self.accounts_expanded[k] = b
1031 def create_account_menu(self, position, k, item):
1033 if item.isExpanded():
1034 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1036 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1037 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1038 if self.wallet.seed_version > 4:
1039 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1040 if self.wallet.account_is_pending(k):
1041 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1042 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1044 def delete_pending_account(self, k):
1045 self.wallet.delete_pending_account(k)
1046 self.update_receive_tab()
1048 def create_receive_menu(self, position):
1049 # fixme: this function apparently has a side effect.
1050 # if it is not called the menu pops up several times
1051 #self.receive_list.selectedIndexes()
1053 selected = self.receive_list.selectedItems()
1054 multi_select = len(selected) > 1
1055 addrs = [unicode(item.text(0)) for item in selected]
1056 if not multi_select:
1057 item = self.receive_list.itemAt(position)
1061 if not is_valid(addr):
1062 k = str(item.data(0,32).toString())
1064 self.create_account_menu(position, k, item)
1066 item.setExpanded(not item.isExpanded())
1070 if not multi_select:
1071 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1072 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1073 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1074 menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1075 if not self.wallet.is_watching_only():
1076 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1077 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1078 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1079 if addr in self.wallet.imported_keys:
1080 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1082 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1083 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1084 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1085 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1087 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1088 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1090 run_hook('receive_menu', menu, addrs)
1091 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1094 def get_sendable_balance(self):
1095 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1098 def get_payment_sources(self):
1100 return self.pay_from
1102 return self.wallet.get_account_addresses(self.current_account)
1105 def send_from_addresses(self, addrs):
1106 self.set_pay_from( addrs )
1107 self.tabs.setCurrentIndex(1)
1110 def payto(self, addr):
1112 label = self.wallet.labels.get(addr)
1113 m_addr = label + ' <' + addr + '>' if label else addr
1114 self.tabs.setCurrentIndex(1)
1115 self.payto_e.setText(m_addr)
1116 self.amount_e.setFocus()
1119 def delete_contact(self, x):
1120 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1121 self.wallet.delete_contact(x)
1122 self.wallet.set_label(x, None)
1123 self.update_history_tab()
1124 self.update_contacts_tab()
1125 self.update_completions()
1128 def create_contact_menu(self, position):
1129 item = self.contacts_list.itemAt(position)
1132 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1134 addr = unicode(item.text(0))
1135 label = unicode(item.text(1))
1136 is_editable = item.data(0,32).toBool()
1137 payto_addr = item.data(0,33).toString()
1138 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1139 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1140 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1142 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1143 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1145 run_hook('create_contact_menu', menu, item)
1146 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1149 def update_receive_item(self, item):
1150 item.setFont(0, QFont(MONOSPACE_FONT))
1151 address = str(item.data(0,0).toString())
1152 label = self.wallet.labels.get(address,'')
1153 item.setData(1,0,label)
1154 item.setData(0,32, True) # is editable
1156 run_hook('update_receive_item', address, item)
1158 if not self.wallet.is_mine(address): return
1160 c, u = self.wallet.get_addr_balance(address)
1161 balance = self.format_amount(c + u)
1162 item.setData(2,0,balance)
1164 if address in self.wallet.frozen_addresses:
1165 item.setBackgroundColor(0, QColor('lightblue'))
1168 def update_receive_tab(self):
1169 l = self.receive_list
1172 l.setColumnHidden(2, False)
1173 l.setColumnHidden(3, False)
1174 for i,width in enumerate(self.column_widths['receive']):
1175 l.setColumnWidth(i, width)
1177 if self.current_account is None:
1178 account_items = sorted(self.wallet.accounts.items())
1179 elif self.current_account != -1:
1180 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1184 pending_accounts = self.wallet.get_pending_accounts()
1186 for k, account in account_items:
1188 if len(account_items) + len(pending_accounts) > 1:
1189 name = self.wallet.get_account_name(k)
1190 c,u = self.wallet.get_account_balance(k)
1191 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1192 l.addTopLevelItem(account_item)
1193 account_item.setExpanded(self.accounts_expanded.get(k, True))
1194 account_item.setData(0, 32, k)
1198 for is_change in ([0,1]):
1199 name = _("Receiving") if not is_change else _("Change")
1200 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1202 account_item.addChild(seq_item)
1204 l.addTopLevelItem(seq_item)
1206 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1208 if not is_change: seq_item.setExpanded(True)
1213 for address in account.get_addresses(is_change):
1214 h = self.wallet.history.get(address,[])
1218 if gap > self.wallet.gap_limit:
1223 c, u = self.wallet.get_addr_balance(address)
1224 num_tx = '*' if h == ['*'] else "%d"%len(h)
1225 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1226 self.update_receive_item(item)
1228 item.setBackgroundColor(1, QColor('red'))
1229 if len(h) > 0 and c == -u:
1231 seq_item.insertChild(0,used_item)
1233 used_item.addChild(item)
1235 seq_item.addChild(item)
1238 for k, addr in pending_accounts:
1239 name = self.wallet.labels.get(k,'')
1240 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1241 self.update_receive_item(item)
1242 l.addTopLevelItem(account_item)
1243 account_item.setExpanded(True)
1244 account_item.setData(0, 32, k)
1245 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1246 account_item.addChild(item)
1247 self.update_receive_item(item)
1250 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1251 c,u = self.wallet.get_imported_balance()
1252 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1253 l.addTopLevelItem(account_item)
1254 account_item.setExpanded(True)
1255 for address in self.wallet.imported_keys.keys():
1256 item = QTreeWidgetItem( [ address, '', '', ''] )
1257 self.update_receive_item(item)
1258 account_item.addChild(item)
1261 # we use column 1 because column 0 may be hidden
1262 l.setCurrentItem(l.topLevelItem(0),1)
1265 def update_contacts_tab(self):
1266 l = self.contacts_list
1269 for address in self.wallet.addressbook:
1270 label = self.wallet.labels.get(address,'')
1271 n = self.wallet.get_num_tx(address)
1272 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1273 item.setFont(0, QFont(MONOSPACE_FONT))
1274 # 32 = label can be edited (bool)
1275 item.setData(0,32, True)
1277 item.setData(0,33, address)
1278 l.addTopLevelItem(item)
1280 run_hook('update_contacts_tab', l)
1281 l.setCurrentItem(l.topLevelItem(0))
1285 def create_console_tab(self):
1286 from console import Console
1287 self.console = console = Console()
1291 def update_console(self):
1292 console = self.console
1293 console.history = self.config.get("console-history",[])
1294 console.history_index = len(console.history)
1296 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1297 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1299 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1301 def mkfunc(f, method):
1302 return lambda *args: apply( f, (method, args, self.password_dialog ))
1304 if m[0]=='_' or m in ['network','wallet']: continue
1305 methods[m] = mkfunc(c._run, m)
1307 console.updateNamespace(methods)
1310 def change_account(self,s):
1311 if s == _("All accounts"):
1312 self.current_account = None
1314 accounts = self.wallet.get_account_names()
1315 for k, v in accounts.items():
1317 self.current_account = k
1318 self.update_history_tab()
1319 self.update_status()
1320 self.update_receive_tab()
1322 def create_status_bar(self):
1325 sb.setFixedHeight(35)
1326 qtVersion = qVersion()
1328 self.balance_label = QLabel("")
1329 sb.addWidget(self.balance_label)
1331 from version_getter import UpdateLabel
1332 self.updatelabel = UpdateLabel(self.config, sb)
1334 self.account_selector = QComboBox()
1335 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1336 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1337 sb.addPermanentWidget(self.account_selector)
1339 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1340 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1342 self.lock_icon = QIcon()
1343 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1344 sb.addPermanentWidget( self.password_button )
1346 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1347 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1348 sb.addPermanentWidget( self.seed_button )
1349 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1350 sb.addPermanentWidget( self.status_button )
1352 run_hook('create_status_bar', (sb,))
1354 self.setStatusBar(sb)
1357 def update_lock_icon(self):
1358 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1359 self.password_button.setIcon( icon )
1362 def update_buttons_on_seed(self):
1363 if self.wallet.has_seed():
1364 self.seed_button.show()
1366 self.seed_button.hide()
1368 if not self.wallet.is_watching_only():
1369 self.password_button.show()
1370 self.send_button.setText(_("Send"))
1372 self.password_button.hide()
1373 self.send_button.setText(_("Create unsigned transaction"))
1376 def change_password_dialog(self):
1377 from password_dialog import PasswordDialog
1378 d = PasswordDialog(self.wallet, self)
1380 self.update_lock_icon()
1383 def new_contact_dialog(self):
1386 d.setWindowTitle(_("New Contact"))
1387 vbox = QVBoxLayout(d)
1388 vbox.addWidget(QLabel(_('New Contact')+':'))
1390 grid = QGridLayout()
1393 grid.addWidget(QLabel(_("Address")), 1, 0)
1394 grid.addWidget(line1, 1, 1)
1395 grid.addWidget(QLabel(_("Name")), 2, 0)
1396 grid.addWidget(line2, 2, 1)
1398 vbox.addLayout(grid)
1399 vbox.addLayout(ok_cancel_buttons(d))
1404 address = str(line1.text())
1405 label = unicode(line2.text())
1407 if not is_valid(address):
1408 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1411 self.wallet.add_contact(address)
1413 self.wallet.set_label(address, label)
1415 self.update_contacts_tab()
1416 self.update_history_tab()
1417 self.update_completions()
1418 self.tabs.setCurrentIndex(3)
1422 def new_account_dialog(self, password):
1424 dialog = QDialog(self)
1426 dialog.setWindowTitle(_("New Account"))
1428 vbox = QVBoxLayout()
1429 vbox.addWidget(QLabel(_('Account name')+':'))
1432 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1433 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1438 vbox.addLayout(ok_cancel_buttons(dialog))
1439 dialog.setLayout(vbox)
1443 name = str(e.text())
1446 self.wallet.create_pending_account(name, password)
1447 self.update_receive_tab()
1448 self.tabs.setCurrentIndex(2)
1453 def show_master_public_keys(self):
1455 dialog = QDialog(self)
1457 dialog.setWindowTitle(_("Master Public Keys"))
1459 main_layout = QGridLayout()
1460 mpk_dict = self.wallet.get_master_public_keys()
1462 for key, value in mpk_dict.items():
1463 main_layout.addWidget(QLabel(key), i, 0)
1464 mpk_text = QTextEdit()
1465 mpk_text.setReadOnly(True)
1466 mpk_text.setMaximumHeight(170)
1467 mpk_text.setText(value)
1468 main_layout.addWidget(mpk_text, i + 1, 0)
1471 vbox = QVBoxLayout()
1472 vbox.addLayout(main_layout)
1473 vbox.addLayout(close_button(dialog))
1475 dialog.setLayout(vbox)
1480 def show_seed_dialog(self, password):
1481 if not self.wallet.has_seed():
1482 QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1486 mnemonic = self.wallet.get_mnemonic(password)
1488 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1490 from seed_dialog import SeedDialog
1491 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1496 def show_qrcode(self, data, title = _("QR code")):
1500 d.setWindowTitle(title)
1501 d.setMinimumSize(270, 300)
1502 vbox = QVBoxLayout()
1503 qrw = QRCodeWidget(data)
1504 vbox.addWidget(qrw, 1)
1505 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1506 hbox = QHBoxLayout()
1509 filename = os.path.join(self.config.path, "qrcode.bmp")
1512 bmp.save_qrcode(qrw.qr, filename)
1513 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1515 def copy_to_clipboard():
1516 bmp.save_qrcode(qrw.qr, filename)
1517 self.app.clipboard().setImage(QImage(filename))
1518 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1520 b = QPushButton(_("Copy"))
1522 b.clicked.connect(copy_to_clipboard)
1524 b = QPushButton(_("Save"))
1526 b.clicked.connect(print_qr)
1528 b = QPushButton(_("Close"))
1530 b.clicked.connect(d.accept)
1533 vbox.addLayout(hbox)
1538 def do_protect(self, func, args):
1539 if self.wallet.use_encryption:
1540 password = self.password_dialog()
1546 if args != (False,):
1547 args = (self,) + args + (password,)
1549 args = (self,password)
1553 def show_public_keys(self, address):
1554 if not address: return
1556 pubkey_list = self.wallet.get_public_keys(address)
1557 except Exception as e:
1558 traceback.print_exc(file=sys.stdout)
1559 self.show_message(str(e))
1563 d.setMinimumSize(600, 200)
1565 vbox = QVBoxLayout()
1566 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1567 vbox.addWidget( QLabel(_("Public key") + ':'))
1569 keys.setReadOnly(True)
1570 keys.setText('\n'.join(pubkey_list))
1571 vbox.addWidget(keys)
1572 #vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1573 vbox.addLayout(close_button(d))
1578 def show_private_key(self, address, password):
1579 if not address: return
1581 pk_list = self.wallet.get_private_key(address, password)
1582 except Exception as e:
1583 traceback.print_exc(file=sys.stdout)
1584 self.show_message(str(e))
1588 d.setMinimumSize(600, 200)
1590 vbox = QVBoxLayout()
1591 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1592 vbox.addWidget( QLabel(_("Private key") + ':'))
1594 keys.setReadOnly(True)
1595 keys.setText('\n'.join(pk_list))
1596 vbox.addWidget(keys)
1597 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1598 vbox.addLayout(close_button(d))
1604 def do_sign(self, address, message, signature, password):
1605 message = unicode(message.toPlainText())
1606 message = message.encode('utf-8')
1608 sig = self.wallet.sign_message(str(address.text()), message, password)
1609 signature.setText(sig)
1610 except Exception as e:
1611 self.show_message(str(e))
1613 def do_verify(self, address, message, signature):
1614 message = unicode(message.toPlainText())
1615 message = message.encode('utf-8')
1616 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1617 self.show_message(_("Signature verified"))
1619 self.show_message(_("Error: wrong signature"))
1622 def sign_verify_message(self, address=''):
1625 d.setWindowTitle(_('Sign/verify Message'))
1626 d.setMinimumSize(410, 290)
1628 layout = QGridLayout(d)
1630 message_e = QTextEdit()
1631 layout.addWidget(QLabel(_('Message')), 1, 0)
1632 layout.addWidget(message_e, 1, 1)
1633 layout.setRowStretch(2,3)
1635 address_e = QLineEdit()
1636 address_e.setText(address)
1637 layout.addWidget(QLabel(_('Address')), 2, 0)
1638 layout.addWidget(address_e, 2, 1)
1640 signature_e = QTextEdit()
1641 layout.addWidget(QLabel(_('Signature')), 3, 0)
1642 layout.addWidget(signature_e, 3, 1)
1643 layout.setRowStretch(3,1)
1645 hbox = QHBoxLayout()
1647 b = QPushButton(_("Sign"))
1648 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1651 b = QPushButton(_("Verify"))
1652 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1655 b = QPushButton(_("Close"))
1656 b.clicked.connect(d.accept)
1658 layout.addLayout(hbox, 4, 1)
1663 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1665 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1666 message_e.setText(decrypted)
1667 except Exception as e:
1668 self.show_message(str(e))
1671 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1672 message = unicode(message_e.toPlainText())
1673 message = message.encode('utf-8')
1675 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1676 encrypted_e.setText(encrypted)
1677 except Exception as e:
1678 self.show_message(str(e))
1682 def encrypt_message(self, address = ''):
1685 d.setWindowTitle(_('Encrypt/decrypt Message'))
1686 d.setMinimumSize(610, 490)
1688 layout = QGridLayout(d)
1690 message_e = QTextEdit()
1691 layout.addWidget(QLabel(_('Message')), 1, 0)
1692 layout.addWidget(message_e, 1, 1)
1693 layout.setRowStretch(2,3)
1695 pubkey_e = QLineEdit()
1697 pubkey = self.wallet.getpubkeys(address)[0]
1698 pubkey_e.setText(pubkey)
1699 layout.addWidget(QLabel(_('Public key')), 2, 0)
1700 layout.addWidget(pubkey_e, 2, 1)
1702 encrypted_e = QTextEdit()
1703 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1704 layout.addWidget(encrypted_e, 3, 1)
1705 layout.setRowStretch(3,1)
1707 hbox = QHBoxLayout()
1708 b = QPushButton(_("Encrypt"))
1709 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1712 b = QPushButton(_("Decrypt"))
1713 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1716 b = QPushButton(_("Close"))
1717 b.clicked.connect(d.accept)
1720 layout.addLayout(hbox, 4, 1)
1724 def question(self, msg):
1725 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1727 def show_message(self, msg):
1728 QMessageBox.information(self, _('Message'), msg, _('OK'))
1730 def password_dialog(self ):
1733 d.setWindowTitle(_("Enter Password"))
1738 vbox = QVBoxLayout()
1739 msg = _('Please enter your password')
1740 vbox.addWidget(QLabel(msg))
1742 grid = QGridLayout()
1744 grid.addWidget(QLabel(_('Password')), 1, 0)
1745 grid.addWidget(pw, 1, 1)
1746 vbox.addLayout(grid)
1748 vbox.addLayout(ok_cancel_buttons(d))
1751 run_hook('password_dialog', pw, grid, 1)
1752 if not d.exec_(): return
1753 return unicode(pw.text())
1762 def tx_from_text(self, txt):
1763 "json or raw hexadecimal"
1766 tx = Transaction(txt)
1772 tx_dict = json.loads(str(txt))
1773 assert "hex" in tx_dict.keys()
1774 assert "complete" in tx_dict.keys()
1775 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1776 if not tx_dict["complete"]:
1777 assert "input_info" in tx_dict.keys()
1778 input_info = json.loads(tx_dict['input_info'])
1779 tx.add_input_info(input_info)
1784 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1788 def read_tx_from_file(self):
1789 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1793 with open(fileName, "r") as f:
1794 file_content = f.read()
1795 except (ValueError, IOError, os.error), reason:
1796 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1798 return self.tx_from_text(file_content)
1802 def sign_raw_transaction(self, tx, input_info, password):
1803 self.wallet.signrawtransaction(tx, input_info, [], password)
1805 def do_process_from_text(self):
1806 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1809 tx = self.tx_from_text(text)
1811 self.show_transaction(tx)
1813 def do_process_from_file(self):
1814 tx = self.read_tx_from_file()
1816 self.show_transaction(tx)
1818 def do_process_from_txid(self):
1819 from electrum import transaction
1820 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1822 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1824 tx = transaction.Transaction(r)
1826 self.show_transaction(tx)
1828 self.show_message("unknown transaction")
1830 def do_process_from_csvReader(self, csvReader):
1835 for position, row in enumerate(csvReader):
1837 if not is_valid(address):
1838 errors.append((position, address))
1840 amount = Decimal(row[1])
1841 amount = int(100000000*amount)
1842 outputs.append((address, amount))
1843 except (ValueError, IOError, os.error), reason:
1844 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1848 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1849 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1853 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1854 except Exception as e:
1855 self.show_message(str(e))
1858 self.show_transaction(tx)
1860 def do_process_from_csv_file(self):
1861 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1865 with open(fileName, "r") as f:
1866 csvReader = csv.reader(f)
1867 self.do_process_from_csvReader(csvReader)
1868 except (ValueError, IOError, os.error), reason:
1869 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1872 def do_process_from_csv_text(self):
1873 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1874 + _("Format: address, amount. One output per line"), _("Load CSV"))
1877 f = StringIO.StringIO(text)
1878 csvReader = csv.reader(f)
1879 self.do_process_from_csvReader(csvReader)
1884 def do_export_privkeys(self, password):
1885 if not self.wallet.seed:
1886 self.show_message(_("This wallet has no seed"))
1889 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.")))
1892 select_export = _('Select file to export your private keys to')
1893 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1895 with open(fileName, "w+") as csvfile:
1896 transaction = csv.writer(csvfile)
1897 transaction.writerow(["address", "private_key"])
1899 addresses = self.wallet.addresses(True)
1901 for addr in addresses:
1902 pk = "".join(self.wallet.get_private_key(addr, password))
1903 transaction.writerow(["%34s"%addr,pk])
1905 self.show_message(_("Private keys exported."))
1907 except (IOError, os.error), reason:
1908 export_error_label = _("Electrum was unable to produce a private key-export.")
1909 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1911 except Exception as e:
1912 self.show_message(str(e))
1916 def do_import_labels(self):
1917 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1918 if not labelsFile: return
1920 f = open(labelsFile, 'r')
1923 for key, value in json.loads(data).items():
1924 self.wallet.set_label(key, value)
1925 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1926 except (IOError, os.error), reason:
1927 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1930 def do_export_labels(self):
1931 labels = self.wallet.labels
1933 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1935 with open(fileName, 'w+') as f:
1936 json.dump(labels, f)
1937 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1938 except (IOError, os.error), reason:
1939 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1942 def do_export_history(self):
1943 wallet = self.wallet
1944 select_export = _('Select file to export your wallet transactions to')
1945 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
1950 with open(fileName, "w+") as csvfile:
1951 transaction = csv.writer(csvfile)
1952 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
1953 for item in wallet.get_tx_history():
1954 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
1956 if timestamp is not None:
1958 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
1959 except [RuntimeError, TypeError, NameError] as reason:
1960 time_string = "unknown"
1963 time_string = "unknown"
1965 time_string = "pending"
1967 if value is not None:
1968 value_string = format_satoshis(value, True)
1973 fee_string = format_satoshis(fee, True)
1978 label, is_default_label = wallet.get_label(tx_hash)
1979 label = label.encode('utf-8')
1983 balance_string = format_satoshis(balance, False)
1984 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
1985 QMessageBox.information(None,_("CSV Export created"), _("Your CSV export has been successfully created."))
1987 except (IOError, os.error), reason:
1988 export_error_label = _("Electrum was unable to produce a transaction export.")
1989 QMessageBox.critical(None,_("Unable to create csv"), export_error_label + "\n" + str(reason))
1994 def do_import_privkey(self, password):
1995 if not self.wallet.imported_keys:
1996 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1997 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1998 + _('Are you sure you understand what you are doing?'), 3, 4)
2001 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2004 text = str(text).split()
2009 addr = self.wallet.import_key(key, password)
2010 except Exception as e:
2016 addrlist.append(addr)
2018 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2020 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2021 self.update_receive_tab()
2022 self.update_history_tab()
2025 def settings_dialog(self):
2027 d.setWindowTitle(_('Electrum Settings'))
2029 vbox = QVBoxLayout()
2030 grid = QGridLayout()
2031 grid.setColumnStretch(0,1)
2033 nz_label = QLabel(_('Display zeros') + ':')
2034 grid.addWidget(nz_label, 0, 0)
2035 nz_e = AmountEdit(None,True)
2036 nz_e.setText("%d"% self.num_zeros)
2037 grid.addWidget(nz_e, 0, 1)
2038 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2039 grid.addWidget(HelpButton(msg), 0, 2)
2040 if not self.config.is_modifiable('num_zeros'):
2041 for w in [nz_e, nz_label]: w.setEnabled(False)
2043 lang_label=QLabel(_('Language') + ':')
2044 grid.addWidget(lang_label, 1, 0)
2045 lang_combo = QComboBox()
2046 from electrum.i18n import languages
2047 lang_combo.addItems(languages.values())
2049 index = languages.keys().index(self.config.get("language",''))
2052 lang_combo.setCurrentIndex(index)
2053 grid.addWidget(lang_combo, 1, 1)
2054 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2055 if not self.config.is_modifiable('language'):
2056 for w in [lang_combo, lang_label]: w.setEnabled(False)
2059 fee_label = QLabel(_('Transaction fee') + ':')
2060 grid.addWidget(fee_label, 2, 0)
2061 fee_e = AmountEdit(self.base_unit)
2062 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2063 grid.addWidget(fee_e, 2, 1)
2064 msg = _('Fee per kilobyte of transaction.') + ' ' \
2065 + _('Recommended value') + ': ' + self.format_amount(20000)
2066 grid.addWidget(HelpButton(msg), 2, 2)
2067 if not self.config.is_modifiable('fee_per_kb'):
2068 for w in [fee_e, fee_label]: w.setEnabled(False)
2070 units = ['BTC', 'mBTC']
2071 unit_label = QLabel(_('Base unit') + ':')
2072 grid.addWidget(unit_label, 3, 0)
2073 unit_combo = QComboBox()
2074 unit_combo.addItems(units)
2075 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2076 grid.addWidget(unit_combo, 3, 1)
2077 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2078 + '\n1BTC=1000mBTC.\n' \
2079 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2081 usechange_cb = QCheckBox(_('Use change addresses'))
2082 usechange_cb.setChecked(self.wallet.use_change)
2083 grid.addWidget(usechange_cb, 4, 0)
2084 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2085 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2087 block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
2088 block_ex_label = QLabel(_('Online Block Explorer') + ':')
2089 grid.addWidget(block_ex_label, 5, 0)
2090 block_ex_combo = QComboBox()
2091 block_ex_combo.addItems(block_explorers)
2092 block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
2093 grid.addWidget(block_ex_combo, 5, 1)
2094 grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2096 show_tx = self.config.get('show_before_broadcast', False)
2097 showtx_cb = QCheckBox(_('Show before broadcast'))
2098 showtx_cb.setChecked(show_tx)
2099 grid.addWidget(showtx_cb, 6, 0)
2100 grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2102 vbox.addLayout(grid)
2104 vbox.addLayout(ok_cancel_buttons(d))
2108 if not d.exec_(): return
2110 fee = unicode(fee_e.text())
2112 fee = self.read_amount(fee)
2114 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2117 self.wallet.set_fee(fee)
2119 nz = unicode(nz_e.text())
2124 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2127 if self.num_zeros != nz:
2129 self.config.set_key('num_zeros', nz, True)
2130 self.update_history_tab()
2131 self.update_receive_tab()
2133 usechange_result = usechange_cb.isChecked()
2134 if self.wallet.use_change != usechange_result:
2135 self.wallet.use_change = usechange_result
2136 self.wallet.storage.put('use_change', self.wallet.use_change)
2138 if showtx_cb.isChecked() != show_tx:
2139 self.config.set_key('show_before_broadcast', not show_tx)
2141 unit_result = units[unit_combo.currentIndex()]
2142 if self.base_unit() != unit_result:
2143 self.decimal_point = 8 if unit_result == 'BTC' else 5
2144 self.config.set_key('decimal_point', self.decimal_point, True)
2145 self.update_history_tab()
2146 self.update_status()
2148 need_restart = False
2150 lang_request = languages.keys()[lang_combo.currentIndex()]
2151 if lang_request != self.config.get('language'):
2152 self.config.set_key("language", lang_request, True)
2155 be_result = block_explorers[block_ex_combo.currentIndex()]
2156 self.config.set_key('block_explorer', be_result, True)
2158 run_hook('close_settings_dialog')
2161 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2164 def run_network_dialog(self):
2165 if not self.network:
2167 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2169 def closeEvent(self, event):
2172 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2173 self.save_column_widths()
2174 self.config.set_key("console-history", self.console.history[-50:], True)
2175 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2179 def plugins_dialog(self):
2180 from electrum.plugins import plugins
2183 d.setWindowTitle(_('Electrum Plugins'))
2186 vbox = QVBoxLayout(d)
2189 scroll = QScrollArea()
2190 scroll.setEnabled(True)
2191 scroll.setWidgetResizable(True)
2192 scroll.setMinimumSize(400,250)
2193 vbox.addWidget(scroll)
2197 w.setMinimumHeight(len(plugins)*35)
2199 grid = QGridLayout()
2200 grid.setColumnStretch(0,1)
2203 def do_toggle(cb, p, w):
2206 if w: w.setEnabled(r)
2208 def mk_toggle(cb, p, w):
2209 return lambda: do_toggle(cb,p,w)
2211 for i, p in enumerate(plugins):
2213 cb = QCheckBox(p.fullname())
2214 cb.setDisabled(not p.is_available())
2215 cb.setChecked(p.is_enabled())
2216 grid.addWidget(cb, i, 0)
2217 if p.requires_settings():
2218 w = p.settings_widget(self)
2219 w.setEnabled( p.is_enabled() )
2220 grid.addWidget(w, i, 1)
2223 cb.clicked.connect(mk_toggle(cb,p,w))
2224 grid.addWidget(HelpButton(p.description()), i, 2)
2226 print_msg(_("Error: cannot display plugin"), p)
2227 traceback.print_exc(file=sys.stdout)
2228 grid.setRowStretch(i+1,1)
2230 vbox.addLayout(close_button(d))
2235 def show_account_details(self, k):
2236 account = self.wallet.accounts[k]
2239 d.setWindowTitle(_('Account Details'))
2242 vbox = QVBoxLayout(d)
2243 name = self.wallet.get_account_name(k)
2244 label = QLabel('Name: ' + name)
2245 vbox.addWidget(label)
2247 vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2249 vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2251 vbox.addWidget(QLabel(_('Master Public Key:')))
2254 text.setReadOnly(True)
2255 text.setMaximumHeight(170)
2256 vbox.addWidget(text)
2258 mpk_text = '\n'.join( account.get_master_pubkeys() )
2259 text.setText(mpk_text)
2261 vbox.addLayout(close_button(d))