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):
102 def build_menu(self):
104 m.addAction(_("Show/Hide"), self.show_or_hide)
106 m.addAction(_("Exit Electrum"), self.close)
107 self.tray.setContextMenu(m)
109 def show_or_hide(self):
110 self.tray_activated(QSystemTrayIcon.DoubleClick)
112 def tray_activated(self, reason):
113 if reason == QSystemTrayIcon.DoubleClick:
114 if self.isMinimized() or self.isHidden():
119 def __init__(self, config, network):
120 QMainWindow.__init__(self)
123 self.network = network
125 self._close_electrum = False
128 if sys.platform == 'darwin':
129 self.icon = QIcon(":icons/electrum_dark_icon.png")
130 #self.icon = QIcon(":icons/lock.png")
132 self.icon = QIcon(':icons/electrum_light_icon.png')
134 self.tray = QSystemTrayIcon(self.icon, self)
135 self.tray.setToolTip('Electrum')
136 self.tray.activated.connect(self.tray_activated)
140 self.create_status_bar()
142 self.need_update = threading.Event()
144 self.decimal_point = config.get('decimal_point', 8)
145 self.num_zeros = int(config.get('num_zeros',0))
147 set_language(config.get('language'))
149 self.funds_error = False
150 self.completions = QStringListModel()
152 self.tabs = tabs = QTabWidget(self)
153 self.column_widths = self.config.get("column_widths_2", default_column_widths )
154 tabs.addTab(self.create_history_tab(), _('History') )
155 tabs.addTab(self.create_send_tab(), _('Send') )
156 tabs.addTab(self.create_receive_tab(), _('Receive') )
157 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
158 tabs.addTab(self.create_console_tab(), _('Console') )
159 tabs.setMinimumSize(600, 400)
160 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
161 self.setCentralWidget(tabs)
163 g = self.config.get("winpos-qt",[100, 100, 840, 400])
164 self.setGeometry(g[0], g[1], g[2], g[3])
166 self.setWindowIcon(QIcon(":icons/electrum.png"))
169 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
170 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
171 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
172 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
173 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
175 for i in range(tabs.count()):
176 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
178 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
179 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
180 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
182 self.history_list.setFocus(True)
186 self.network.register_callback('updated', lambda: self.need_update.set())
187 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
188 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
189 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
190 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
192 # set initial message
193 self.console.showMessage(self.network.banner)
200 self.config.set_key('lite_mode', False, True)
205 self.config.set_key('lite_mode', True, True)
212 if not self.check_qt_version():
213 if self.config.get('lite_mode') is True:
214 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
215 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
216 self.config.set_key('lite_mode', False, True)
222 actuator = lite_window.MiniActuator(self)
224 actuator.load_theme()
226 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
228 driver = lite_window.MiniDriver(self, self.mini)
230 if self.config.get('lite_mode') is True:
236 def check_qt_version(self):
237 qtVersion = qVersion()
238 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
241 def update_account_selector(self):
243 accounts = self.wallet.get_account_names()
244 self.account_selector.clear()
245 if len(accounts) > 1:
246 self.account_selector.addItems([_("All accounts")] + accounts.values())
247 self.account_selector.setCurrentIndex(0)
248 self.account_selector.show()
250 self.account_selector.hide()
253 def load_wallet(self, wallet):
256 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
257 self.current_account = self.wallet.storage.get("current_account", None)
259 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
260 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
261 self.setWindowTitle( title )
263 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
264 self.notify_transactions()
265 self.update_account_selector()
266 self.new_account.setEnabled(self.wallet.seed_version>4)
267 self.update_lock_icon()
268 self.update_buttons_on_seed()
269 self.update_console()
271 run_hook('load_wallet', wallet)
274 def open_wallet(self):
275 wallet_folder = self.wallet.storage.path
276 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
280 storage = WalletStorage({'wallet_path': filename})
281 if not storage.file_exists:
282 self.show_message("file not found "+ filename)
285 self.wallet.stop_threads()
288 wallet = Wallet(storage)
289 wallet.start_threads(self.network)
291 self.load_wallet(wallet)
295 def backup_wallet(self):
297 path = self.wallet.storage.path
298 wallet_folder = os.path.dirname(path)
299 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
303 new_path = os.path.join(wallet_folder, filename)
306 shutil.copy2(path, new_path)
307 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
308 except (IOError, os.error), reason:
309 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
312 def new_wallet(self):
315 wallet_folder = os.path.dirname(self.wallet.storage.path)
316 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
319 filename = os.path.join(wallet_folder, filename)
321 storage = WalletStorage({'wallet_path': filename})
322 if storage.file_exists:
323 QMessageBox.critical(None, "Error", _("File exists"))
326 wizard = installwizard.InstallWizard(self.config, self.network, storage)
327 wallet = wizard.run()
329 self.load_wallet(wallet)
333 def init_menubar(self):
336 file_menu = menubar.addMenu(_("&File"))
337 file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
338 file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
339 file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
340 file_menu.addAction(_("&Quit"), self.close)
342 wallet_menu = menubar.addMenu(_("&Wallet"))
343 wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
344 self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
346 wallet_menu.addSeparator()
348 wallet_menu.addAction(_("&Password"), self.change_password_dialog)
349 wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
350 wallet_menu.addAction(_("&Master Public Key"), self.show_master_public_key)
352 wallet_menu.addSeparator()
353 labels_menu = wallet_menu.addMenu(_("&Labels"))
354 labels_menu.addAction(_("&Import"), self.do_import_labels)
355 labels_menu.addAction(_("&Export"), self.do_export_labels)
357 keys_menu = wallet_menu.addMenu(_("&Private keys"))
358 keys_menu.addAction(_("&Import"), self.do_import_privkey)
359 keys_menu.addAction(_("&Export"), self.do_export_privkeys)
361 wallet_menu.addAction(_("&Export History"), self.do_export_history)
363 tools_menu = menubar.addMenu(_("&Tools"))
365 # Settings / Preferences are all reserved keywords in OSX using this as work around
366 tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
367 tools_menu.addAction(_("&Network"), self.run_network_dialog)
368 tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
369 tools_menu.addSeparator()
370 tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
371 #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
372 tools_menu.addSeparator()
374 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
375 csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
376 csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
378 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
379 raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
380 raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
381 raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
383 help_menu = menubar.addMenu(_("&Help"))
384 help_menu.addAction(_("&About"), self.show_about)
385 help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
386 help_menu.addSeparator()
387 help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
388 help_menu.addAction(_("&Report Bug"), self.show_report_bug)
390 self.setMenuBar(menubar)
392 def show_about(self):
393 QMessageBox.about(self, "Electrum",
394 _("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."))
396 def show_report_bug(self):
397 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
398 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
401 def notify_transactions(self):
402 if not self.network or not self.network.is_connected():
405 print_error("Notifying GUI")
406 if len(self.network.interface.pending_transactions_for_notifications) > 0:
407 # Combine the transactions if there are more then three
408 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
411 for tx in self.network.interface.pending_transactions_for_notifications:
412 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
416 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
417 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
419 self.network.interface.pending_transactions_for_notifications = []
421 for tx in self.network.interface.pending_transactions_for_notifications:
423 self.network.interface.pending_transactions_for_notifications.remove(tx)
424 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
426 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
428 def notify(self, message):
429 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
433 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
434 def getOpenFileName(self, title, filter = ""):
435 directory = self.config.get('io_dir', os.path.expanduser('~'))
436 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
437 if fileName and directory != os.path.dirname(fileName):
438 self.config.set_key('io_dir', os.path.dirname(fileName), True)
441 def getSaveFileName(self, title, filename, filter = ""):
442 directory = self.config.get('io_dir', os.path.expanduser('~'))
443 path = os.path.join( directory, filename )
444 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
445 if fileName and directory != os.path.dirname(fileName):
446 self.config.set_key('io_dir', os.path.dirname(fileName), True)
450 QMainWindow.close(self)
451 run_hook('close_main_window')
453 def connect_slots(self, sender):
454 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
455 self.previous_payto_e=''
457 def timer_actions(self):
458 if self.need_update.is_set():
460 self.need_update.clear()
461 run_hook('timer_actions')
463 def format_amount(self, x, is_diff=False, whitespaces=False):
464 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
466 def read_amount(self, x):
467 if x in['.', '']: return None
468 p = pow(10, self.decimal_point)
469 return int( p * Decimal(x) )
472 assert self.decimal_point in [5,8]
473 return "BTC" if self.decimal_point == 8 else "mBTC"
476 def update_status(self):
477 if self.network is None or not self.network.is_running():
479 icon = QIcon(":icons/status_disconnected.png")
481 elif self.network.is_connected():
482 if not self.wallet.up_to_date:
483 text = _("Synchronizing...")
484 icon = QIcon(":icons/status_waiting.png")
485 elif self.network.server_lag > 1:
486 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
487 icon = QIcon(":icons/status_lagging.png")
489 c, u = self.wallet.get_account_balance(self.current_account)
490 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
491 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
493 # append fiat balance and price from exchange rate plugin
495 run_hook('get_fiat_status_text', c+u, r)
500 self.tray.setToolTip(text)
501 icon = QIcon(":icons/status_connected.png")
503 text = _("Not connected")
504 icon = QIcon(":icons/status_disconnected.png")
506 self.balance_label.setText(text)
507 self.status_button.setIcon( icon )
510 def update_wallet(self):
512 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
513 self.update_history_tab()
514 self.update_receive_tab()
515 self.update_contacts_tab()
516 self.update_completions()
519 def create_history_tab(self):
520 self.history_list = l = MyTreeWidget(self)
522 for i,width in enumerate(self.column_widths['history']):
523 l.setColumnWidth(i, width)
524 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
525 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
526 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
528 l.customContextMenuRequested.connect(self.create_history_menu)
532 def create_history_menu(self, position):
533 self.history_list.selectedIndexes()
534 item = self.history_list.currentItem()
536 tx_hash = str(item.data(0, Qt.UserRole).toString())
537 if not tx_hash: return
539 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
540 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
541 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
542 menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
543 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
546 def show_transaction(self, tx):
547 import transaction_dialog
548 d = transaction_dialog.TxDialog(tx, self)
551 def tx_label_clicked(self, item, column):
552 if column==2 and item.isSelected():
554 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
555 self.history_list.editItem( item, column )
556 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
559 def tx_label_changed(self, item, column):
563 tx_hash = str(item.data(0, Qt.UserRole).toString())
564 tx = self.wallet.transactions.get(tx_hash)
565 text = unicode( item.text(2) )
566 self.wallet.set_label(tx_hash, text)
568 item.setForeground(2, QBrush(QColor('black')))
570 text = self.wallet.get_default_label(tx_hash)
571 item.setText(2, text)
572 item.setForeground(2, QBrush(QColor('gray')))
576 def edit_label(self, is_recv):
577 l = self.receive_list if is_recv else self.contacts_list
578 item = l.currentItem()
579 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
580 l.editItem( item, 1 )
581 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
585 def address_label_clicked(self, item, column, l, column_addr, column_label):
586 if column == column_label and item.isSelected():
587 is_editable = item.data(0, 32).toBool()
590 addr = unicode( item.text(column_addr) )
591 label = unicode( item.text(column_label) )
592 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
593 l.editItem( item, column )
594 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
597 def address_label_changed(self, item, column, l, column_addr, column_label):
598 if column == column_label:
599 addr = unicode( item.text(column_addr) )
600 text = unicode( item.text(column_label) )
601 is_editable = item.data(0, 32).toBool()
605 changed = self.wallet.set_label(addr, text)
607 self.update_history_tab()
608 self.update_completions()
610 self.current_item_changed(item)
612 run_hook('item_changed', item, column)
615 def current_item_changed(self, a):
616 run_hook('current_item_changed', a)
620 def update_history_tab(self):
622 self.history_list.clear()
623 for item in self.wallet.get_tx_history(self.current_account):
624 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
625 time_str = _("unknown")
628 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
630 time_str = _("error")
633 time_str = 'unverified'
634 icon = QIcon(":icons/unconfirmed.png")
637 icon = QIcon(":icons/unconfirmed.png")
639 icon = QIcon(":icons/clock%d.png"%conf)
641 icon = QIcon(":icons/confirmed.png")
643 if value is not None:
644 v_str = self.format_amount(value, True, whitespaces=True)
648 balance_str = self.format_amount(balance, whitespaces=True)
651 label, is_default_label = self.wallet.get_label(tx_hash)
653 label = _('Pruned transaction outputs')
654 is_default_label = False
656 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
657 item.setFont(2, QFont(MONOSPACE_FONT))
658 item.setFont(3, QFont(MONOSPACE_FONT))
659 item.setFont(4, QFont(MONOSPACE_FONT))
661 item.setForeground(3, QBrush(QColor("#BC1E1E")))
663 item.setData(0, Qt.UserRole, tx_hash)
664 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
666 item.setForeground(2, QBrush(QColor('grey')))
668 item.setIcon(0, icon)
669 self.history_list.insertTopLevelItem(0,item)
672 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
673 run_hook('history_tab_update')
676 def create_send_tab(self):
681 grid.setColumnMinimumWidth(3,300)
682 grid.setColumnStretch(5,1)
685 self.payto_e = QLineEdit()
686 grid.addWidget(QLabel(_('Pay to')), 1, 0)
687 grid.addWidget(self.payto_e, 1, 1, 1, 3)
689 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)
691 completer = QCompleter()
692 completer.setCaseSensitivity(False)
693 self.payto_e.setCompleter(completer)
694 completer.setModel(self.completions)
696 self.message_e = QLineEdit()
697 grid.addWidget(QLabel(_('Description')), 2, 0)
698 grid.addWidget(self.message_e, 2, 1, 1, 3)
699 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)
701 self.from_label = QLabel(_('From'))
702 grid.addWidget(self.from_label, 3, 0)
703 self.from_list = QTreeWidget(self)
704 self.from_list.setColumnCount(2)
705 self.from_list.setColumnWidth(0, 350)
706 self.from_list.setColumnWidth(1, 50)
707 self.from_list.setHeaderHidden (True)
708 self.from_list.setMaximumHeight(80)
709 grid.addWidget(self.from_list, 3, 1, 1, 3)
710 self.set_pay_from([])
712 self.amount_e = AmountEdit(self.base_unit)
713 grid.addWidget(QLabel(_('Amount')), 4, 0)
714 grid.addWidget(self.amount_e, 4, 1, 1, 2)
715 grid.addWidget(HelpButton(
716 _('Amount to be sent.') + '\n\n' \
717 + _('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.') \
718 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
720 self.fee_e = AmountEdit(self.base_unit)
721 grid.addWidget(QLabel(_('Fee')), 5, 0)
722 grid.addWidget(self.fee_e, 5, 1, 1, 2)
723 grid.addWidget(HelpButton(
724 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
725 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
726 + _('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)
728 run_hook('exchange_rate_button', grid)
730 self.send_button = EnterButton(_("Send"), self.do_send)
731 grid.addWidget(self.send_button, 6, 1)
733 b = EnterButton(_("Clear"),self.do_clear)
734 grid.addWidget(b, 6, 2)
736 self.payto_sig = QLabel('')
737 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
739 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
740 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
749 def entry_changed( is_fee ):
750 self.funds_error = False
752 if self.amount_e.is_shortcut:
753 self.amount_e.is_shortcut = False
754 sendable = self.get_sendable_balance()
755 # there is only one output because we are completely spending inputs
756 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
757 fee = self.wallet.estimated_fee(inputs, 1)
759 self.amount_e.setText( self.format_amount(amount) )
760 self.fee_e.setText( self.format_amount( fee ) )
763 amount = self.read_amount(str(self.amount_e.text()))
764 fee = self.read_amount(str(self.fee_e.text()))
766 if not is_fee: fee = None
769 # assume that there will be 2 outputs (one for change)
770 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
772 self.fee_e.setText( self.format_amount( fee ) )
775 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
779 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
780 self.funds_error = True
781 text = _( "Not enough funds" )
782 c, u = self.wallet.get_frozen_balance()
783 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
785 self.statusBar().showMessage(text)
786 self.amount_e.setPalette(palette)
787 self.fee_e.setPalette(palette)
789 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
790 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
792 run_hook('create_send_tab', grid)
796 def set_pay_from(self, l):
798 self.from_list.clear()
799 self.from_label.setHidden(len(self.pay_from) == 0)
800 self.from_list.setHidden(len(self.pay_from) == 0)
801 for addr in self.pay_from:
802 c, u = self.wallet.get_addr_balance(addr)
803 balance = self.format_amount(c + u)
804 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
807 def update_completions(self):
809 for addr,label in self.wallet.labels.items():
810 if addr in self.wallet.addressbook:
811 l.append( label + ' <' + addr + '>')
813 run_hook('update_completions', l)
814 self.completions.setStringList(l)
818 return lambda s, *args: s.do_protect(func, args)
823 label = unicode( self.message_e.text() )
824 r = unicode( self.payto_e.text() )
827 # label or alias, with address in brackets
828 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
829 to_address = m.group(2) if m else r
831 if not is_valid(to_address):
832 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
836 amount = self.read_amount(unicode( self.amount_e.text()))
838 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
841 fee = self.read_amount(unicode( self.fee_e.text()))
843 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
846 confirm_amount = self.config.get('confirm_amount', 100000000)
847 if amount >= confirm_amount:
848 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
851 confirm_fee = self.config.get('confirm_fee', 100000)
852 if fee >= confirm_fee:
853 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()}):
856 self.send_tx(to_address, amount, fee, label)
859 def waiting_dialog(self, message):
861 d.setWindowTitle('Please wait')
863 vbox = QVBoxLayout(d)
870 def send_tx(self, to_address, amount, fee, label, password):
872 # first, create an unsigned tx
873 domain = self.get_payment_sources()
874 outputs = [(to_address, amount)]
876 tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
877 except Exception as e:
878 traceback.print_exc(file=sys.stdout)
879 self.show_message(str(e))
882 # call hook to see if plugin needs gui interaction
883 run_hook('send_tx', tx)
888 self.wallet.add_keypairs_from_wallet(tx, keypairs, password)
889 self.wallet.sign_transaction(tx, keypairs, password)
891 self.emit(SIGNAL('send_tx2'))
894 dialog = self.waiting_dialog('Signing..')
895 self.connect(self, QtCore.SIGNAL('send_tx2'), lambda: self.send_tx2(self.signed_tx, fee, label, dialog, password))
896 threading.Thread(target=sign_thread).start()
898 # add recipient to addressbook
899 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
900 self.wallet.addressbook.append(to_address)
903 def send_tx2(self, tx, fee, label, dialog, password):
906 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
907 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
911 self.wallet.set_label(tx.hash(), label)
915 d = self.waiting_dialog('Broadcasting...')
916 h = self.wallet.send_tx(tx)
917 self.wallet.tx_event.wait()
920 status, msg = self.wallet.receive_tx( h, tx )
922 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
924 self.update_contacts_tab()
926 QMessageBox.warning(self, _('Error'), msg, _('OK'))
929 self.show_transaction(tx)
934 def set_url(self, url):
935 address, amount, label, message, signature, identity, url = util.parse_url(url)
938 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
939 elif amount: amount = str(Decimal(amount))
942 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
945 self.mini.set_payment_fields(address, amount)
947 if label and self.wallet.labels.get(address) != label:
948 if self.question('Give label "%s" to address %s ?'%(label,address)):
949 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
950 self.wallet.addressbook.append(address)
951 self.wallet.set_label(address, label)
953 run_hook('set_url', url, self.show_message, self.question)
955 self.tabs.setCurrentIndex(1)
956 label = self.wallet.labels.get(address)
957 m_addr = label + ' <'+ address +'>' if label else address
958 self.payto_e.setText(m_addr)
960 self.message_e.setText(message)
962 self.amount_e.setText(amount)
965 self.set_frozen(self.payto_e,True)
966 self.set_frozen(self.amount_e,True)
967 self.set_frozen(self.message_e,True)
968 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
970 self.payto_sig.setVisible(False)
973 self.payto_sig.setVisible(False)
974 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
976 self.set_frozen(e,False)
978 self.set_pay_from([])
981 def set_frozen(self,entry,frozen):
983 entry.setReadOnly(True)
984 entry.setFrame(False)
986 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
987 entry.setPalette(palette)
989 entry.setReadOnly(False)
992 palette.setColor(entry.backgroundRole(), QColor('white'))
993 entry.setPalette(palette)
996 def set_addrs_frozen(self,addrs,freeze):
998 if not addr: continue
999 if addr in self.wallet.frozen_addresses and not freeze:
1000 self.wallet.unfreeze(addr)
1001 elif addr not in self.wallet.frozen_addresses and freeze:
1002 self.wallet.freeze(addr)
1003 self.update_receive_tab()
1007 def create_list_tab(self, headers):
1008 "generic tab creation method"
1009 l = MyTreeWidget(self)
1010 l.setColumnCount( len(headers) )
1011 l.setHeaderLabels( headers )
1014 vbox = QVBoxLayout()
1021 vbox.addWidget(buttons)
1023 hbox = QHBoxLayout()
1026 buttons.setLayout(hbox)
1031 def create_receive_tab(self):
1032 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1033 l.setContextMenuPolicy(Qt.CustomContextMenu)
1034 l.customContextMenuRequested.connect(self.create_receive_menu)
1035 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1036 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1037 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1038 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1039 self.receive_list = l
1040 self.receive_buttons_hbox = hbox
1047 def save_column_widths(self):
1048 self.column_widths["receive"] = []
1049 for i in range(self.receive_list.columnCount() -1):
1050 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1052 self.column_widths["history"] = []
1053 for i in range(self.history_list.columnCount() - 1):
1054 self.column_widths["history"].append(self.history_list.columnWidth(i))
1056 self.column_widths["contacts"] = []
1057 for i in range(self.contacts_list.columnCount() - 1):
1058 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1060 self.config.set_key("column_widths_2", self.column_widths, True)
1063 def create_contacts_tab(self):
1064 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1065 l.setContextMenuPolicy(Qt.CustomContextMenu)
1066 l.customContextMenuRequested.connect(self.create_contact_menu)
1067 for i,width in enumerate(self.column_widths['contacts']):
1068 l.setColumnWidth(i, width)
1070 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1071 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1072 self.contacts_list = l
1073 self.contacts_buttons_hbox = hbox
1078 def delete_imported_key(self, addr):
1079 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1080 self.wallet.delete_imported_key(addr)
1081 self.update_receive_tab()
1082 self.update_history_tab()
1084 def edit_account_label(self, k):
1085 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1087 label = unicode(text)
1088 self.wallet.set_label(k,label)
1089 self.update_receive_tab()
1091 def account_set_expanded(self, item, k, b):
1093 self.accounts_expanded[k] = b
1095 def create_account_menu(self, position, k, item):
1097 if item.isExpanded():
1098 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1100 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1101 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1102 if self.wallet.seed_version > 4:
1103 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1104 if self.wallet.account_is_pending(k):
1105 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1106 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1108 def delete_pending_account(self, k):
1109 self.wallet.delete_pending_account(k)
1110 self.update_receive_tab()
1112 def create_receive_menu(self, position):
1113 # fixme: this function apparently has a side effect.
1114 # if it is not called the menu pops up several times
1115 #self.receive_list.selectedIndexes()
1117 selected = self.receive_list.selectedItems()
1118 multi_select = len(selected) > 1
1119 addrs = [unicode(item.text(0)) for item in selected]
1120 if not multi_select:
1121 item = self.receive_list.itemAt(position)
1125 if not is_valid(addr):
1126 k = str(item.data(0,32).toString())
1128 self.create_account_menu(position, k, item)
1130 item.setExpanded(not item.isExpanded())
1134 if not multi_select:
1135 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1136 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1137 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1138 if self.wallet.seed:
1139 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1140 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1141 #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1142 if addr in self.wallet.imported_keys:
1143 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1145 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1146 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1147 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1148 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1150 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1151 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1153 run_hook('receive_menu', menu, addrs)
1154 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1157 def get_sendable_balance(self):
1158 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1161 def get_payment_sources(self):
1163 return self.pay_from
1165 return self.wallet.get_account_addresses(self.current_account)
1168 def send_from_addresses(self, addrs):
1169 self.set_pay_from( addrs )
1170 self.tabs.setCurrentIndex(1)
1173 def payto(self, addr):
1175 label = self.wallet.labels.get(addr)
1176 m_addr = label + ' <' + addr + '>' if label else addr
1177 self.tabs.setCurrentIndex(1)
1178 self.payto_e.setText(m_addr)
1179 self.amount_e.setFocus()
1182 def delete_contact(self, x):
1183 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1184 self.wallet.delete_contact(x)
1185 self.wallet.set_label(x, None)
1186 self.update_history_tab()
1187 self.update_contacts_tab()
1188 self.update_completions()
1191 def create_contact_menu(self, position):
1192 item = self.contacts_list.itemAt(position)
1195 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1197 addr = unicode(item.text(0))
1198 label = unicode(item.text(1))
1199 is_editable = item.data(0,32).toBool()
1200 payto_addr = item.data(0,33).toString()
1201 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1202 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1203 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1205 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1206 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1208 run_hook('create_contact_menu', menu, item)
1209 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1212 def update_receive_item(self, item):
1213 item.setFont(0, QFont(MONOSPACE_FONT))
1214 address = str(item.data(0,0).toString())
1215 label = self.wallet.labels.get(address,'')
1216 item.setData(1,0,label)
1217 item.setData(0,32, True) # is editable
1219 run_hook('update_receive_item', address, item)
1221 if not self.wallet.is_mine(address): return
1223 c, u = self.wallet.get_addr_balance(address)
1224 balance = self.format_amount(c + u)
1225 item.setData(2,0,balance)
1227 if address in self.wallet.frozen_addresses:
1228 item.setBackgroundColor(0, QColor('lightblue'))
1231 def update_receive_tab(self):
1232 l = self.receive_list
1235 l.setColumnHidden(2, False)
1236 l.setColumnHidden(3, False)
1237 for i,width in enumerate(self.column_widths['receive']):
1238 l.setColumnWidth(i, width)
1240 if self.current_account is None:
1241 account_items = self.wallet.accounts.items()
1242 elif self.current_account != -1:
1243 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1247 for k, account in account_items:
1248 name = self.wallet.get_account_name(k)
1249 c,u = self.wallet.get_account_balance(k)
1250 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1251 l.addTopLevelItem(account_item)
1252 account_item.setExpanded(self.accounts_expanded.get(k, True))
1253 account_item.setData(0, 32, k)
1255 if not self.wallet.is_seeded(k):
1256 icon = QIcon(":icons/key.png")
1257 account_item.setIcon(0, icon)
1259 for is_change in ([0,1]):
1260 name = _("Receiving") if not is_change else _("Change")
1261 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1262 account_item.addChild(seq_item)
1263 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1265 if not is_change: seq_item.setExpanded(True)
1270 for address in account.get_addresses(is_change):
1271 h = self.wallet.history.get(address,[])
1275 if gap > self.wallet.gap_limit:
1280 c, u = self.wallet.get_addr_balance(address)
1281 num_tx = '*' if h == ['*'] else "%d"%len(h)
1282 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1283 self.update_receive_item(item)
1285 item.setBackgroundColor(1, QColor('red'))
1286 if len(h) > 0 and c == -u:
1288 seq_item.insertChild(0,used_item)
1290 used_item.addChild(item)
1292 seq_item.addChild(item)
1295 for k, addr in self.wallet.get_pending_accounts():
1296 name = self.wallet.labels.get(k,'')
1297 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1298 self.update_receive_item(item)
1299 l.addTopLevelItem(account_item)
1300 account_item.setExpanded(True)
1301 account_item.setData(0, 32, k)
1302 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1303 account_item.addChild(item)
1304 self.update_receive_item(item)
1307 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1308 c,u = self.wallet.get_imported_balance()
1309 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1310 l.addTopLevelItem(account_item)
1311 account_item.setExpanded(True)
1312 for address in self.wallet.imported_keys.keys():
1313 item = QTreeWidgetItem( [ address, '', '', ''] )
1314 self.update_receive_item(item)
1315 account_item.addChild(item)
1318 # we use column 1 because column 0 may be hidden
1319 l.setCurrentItem(l.topLevelItem(0),1)
1322 def update_contacts_tab(self):
1323 l = self.contacts_list
1326 for address in self.wallet.addressbook:
1327 label = self.wallet.labels.get(address,'')
1328 n = self.wallet.get_num_tx(address)
1329 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1330 item.setFont(0, QFont(MONOSPACE_FONT))
1331 # 32 = label can be edited (bool)
1332 item.setData(0,32, True)
1334 item.setData(0,33, address)
1335 l.addTopLevelItem(item)
1337 run_hook('update_contacts_tab', l)
1338 l.setCurrentItem(l.topLevelItem(0))
1342 def create_console_tab(self):
1343 from console import Console
1344 self.console = console = Console()
1348 def update_console(self):
1349 console = self.console
1350 console.history = self.config.get("console-history",[])
1351 console.history_index = len(console.history)
1353 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1354 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1356 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1358 def mkfunc(f, method):
1359 return lambda *args: apply( f, (method, args, self.password_dialog ))
1361 if m[0]=='_' or m in ['network','wallet']: continue
1362 methods[m] = mkfunc(c._run, m)
1364 console.updateNamespace(methods)
1367 def change_account(self,s):
1368 if s == _("All accounts"):
1369 self.current_account = None
1371 accounts = self.wallet.get_account_names()
1372 for k, v in accounts.items():
1374 self.current_account = k
1375 self.update_history_tab()
1376 self.update_status()
1377 self.update_receive_tab()
1379 def create_status_bar(self):
1382 sb.setFixedHeight(35)
1383 qtVersion = qVersion()
1385 self.balance_label = QLabel("")
1386 sb.addWidget(self.balance_label)
1388 from version_getter import UpdateLabel
1389 self.updatelabel = UpdateLabel(self.config, sb)
1391 self.account_selector = QComboBox()
1392 self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1393 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1394 sb.addPermanentWidget(self.account_selector)
1396 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1397 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1399 self.lock_icon = QIcon()
1400 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1401 sb.addPermanentWidget( self.password_button )
1403 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1404 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1405 sb.addPermanentWidget( self.seed_button )
1406 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1407 sb.addPermanentWidget( self.status_button )
1409 run_hook('create_status_bar', (sb,))
1411 self.setStatusBar(sb)
1414 def update_lock_icon(self):
1415 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1416 self.password_button.setIcon( icon )
1419 def update_buttons_on_seed(self):
1420 if not self.wallet.is_watching_only():
1421 self.seed_button.show()
1422 self.password_button.show()
1423 self.send_button.setText(_("Send"))
1425 self.password_button.hide()
1426 self.seed_button.hide()
1427 self.send_button.setText(_("Create unsigned transaction"))
1430 def change_password_dialog(self):
1431 from password_dialog import PasswordDialog
1432 d = PasswordDialog(self.wallet, self)
1434 self.update_lock_icon()
1437 def new_contact_dialog(self):
1440 d.setWindowTitle(_("New Contact"))
1441 vbox = QVBoxLayout(d)
1442 vbox.addWidget(QLabel(_('New Contact')+':'))
1444 grid = QGridLayout()
1447 grid.addWidget(QLabel(_("Address")), 1, 0)
1448 grid.addWidget(line1, 1, 1)
1449 grid.addWidget(QLabel(_("Name")), 2, 0)
1450 grid.addWidget(line2, 2, 1)
1452 vbox.addLayout(grid)
1453 vbox.addLayout(ok_cancel_buttons(d))
1458 address = str(line1.text())
1459 label = unicode(line2.text())
1461 if not is_valid(address):
1462 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1465 self.wallet.add_contact(address)
1467 self.wallet.set_label(address, label)
1469 self.update_contacts_tab()
1470 self.update_history_tab()
1471 self.update_completions()
1472 self.tabs.setCurrentIndex(3)
1475 def new_account_dialog(self):
1477 dialog = QDialog(self)
1479 dialog.setWindowTitle(_("New Account"))
1481 vbox = QVBoxLayout()
1482 vbox.addWidget(QLabel(_('Account name')+':'))
1485 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1486 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1491 vbox.addLayout(ok_cancel_buttons(dialog))
1492 dialog.setLayout(vbox)
1496 name = str(e.text())
1499 self.wallet.create_pending_account('1', name)
1500 self.update_receive_tab()
1501 self.tabs.setCurrentIndex(2)
1505 def show_master_public_key_old(self):
1506 dialog = QDialog(self)
1508 dialog.setWindowTitle(_("Master Public Key"))
1510 main_text = QTextEdit()
1511 main_text.setText(self.wallet.get_master_public_key())
1512 main_text.setReadOnly(True)
1513 main_text.setMaximumHeight(170)
1514 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1516 ok_button = QPushButton(_("OK"))
1517 ok_button.setDefault(True)
1518 ok_button.clicked.connect(dialog.accept)
1520 main_layout = QGridLayout()
1521 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1523 main_layout.addWidget(main_text, 1, 0)
1524 main_layout.addWidget(qrw, 1, 1 )
1526 vbox = QVBoxLayout()
1527 vbox.addLayout(main_layout)
1528 vbox.addLayout(close_button(dialog))
1529 dialog.setLayout(vbox)
1533 def show_master_public_key(self):
1535 if self.wallet.seed_version == 4:
1536 self.show_master_public_key_old()
1539 dialog = QDialog(self)
1541 dialog.setWindowTitle(_("Master Public Keys"))
1543 chain_text = QTextEdit()
1544 chain_text.setReadOnly(True)
1545 chain_text.setMaximumHeight(170)
1546 chain_qrw = QRCodeWidget()
1548 mpk_text = QTextEdit()
1549 mpk_text.setReadOnly(True)
1550 mpk_text.setMaximumHeight(170)
1551 mpk_qrw = QRCodeWidget()
1553 main_layout = QGridLayout()
1555 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1556 main_layout.addWidget(mpk_text, 1, 1)
1557 main_layout.addWidget(mpk_qrw, 1, 2)
1559 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1560 main_layout.addWidget(chain_text, 2, 1)
1561 main_layout.addWidget(chain_qrw, 2, 2)
1564 c, K, cK = self.wallet.master_public_keys[str(key)]
1565 chain_text.setText(c)
1566 chain_qrw.set_addr(c)
1567 chain_qrw.update_qr()
1572 key_selector = QComboBox()
1573 keys = sorted(self.wallet.master_public_keys.keys())
1574 key_selector.addItems(keys)
1576 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1577 main_layout.addWidget(key_selector, 0, 1)
1578 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1582 vbox = QVBoxLayout()
1583 vbox.addLayout(main_layout)
1584 vbox.addLayout(close_button(dialog))
1586 dialog.setLayout(vbox)
1591 def show_seed_dialog(self, password):
1592 if self.wallet.is_watching_only():
1593 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1596 if self.wallet.seed:
1598 mnemonic = self.wallet.get_mnemonic(password)
1600 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1602 from seed_dialog import SeedDialog
1603 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1607 for k in self.wallet.master_private_keys.keys():
1608 pk = self.wallet.get_master_private_key(k, password)
1610 from seed_dialog import PrivateKeysDialog
1611 d = PrivateKeysDialog(self,l)
1618 def show_qrcode(self, data, title = _("QR code")):
1622 d.setWindowTitle(title)
1623 d.setMinimumSize(270, 300)
1624 vbox = QVBoxLayout()
1625 qrw = QRCodeWidget(data)
1626 vbox.addWidget(qrw, 1)
1627 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1628 hbox = QHBoxLayout()
1631 filename = os.path.join(self.config.path, "qrcode.bmp")
1634 bmp.save_qrcode(qrw.qr, filename)
1635 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1637 def copy_to_clipboard():
1638 bmp.save_qrcode(qrw.qr, filename)
1639 self.app.clipboard().setImage(QImage(filename))
1640 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1642 b = QPushButton(_("Copy"))
1644 b.clicked.connect(copy_to_clipboard)
1646 b = QPushButton(_("Save"))
1648 b.clicked.connect(print_qr)
1650 b = QPushButton(_("Close"))
1652 b.clicked.connect(d.accept)
1655 vbox.addLayout(hbox)
1660 def do_protect(self, func, args):
1661 if self.wallet.use_encryption:
1662 password = self.password_dialog()
1668 if args != (False,):
1669 args = (self,) + args + (password,)
1671 args = (self,password)
1676 def show_private_key(self, address, password):
1677 if not address: return
1679 pk_list = self.wallet.get_private_key(address, password)
1680 except Exception as e:
1681 self.show_message(str(e))
1685 d.setMinimumSize(600, 200)
1687 vbox = QVBoxLayout()
1688 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1689 vbox.addWidget( QLabel(_("Private key") + ':'))
1691 keys.setReadOnly(True)
1692 keys.setText('\n'.join(pk_list))
1693 vbox.addWidget(keys)
1694 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1695 vbox.addLayout(close_button(d))
1701 def do_sign(self, address, message, signature, password):
1702 message = unicode(message.toPlainText())
1703 message = message.encode('utf-8')
1705 sig = self.wallet.sign_message(str(address.text()), message, password)
1706 signature.setText(sig)
1707 except Exception as e:
1708 self.show_message(str(e))
1710 def do_verify(self, address, message, signature):
1711 message = unicode(message.toPlainText())
1712 message = message.encode('utf-8')
1713 if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1714 self.show_message(_("Signature verified"))
1716 self.show_message(_("Error: wrong signature"))
1719 def sign_verify_message(self, address=''):
1722 d.setWindowTitle(_('Sign/verify Message'))
1723 d.setMinimumSize(410, 290)
1725 layout = QGridLayout(d)
1727 message_e = QTextEdit()
1728 layout.addWidget(QLabel(_('Message')), 1, 0)
1729 layout.addWidget(message_e, 1, 1)
1730 layout.setRowStretch(2,3)
1732 address_e = QLineEdit()
1733 address_e.setText(address)
1734 layout.addWidget(QLabel(_('Address')), 2, 0)
1735 layout.addWidget(address_e, 2, 1)
1737 signature_e = QTextEdit()
1738 layout.addWidget(QLabel(_('Signature')), 3, 0)
1739 layout.addWidget(signature_e, 3, 1)
1740 layout.setRowStretch(3,1)
1742 hbox = QHBoxLayout()
1744 b = QPushButton(_("Sign"))
1745 b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1748 b = QPushButton(_("Verify"))
1749 b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1752 b = QPushButton(_("Close"))
1753 b.clicked.connect(d.accept)
1755 layout.addLayout(hbox, 4, 1)
1760 def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1762 decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1763 message_e.setText(decrypted)
1764 except Exception as e:
1765 self.show_message(str(e))
1768 def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1769 message = unicode(message_e.toPlainText())
1770 message = message.encode('utf-8')
1772 encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1773 encrypted_e.setText(encrypted)
1774 except Exception as e:
1775 self.show_message(str(e))
1779 def encrypt_message(self, address = ''):
1782 d.setWindowTitle(_('Encrypt/decrypt Message'))
1783 d.setMinimumSize(610, 490)
1785 layout = QGridLayout(d)
1787 message_e = QTextEdit()
1788 layout.addWidget(QLabel(_('Message')), 1, 0)
1789 layout.addWidget(message_e, 1, 1)
1790 layout.setRowStretch(2,3)
1792 pubkey_e = QLineEdit()
1794 pubkey = self.wallet.getpubkeys(address)[0]
1795 pubkey_e.setText(pubkey)
1796 layout.addWidget(QLabel(_('Public key')), 2, 0)
1797 layout.addWidget(pubkey_e, 2, 1)
1799 encrypted_e = QTextEdit()
1800 layout.addWidget(QLabel(_('Encrypted')), 3, 0)
1801 layout.addWidget(encrypted_e, 3, 1)
1802 layout.setRowStretch(3,1)
1804 hbox = QHBoxLayout()
1805 b = QPushButton(_("Encrypt"))
1806 b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
1809 b = QPushButton(_("Decrypt"))
1810 b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
1813 b = QPushButton(_("Close"))
1814 b.clicked.connect(d.accept)
1817 layout.addLayout(hbox, 4, 1)
1821 def question(self, msg):
1822 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1824 def show_message(self, msg):
1825 QMessageBox.information(self, _('Message'), msg, _('OK'))
1827 def password_dialog(self ):
1830 d.setWindowTitle(_("Enter Password"))
1835 vbox = QVBoxLayout()
1836 msg = _('Please enter your password')
1837 vbox.addWidget(QLabel(msg))
1839 grid = QGridLayout()
1841 grid.addWidget(QLabel(_('Password')), 1, 0)
1842 grid.addWidget(pw, 1, 1)
1843 vbox.addLayout(grid)
1845 vbox.addLayout(ok_cancel_buttons(d))
1848 run_hook('password_dialog', pw, grid, 1)
1849 if not d.exec_(): return
1850 return unicode(pw.text())
1859 def tx_from_text(self, txt):
1860 "json or raw hexadecimal"
1863 tx = Transaction(txt)
1869 tx_dict = json.loads(str(txt))
1870 assert "hex" in tx_dict.keys()
1871 assert "complete" in tx_dict.keys()
1872 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1873 if not tx_dict["complete"]:
1874 assert "input_info" in tx_dict.keys()
1875 input_info = json.loads(tx_dict['input_info'])
1876 tx.add_input_info(input_info)
1881 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1885 def read_tx_from_file(self):
1886 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1890 with open(fileName, "r") as f:
1891 file_content = f.read()
1892 except (ValueError, IOError, os.error), reason:
1893 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1895 return self.tx_from_text(file_content)
1899 def sign_raw_transaction(self, tx, input_info, password):
1900 self.wallet.signrawtransaction(tx, input_info, [], password)
1902 def do_process_from_text(self):
1903 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1906 tx = self.tx_from_text(text)
1908 self.show_transaction(tx)
1910 def do_process_from_file(self):
1911 tx = self.read_tx_from_file()
1913 self.show_transaction(tx)
1915 def do_process_from_txid(self):
1916 from electrum import transaction
1917 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1919 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1921 tx = transaction.Transaction(r)
1923 self.show_transaction(tx)
1925 self.show_message("unknown transaction")
1927 def do_process_from_csvReader(self, csvReader):
1932 for position, row in enumerate(csvReader):
1934 if not is_valid(address):
1935 errors.append((position, address))
1937 amount = Decimal(row[1])
1938 amount = int(100000000*amount)
1939 outputs.append((address, amount))
1940 except (ValueError, IOError, os.error), reason:
1941 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1945 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1946 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1950 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1951 except Exception as e:
1952 self.show_message(str(e))
1955 self.show_transaction(tx)
1957 def do_process_from_csv_file(self):
1958 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1962 with open(fileName, "r") as f:
1963 csvReader = csv.reader(f)
1964 self.do_process_from_csvReader(csvReader)
1965 except (ValueError, IOError, os.error), reason:
1966 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1969 def do_process_from_csv_text(self):
1970 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1971 + _("Format: address, amount. One output per line"), _("Load CSV"))
1974 f = StringIO.StringIO(text)
1975 csvReader = csv.reader(f)
1976 self.do_process_from_csvReader(csvReader)
1981 def do_export_privkeys(self, password):
1982 if not self.wallet.seed:
1983 self.show_message(_("This wallet has no seed"))
1986 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.")))
1989 select_export = _('Select file to export your private keys to')
1990 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1992 with open(fileName, "w+") as csvfile:
1993 transaction = csv.writer(csvfile)
1994 transaction.writerow(["address", "private_key"])
1996 addresses = self.wallet.addresses(True)
1998 for addr in addresses:
1999 pk = "".join(self.wallet.get_private_key(addr, password))
2000 transaction.writerow(["%34s"%addr,pk])
2002 self.show_message(_("Private keys exported."))
2004 except (IOError, os.error), reason:
2005 export_error_label = _("Electrum was unable to produce a private key-export.")
2006 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2008 except Exception as e:
2009 self.show_message(str(e))
2013 def do_import_labels(self):
2014 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2015 if not labelsFile: return
2017 f = open(labelsFile, 'r')
2020 for key, value in json.loads(data).items():
2021 self.wallet.set_label(key, value)
2022 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2023 except (IOError, os.error), reason:
2024 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2027 def do_export_labels(self):
2028 labels = self.wallet.labels
2030 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2032 with open(fileName, 'w+') as f:
2033 json.dump(labels, f)
2034 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2035 except (IOError, os.error), reason:
2036 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2039 def do_export_history(self):
2040 from lite_window import csv_transaction
2041 csv_transaction(self.wallet)
2045 def do_import_privkey(self, password):
2046 if not self.wallet.imported_keys:
2047 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2048 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2049 + _('Are you sure you understand what you are doing?'), 3, 4)
2052 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2055 text = str(text).split()
2060 addr = self.wallet.import_key(key, password)
2061 except Exception as e:
2067 addrlist.append(addr)
2069 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2071 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2072 self.update_receive_tab()
2073 self.update_history_tab()
2076 def settings_dialog(self):
2078 d.setWindowTitle(_('Electrum Settings'))
2080 vbox = QVBoxLayout()
2081 grid = QGridLayout()
2082 grid.setColumnStretch(0,1)
2084 nz_label = QLabel(_('Display zeros') + ':')
2085 grid.addWidget(nz_label, 0, 0)
2086 nz_e = AmountEdit(None,True)
2087 nz_e.setText("%d"% self.num_zeros)
2088 grid.addWidget(nz_e, 0, 1)
2089 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2090 grid.addWidget(HelpButton(msg), 0, 2)
2091 if not self.config.is_modifiable('num_zeros'):
2092 for w in [nz_e, nz_label]: w.setEnabled(False)
2094 lang_label=QLabel(_('Language') + ':')
2095 grid.addWidget(lang_label, 1, 0)
2096 lang_combo = QComboBox()
2097 from electrum.i18n import languages
2098 lang_combo.addItems(languages.values())
2100 index = languages.keys().index(self.config.get("language",''))
2103 lang_combo.setCurrentIndex(index)
2104 grid.addWidget(lang_combo, 1, 1)
2105 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2106 if not self.config.is_modifiable('language'):
2107 for w in [lang_combo, lang_label]: w.setEnabled(False)
2110 fee_label = QLabel(_('Transaction fee') + ':')
2111 grid.addWidget(fee_label, 2, 0)
2112 fee_e = AmountEdit(self.base_unit)
2113 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2114 grid.addWidget(fee_e, 2, 1)
2115 msg = _('Fee per kilobyte of transaction.') + ' ' \
2116 + _('Recommended value') + ': ' + self.format_amount(20000)
2117 grid.addWidget(HelpButton(msg), 2, 2)
2118 if not self.config.is_modifiable('fee_per_kb'):
2119 for w in [fee_e, fee_label]: w.setEnabled(False)
2121 units = ['BTC', 'mBTC']
2122 unit_label = QLabel(_('Base unit') + ':')
2123 grid.addWidget(unit_label, 3, 0)
2124 unit_combo = QComboBox()
2125 unit_combo.addItems(units)
2126 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2127 grid.addWidget(unit_combo, 3, 1)
2128 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2129 + '\n1BTC=1000mBTC.\n' \
2130 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2132 usechange_cb = QCheckBox(_('Use change addresses'))
2133 usechange_cb.setChecked(self.wallet.use_change)
2134 grid.addWidget(usechange_cb, 4, 0)
2135 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2136 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2138 grid.setRowStretch(5,1)
2140 vbox.addLayout(grid)
2141 vbox.addLayout(ok_cancel_buttons(d))
2145 if not d.exec_(): return
2147 fee = unicode(fee_e.text())
2149 fee = self.read_amount(fee)
2151 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2154 self.wallet.set_fee(fee)
2156 nz = unicode(nz_e.text())
2161 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2164 if self.num_zeros != nz:
2166 self.config.set_key('num_zeros', nz, True)
2167 self.update_history_tab()
2168 self.update_receive_tab()
2170 usechange_result = usechange_cb.isChecked()
2171 if self.wallet.use_change != usechange_result:
2172 self.wallet.use_change = usechange_result
2173 self.wallet.storage.put('use_change', self.wallet.use_change)
2175 unit_result = units[unit_combo.currentIndex()]
2176 if self.base_unit() != unit_result:
2177 self.decimal_point = 8 if unit_result == 'BTC' else 5
2178 self.config.set_key('decimal_point', self.decimal_point, True)
2179 self.update_history_tab()
2180 self.update_status()
2182 need_restart = False
2184 lang_request = languages.keys()[lang_combo.currentIndex()]
2185 if lang_request != self.config.get('language'):
2186 self.config.set_key("language", lang_request, True)
2189 run_hook('close_settings_dialog')
2192 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2195 def run_network_dialog(self):
2196 if not self.network:
2198 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2200 def closeEvent(self, event):
2203 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2204 self.save_column_widths()
2205 self.config.set_key("console-history", self.console.history[-50:], True)
2206 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2210 def plugins_dialog(self):
2211 from electrum.plugins import plugins
2214 d.setWindowTitle(_('Electrum Plugins'))
2217 vbox = QVBoxLayout(d)
2220 scroll = QScrollArea()
2221 scroll.setEnabled(True)
2222 scroll.setWidgetResizable(True)
2223 scroll.setMinimumSize(400,250)
2224 vbox.addWidget(scroll)
2228 w.setMinimumHeight(len(plugins)*35)
2230 grid = QGridLayout()
2231 grid.setColumnStretch(0,1)
2234 def do_toggle(cb, p, w):
2237 if w: w.setEnabled(r)
2239 def mk_toggle(cb, p, w):
2240 return lambda: do_toggle(cb,p,w)
2242 for i, p in enumerate(plugins):
2244 cb = QCheckBox(p.fullname())
2245 cb.setDisabled(not p.is_available())
2246 cb.setChecked(p.is_enabled())
2247 grid.addWidget(cb, i, 0)
2248 if p.requires_settings():
2249 w = p.settings_widget(self)
2250 w.setEnabled( p.is_enabled() )
2251 grid.addWidget(w, i, 1)
2254 cb.clicked.connect(mk_toggle(cb,p,w))
2255 grid.addWidget(HelpButton(p.description()), i, 2)
2257 print_msg(_("Error: cannot display plugin"), p)
2258 traceback.print_exc(file=sys.stdout)
2259 grid.setRowStretch(i+1,1)
2261 vbox.addLayout(close_button(d))
2266 def show_account_details(self, k):
2268 d.setWindowTitle(_('Account Details'))
2271 vbox = QVBoxLayout(d)
2272 roots = self.wallet.get_roots(k)
2274 name = self.wallet.get_account_name(k)
2275 label = QLabel('Name: ' + name)
2276 vbox.addWidget(label)
2278 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2279 vbox.addWidget(QLabel('Type: ' + acctype))
2281 label = QLabel('Derivation: ' + k)
2282 vbox.addWidget(label)
2285 # mpk = self.wallet.master_public_keys[root]
2286 # text = QTextEdit()
2287 # text.setReadOnly(True)
2288 # text.setMaximumHeight(120)
2289 # text.setText(repr(mpk))
2290 # vbox.addWidget(text)
2292 vbox.addLayout(close_button(d))