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
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
32 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
33 from electrum.plugins import run_hook
37 from electrum.wallet import format_satoshis
38 from electrum import Transaction
39 from electrum import mnemonic
40 from electrum import util, bitcoin, commands, Interface, Wallet
41 from electrum import SimpleConfig, Wallet, WalletStorage
44 from electrum import bmp, pyqrnative
46 from amountedit import AmountEdit
47 from network_dialog import NetworkDialog
48 from qrcodewidget import QRCodeWidget
50 from decimal import Decimal
58 if platform.system() == 'Windows':
59 MONOSPACE_FONT = 'Lucida Console'
60 elif platform.system() == 'Darwin':
61 MONOSPACE_FONT = 'Monaco'
63 MONOSPACE_FONT = 'monospace'
65 from electrum import ELECTRUM_VERSION
75 class StatusBarButton(QPushButton):
76 def __init__(self, icon, tooltip, func):
77 QPushButton.__init__(self, icon, '')
78 self.setToolTip(tooltip)
80 self.setMaximumWidth(25)
81 self.clicked.connect(func)
83 self.setIconSize(QSize(25,25))
85 def keyPressEvent(self, e):
86 if e.key() == QtCore.Qt.Key_Return:
98 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
100 class ElectrumWindow(QMainWindow):
101 def build_menu(self):
103 m.addAction(_("Show/Hide"), self.show_or_hide)
105 m.addAction(_("Exit Electrum"), self.close)
106 self.tray.setContextMenu(m)
108 def show_or_hide(self):
109 self.tray_activated(QSystemTrayIcon.DoubleClick)
111 def tray_activated(self, reason):
112 if reason == QSystemTrayIcon.DoubleClick:
113 if self.isMinimized() or self.isHidden():
118 def __init__(self, config, network):
119 QMainWindow.__init__(self)
122 self.network = network
124 self._close_electrum = False
127 if sys.platform == 'darwin':
128 self.icon = QIcon(":icons/electrum_dark_icon.png")
129 #self.icon = QIcon(":icons/lock.png")
131 self.icon = QIcon(':icons/electrum_light_icon.png')
133 self.tray = QSystemTrayIcon(self.icon, self)
134 self.tray.setToolTip('Electrum')
135 self.tray.activated.connect(self.tray_activated)
139 self.create_status_bar()
141 self.need_update = threading.Event()
143 self.decimal_point = config.get('decimal_point', 8)
144 self.num_zeros = int(config.get('num_zeros',0))
146 set_language(config.get('language'))
148 self.funds_error = False
149 self.completions = QStringListModel()
151 self.tabs = tabs = QTabWidget(self)
152 self.column_widths = self.config.get("column_widths_2", default_column_widths )
153 tabs.addTab(self.create_history_tab(), _('History') )
154 tabs.addTab(self.create_send_tab(), _('Send') )
155 tabs.addTab(self.create_receive_tab(), _('Receive') )
156 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
157 tabs.addTab(self.create_console_tab(), _('Console') )
158 tabs.setMinimumSize(600, 400)
159 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
160 self.setCentralWidget(tabs)
162 g = self.config.get("winpos-qt",[100, 100, 840, 400])
163 self.setGeometry(g[0], g[1], g[2], g[3])
165 self.setWindowIcon(QIcon(":icons/electrum.png"))
168 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
169 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
170 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
171 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
172 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
174 for i in range(tabs.count()):
175 QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
177 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
178 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
179 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
181 self.history_list.setFocus(True)
185 self.network.register_callback('updated', lambda: self.need_update.set())
186 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
187 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
188 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
189 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
191 # set initial message
192 self.console.showMessage(self.network.banner)
199 self.config.set_key('lite_mode', False, True)
204 self.config.set_key('lite_mode', True, True)
211 if not self.check_qt_version():
212 if self.config.get('lite_mode') is True:
213 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
214 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
215 self.config.set_key('lite_mode', False, True)
221 actuator = lite_window.MiniActuator(self)
223 # Should probably not modify the current path but instead
224 # change the behaviour of rsrc(...)
225 old_path = QDir.currentPath()
226 actuator.load_theme()
228 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
230 driver = lite_window.MiniDriver(self, self.mini)
232 # Reset path back to original value now that loading the GUI
234 QDir.setCurrent(old_path)
236 if self.config.get('lite_mode') is True:
242 def check_qt_version(self):
243 qtVersion = qVersion()
244 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
247 def update_account_selector(self):
249 accounts = self.wallet.get_account_names()
250 self.account_selector.clear()
251 if len(accounts) > 1:
252 self.account_selector.addItems([_("All accounts")] + accounts.values())
253 self.account_selector.setCurrentIndex(0)
254 self.account_selector.show()
256 self.account_selector.hide()
259 def load_wallet(self, wallet):
262 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
263 self.current_account = self.wallet.storage.get("current_account", None)
265 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
266 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
267 self.setWindowTitle( title )
269 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
270 self.notify_transactions()
271 self.update_account_selector()
272 self.new_account.setEnabled(self.wallet.seed_version>4)
273 self.update_lock_icon()
274 self.update_buttons_on_seed()
275 self.update_console()
277 run_hook('load_wallet', wallet)
280 def open_wallet(self):
281 wallet_folder = self.wallet.storage.path
282 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
286 storage = WalletStorage({'wallet_path': filename})
287 if not storage.file_exists:
288 self.show_message("file not found "+ filename)
291 self.wallet.stop_threads()
294 wallet = Wallet(storage)
295 wallet.start_threads(self.network)
297 self.load_wallet(wallet)
301 def backup_wallet(self):
303 path = self.wallet.storage.path
304 wallet_folder = os.path.dirname(path)
305 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
309 new_path = os.path.join(wallet_folder, filename)
312 shutil.copy2(path, new_path)
313 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
314 except (IOError, os.error), reason:
315 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
318 def new_wallet(self):
321 wallet_folder = os.path.dirname(self.wallet.storage.path)
322 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
325 filename = os.path.join(wallet_folder, filename)
327 storage = WalletStorage({'wallet_path': filename})
328 if storage.file_exists:
329 QMessageBox.critical(None, "Error", _("File exists"))
332 wizard = installwizard.InstallWizard(self.config, self.network, storage)
333 wallet = wizard.run()
335 self.load_wallet(wallet)
339 def init_menubar(self):
342 file_menu = menubar.addMenu(_("&File"))
343 open_wallet_action = file_menu.addAction(_("&Open"))
344 open_wallet_action.setShortcut(QKeySequence.Open)
345 open_wallet_action.triggered.connect(self.open_wallet)
347 new_wallet_action = file_menu.addAction(_("&New/Restore"))
348 new_wallet_action.setShortcut(QKeySequence.New)
349 new_wallet_action.triggered.connect(self.new_wallet)
351 wallet_backup = file_menu.addAction(_("&Save Copy"))
352 wallet_backup.setShortcut(QKeySequence.SaveAs)
353 wallet_backup.triggered.connect(self.backup_wallet)
355 quit_item = file_menu.addAction(_("&Quit"))
356 #quit_item.setShortcut(QKeySequence.Quit)
357 quit_item.triggered.connect(self.close)
359 wallet_menu = menubar.addMenu(_("&Wallet"))
361 new_contact = wallet_menu.addAction(_("&New contact"))
362 new_contact.triggered.connect(self.new_contact_dialog)
364 self.new_account = wallet_menu.addAction(_("&New account"))
365 self.new_account.triggered.connect(self.new_account_dialog)
367 wallet_menu.addSeparator()
369 pw = wallet_menu.addAction(_("&Password"))
370 pw.triggered.connect(self.change_password_dialog)
372 show_seed = wallet_menu.addAction(_("&Seed"))
373 show_seed.triggered.connect(self.show_seed_dialog)
375 show_mpk = wallet_menu.addAction(_("&Master Public Key"))
376 show_mpk.triggered.connect(self.show_master_public_key)
378 wallet_menu.addSeparator()
380 labels_menu = wallet_menu.addMenu(_("&Labels"))
381 import_labels = labels_menu.addAction(_("&Import"))
382 import_labels.triggered.connect(self.do_import_labels)
383 export_labels = labels_menu.addAction(_("&Export"))
384 export_labels.triggered.connect(self.do_export_labels)
386 keys_menu = wallet_menu.addMenu(_("&Private keys"))
387 import_keys = keys_menu.addAction(_("&Import"))
388 import_keys.triggered.connect(self.do_import_privkey)
389 export_keys = keys_menu.addAction(_("&Export"))
390 export_keys.triggered.connect(self.do_export_privkeys)
392 ex_history = wallet_menu.addAction(_("&Export History"))
393 ex_history.triggered.connect(self.do_export_history)
397 tools_menu = menubar.addMenu(_("&Tools"))
399 # Settings / Preferences are all reserved keywords in OSX using this as work around
400 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
401 preferences_menu = tools_menu.addAction(preferences_name)
402 #preferences_menu.setShortcut(QKeySequence.Preferences)
403 preferences_menu.triggered.connect(self.settings_dialog)
405 network = tools_menu.addAction(_("&Network"))
406 network.triggered.connect(self.run_network_dialog)
408 plugins_labels = tools_menu.addAction(_("&Plugins"))
409 plugins_labels.triggered.connect(self.plugins_dialog)
411 tools_menu.addSeparator()
413 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
415 csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
416 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
418 csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
419 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
421 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
423 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
424 raw_transaction_file.triggered.connect(self.do_process_from_file)
426 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
427 raw_transaction_text.triggered.connect(self.do_process_from_text)
429 raw_transaction_text = raw_transaction_menu.addAction(_("&From the blockchain"))
430 raw_transaction_text.triggered.connect(self.do_process_from_txid)
433 help_menu = menubar.addMenu(_("&Help"))
434 show_about = help_menu.addAction(_("&About"))
435 show_about.triggered.connect(self.show_about)
436 web_open = help_menu.addAction(_("&Official website"))
437 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
439 help_menu.addSeparator()
440 doc_open = help_menu.addAction(_("&Documentation"))
441 doc_open.setShortcut(QKeySequence.HelpContents)
442 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
443 report_bug = help_menu.addAction(_("&Report Bug"))
444 report_bug.triggered.connect(self.show_report_bug)
446 self.setMenuBar(menubar)
448 def show_about(self):
449 QMessageBox.about(self, "Electrum",
450 _("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."))
452 def show_report_bug(self):
453 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
454 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
457 def notify_transactions(self):
458 if not self.network or not self.network.is_connected():
461 print_error("Notifying GUI")
462 if len(self.network.interface.pending_transactions_for_notifications) > 0:
463 # Combine the transactions if there are more then three
464 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
467 for tx in self.network.interface.pending_transactions_for_notifications:
468 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
472 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
473 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
475 self.network.interface.pending_transactions_for_notifications = []
477 for tx in self.network.interface.pending_transactions_for_notifications:
479 self.network.interface.pending_transactions_for_notifications.remove(tx)
480 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
482 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
484 def notify(self, message):
485 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
489 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
490 def getOpenFileName(self, title, filter = ""):
491 directory = self.config.get('io_dir', os.path.expanduser('~'))
492 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
493 if fileName and directory != os.path.dirname(fileName):
494 self.config.set_key('io_dir', os.path.dirname(fileName), True)
497 def getSaveFileName(self, title, filename, filter = ""):
498 directory = self.config.get('io_dir', os.path.expanduser('~'))
499 path = os.path.join( directory, filename )
500 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
501 if fileName and directory != os.path.dirname(fileName):
502 self.config.set_key('io_dir', os.path.dirname(fileName), True)
506 QMainWindow.close(self)
507 run_hook('close_main_window')
509 def connect_slots(self, sender):
510 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
511 self.previous_payto_e=''
513 def timer_actions(self):
514 if self.need_update.is_set():
516 self.need_update.clear()
517 run_hook('timer_actions')
519 def format_amount(self, x, is_diff=False, whitespaces=False):
520 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
522 def read_amount(self, x):
523 if x in['.', '']: return None
524 p = pow(10, self.decimal_point)
525 return int( p * Decimal(x) )
528 assert self.decimal_point in [5,8]
529 return "BTC" if self.decimal_point == 8 else "mBTC"
532 def update_status(self):
533 if self.network is None or not self.network.is_running():
535 icon = QIcon(":icons/status_disconnected.png")
537 elif self.network.is_connected():
538 if not self.wallet.up_to_date:
539 text = _("Synchronizing...")
540 icon = QIcon(":icons/status_waiting.png")
541 elif self.network.server_lag > 1:
542 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
543 icon = QIcon(":icons/status_lagging.png")
545 c, u = self.wallet.get_account_balance(self.current_account)
546 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
547 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
550 run_hook('set_quote_text', c+u, r)
553 text += " (%s)"%quote
555 self.tray.setToolTip(text)
556 icon = QIcon(":icons/status_connected.png")
558 text = _("Not connected")
559 icon = QIcon(":icons/status_disconnected.png")
561 self.balance_label.setText(text)
562 self.status_button.setIcon( icon )
565 def update_wallet(self):
567 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
568 self.update_history_tab()
569 self.update_receive_tab()
570 self.update_contacts_tab()
571 self.update_completions()
574 def create_history_tab(self):
575 self.history_list = l = MyTreeWidget(self)
577 for i,width in enumerate(self.column_widths['history']):
578 l.setColumnWidth(i, width)
579 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
580 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
581 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
583 l.customContextMenuRequested.connect(self.create_history_menu)
587 def create_history_menu(self, position):
588 self.history_list.selectedIndexes()
589 item = self.history_list.currentItem()
591 tx_hash = str(item.data(0, Qt.UserRole).toString())
592 if not tx_hash: return
594 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
595 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
596 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
597 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
600 def show_transaction(self, tx):
601 import transaction_dialog
602 d = transaction_dialog.TxDialog(tx, self)
605 def tx_label_clicked(self, item, column):
606 if column==2 and item.isSelected():
608 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
609 self.history_list.editItem( item, column )
610 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
613 def tx_label_changed(self, item, column):
617 tx_hash = str(item.data(0, Qt.UserRole).toString())
618 tx = self.wallet.transactions.get(tx_hash)
619 text = unicode( item.text(2) )
620 self.wallet.set_label(tx_hash, text)
622 item.setForeground(2, QBrush(QColor('black')))
624 text = self.wallet.get_default_label(tx_hash)
625 item.setText(2, text)
626 item.setForeground(2, QBrush(QColor('gray')))
630 def edit_label(self, is_recv):
631 l = self.receive_list if is_recv else self.contacts_list
632 item = l.currentItem()
633 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
634 l.editItem( item, 1 )
635 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
639 def address_label_clicked(self, item, column, l, column_addr, column_label):
640 if column == column_label and item.isSelected():
641 is_editable = item.data(0, 32).toBool()
644 addr = unicode( item.text(column_addr) )
645 label = unicode( item.text(column_label) )
646 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
647 l.editItem( item, column )
648 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
651 def address_label_changed(self, item, column, l, column_addr, column_label):
652 if column == column_label:
653 addr = unicode( item.text(column_addr) )
654 text = unicode( item.text(column_label) )
655 is_editable = item.data(0, 32).toBool()
659 changed = self.wallet.set_label(addr, text)
661 self.update_history_tab()
662 self.update_completions()
664 self.current_item_changed(item)
666 run_hook('item_changed', item, column)
669 def current_item_changed(self, a):
670 run_hook('current_item_changed', a)
674 def update_history_tab(self):
676 self.history_list.clear()
677 for item in self.wallet.get_tx_history(self.current_account):
678 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
679 time_str = _("unknown")
682 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
684 time_str = _("error")
687 time_str = 'unverified'
688 icon = QIcon(":icons/unconfirmed.png")
691 icon = QIcon(":icons/unconfirmed.png")
693 icon = QIcon(":icons/clock%d.png"%conf)
695 icon = QIcon(":icons/confirmed.png")
697 if value is not None:
698 v_str = self.format_amount(value, True, whitespaces=True)
702 balance_str = self.format_amount(balance, whitespaces=True)
705 label, is_default_label = self.wallet.get_label(tx_hash)
707 label = _('Pruned transaction outputs')
708 is_default_label = False
710 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
711 item.setFont(2, QFont(MONOSPACE_FONT))
712 item.setFont(3, QFont(MONOSPACE_FONT))
713 item.setFont(4, QFont(MONOSPACE_FONT))
715 item.setForeground(3, QBrush(QColor("#BC1E1E")))
717 item.setData(0, Qt.UserRole, tx_hash)
718 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
720 item.setForeground(2, QBrush(QColor('grey')))
722 item.setIcon(0, icon)
723 self.history_list.insertTopLevelItem(0,item)
726 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
729 def create_send_tab(self):
734 grid.setColumnMinimumWidth(3,300)
735 grid.setColumnStretch(5,1)
738 self.payto_e = QLineEdit()
739 grid.addWidget(QLabel(_('Pay to')), 1, 0)
740 grid.addWidget(self.payto_e, 1, 1, 1, 3)
742 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)
744 completer = QCompleter()
745 completer.setCaseSensitivity(False)
746 self.payto_e.setCompleter(completer)
747 completer.setModel(self.completions)
749 self.message_e = QLineEdit()
750 grid.addWidget(QLabel(_('Description')), 2, 0)
751 grid.addWidget(self.message_e, 2, 1, 1, 3)
752 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)
754 self.from_label = QLabel(_('From'))
755 grid.addWidget(self.from_label, 3, 0)
756 self.from_list = QTreeWidget(self)
757 self.from_list.setColumnCount(2)
758 self.from_list.setColumnWidth(0, 350)
759 self.from_list.setColumnWidth(1, 50)
760 self.from_list.setHeaderHidden (True)
761 self.from_list.setMaximumHeight(80)
762 grid.addWidget(self.from_list, 3, 1, 1, 3)
763 self.set_pay_from([])
765 self.amount_e = AmountEdit(self.base_unit)
766 grid.addWidget(QLabel(_('Amount')), 4, 0)
767 grid.addWidget(self.amount_e, 4, 1, 1, 2)
768 grid.addWidget(HelpButton(
769 _('Amount to be sent.') + '\n\n' \
770 + _('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.') \
771 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
773 self.fee_e = AmountEdit(self.base_unit)
774 grid.addWidget(QLabel(_('Fee')), 5, 0)
775 grid.addWidget(self.fee_e, 5, 1, 1, 2)
776 grid.addWidget(HelpButton(
777 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
778 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
779 + _('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)
782 self.send_button = EnterButton(_("Send"), self.do_send)
783 grid.addWidget(self.send_button, 6, 1)
785 b = EnterButton(_("Clear"),self.do_clear)
786 grid.addWidget(b, 6, 2)
788 self.payto_sig = QLabel('')
789 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
791 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
792 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
801 def entry_changed( is_fee ):
802 self.funds_error = False
804 if self.amount_e.is_shortcut:
805 self.amount_e.is_shortcut = False
806 sendable = self.get_sendable_balance()
807 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, self.get_payment_sources())
808 fee = self.wallet.estimated_fee(inputs)
810 self.amount_e.setText( self.format_amount(amount) )
811 self.fee_e.setText( self.format_amount( fee ) )
814 amount = self.read_amount(str(self.amount_e.text()))
815 fee = self.read_amount(str(self.fee_e.text()))
817 if not is_fee: fee = None
820 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, self.get_payment_sources())
822 self.fee_e.setText( self.format_amount( fee ) )
825 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
829 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
830 self.funds_error = True
831 text = _( "Not enough funds" )
832 c, u = self.wallet.get_frozen_balance()
833 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
835 self.statusBar().showMessage(text)
836 self.amount_e.setPalette(palette)
837 self.fee_e.setPalette(palette)
839 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
840 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
842 run_hook('create_send_tab', grid)
846 def set_pay_from(self, l):
848 self.from_list.clear()
849 self.from_label.setHidden(len(self.pay_from) == 0)
850 self.from_list.setHidden(len(self.pay_from) == 0)
851 for addr in self.pay_from:
852 c, u = self.wallet.get_addr_balance(addr)
853 balance = self.format_amount(c + u)
854 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
857 def update_completions(self):
859 for addr,label in self.wallet.labels.items():
860 if addr in self.wallet.addressbook:
861 l.append( label + ' <' + addr + '>')
863 run_hook('update_completions', l)
864 self.completions.setStringList(l)
868 return lambda s, *args: s.do_protect(func, args)
873 label = unicode( self.message_e.text() )
874 r = unicode( self.payto_e.text() )
877 # label or alias, with address in brackets
878 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
879 to_address = m.group(2) if m else r
881 if not is_valid(to_address):
882 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
886 amount = self.read_amount(unicode( self.amount_e.text()))
888 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
891 fee = self.read_amount(unicode( self.fee_e.text()))
893 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
896 confirm_amount = self.config.get('confirm_amount', 100000000)
897 if amount >= confirm_amount:
898 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
901 confirm_fee = self.config.get('confirm_fee', 100000)
902 if fee >= confirm_fee:
903 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()}):
906 self.send_tx(to_address, amount, fee, label)
910 def send_tx(self, to_address, amount, fee, label, password):
912 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
913 domain=self.get_payment_sources())
914 except Exception as e:
915 traceback.print_exc(file=sys.stdout)
916 self.show_message(str(e))
919 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
920 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
924 self.wallet.set_label(tx.hash(), label)
927 h = self.wallet.send_tx(tx)
928 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
929 status, msg = self.wallet.receive_tx( h, tx )
931 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
933 self.update_contacts_tab()
935 QMessageBox.warning(self, _('Error'), msg, _('OK'))
938 self.show_transaction(tx)
940 # add recipient to addressbook
941 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
942 self.wallet.addressbook.append(to_address)
947 def set_url(self, url):
948 address, amount, label, message, signature, identity, url = util.parse_url(url)
951 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
952 elif amount: amount = str(Decimal(amount))
955 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
958 self.mini.set_payment_fields(address, amount)
960 if label and self.wallet.labels.get(address) != label:
961 if self.question('Give label "%s" to address %s ?'%(label,address)):
962 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
963 self.wallet.addressbook.append(address)
964 self.wallet.set_label(address, label)
966 run_hook('set_url', url, self.show_message, self.question)
968 self.tabs.setCurrentIndex(1)
969 label = self.wallet.labels.get(address)
970 m_addr = label + ' <'+ address +'>' if label else address
971 self.payto_e.setText(m_addr)
973 self.message_e.setText(message)
975 self.amount_e.setText(amount)
978 self.set_frozen(self.payto_e,True)
979 self.set_frozen(self.amount_e,True)
980 self.set_frozen(self.message_e,True)
981 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
983 self.payto_sig.setVisible(False)
986 self.payto_sig.setVisible(False)
987 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
989 self.set_frozen(e,False)
991 self.set_pay_from([])
994 def set_frozen(self,entry,frozen):
996 entry.setReadOnly(True)
997 entry.setFrame(False)
999 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1000 entry.setPalette(palette)
1002 entry.setReadOnly(False)
1003 entry.setFrame(True)
1004 palette = QPalette()
1005 palette.setColor(entry.backgroundRole(), QColor('white'))
1006 entry.setPalette(palette)
1009 def set_addrs_frozen(self,addrs,freeze):
1011 if not addr: continue
1012 if addr in self.wallet.frozen_addresses and not freeze:
1013 self.wallet.unfreeze(addr)
1014 elif addr not in self.wallet.frozen_addresses and freeze:
1015 self.wallet.freeze(addr)
1016 self.update_receive_tab()
1020 def create_list_tab(self, headers):
1021 "generic tab creation method"
1022 l = MyTreeWidget(self)
1023 l.setColumnCount( len(headers) )
1024 l.setHeaderLabels( headers )
1027 vbox = QVBoxLayout()
1034 vbox.addWidget(buttons)
1036 hbox = QHBoxLayout()
1039 buttons.setLayout(hbox)
1044 def create_receive_tab(self):
1045 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1046 l.setContextMenuPolicy(Qt.CustomContextMenu)
1047 l.customContextMenuRequested.connect(self.create_receive_menu)
1048 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1049 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1050 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1051 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1052 self.receive_list = l
1053 self.receive_buttons_hbox = hbox
1060 def save_column_widths(self):
1061 self.column_widths["receive"] = []
1062 for i in range(self.receive_list.columnCount() -1):
1063 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1065 self.column_widths["history"] = []
1066 for i in range(self.history_list.columnCount() - 1):
1067 self.column_widths["history"].append(self.history_list.columnWidth(i))
1069 self.column_widths["contacts"] = []
1070 for i in range(self.contacts_list.columnCount() - 1):
1071 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1073 self.config.set_key("column_widths_2", self.column_widths, True)
1076 def create_contacts_tab(self):
1077 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1078 l.setContextMenuPolicy(Qt.CustomContextMenu)
1079 l.customContextMenuRequested.connect(self.create_contact_menu)
1080 for i,width in enumerate(self.column_widths['contacts']):
1081 l.setColumnWidth(i, width)
1083 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1084 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1085 self.contacts_list = l
1086 self.contacts_buttons_hbox = hbox
1091 def delete_imported_key(self, addr):
1092 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1093 self.wallet.delete_imported_key(addr)
1094 self.update_receive_tab()
1095 self.update_history_tab()
1097 def edit_account_label(self, k):
1098 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1100 label = unicode(text)
1101 self.wallet.set_label(k,label)
1102 self.update_receive_tab()
1104 def account_set_expanded(self, item, k, b):
1106 self.accounts_expanded[k] = b
1108 def create_account_menu(self, position, k, item):
1110 if item.isExpanded():
1111 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1113 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1114 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1115 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1116 if self.wallet.account_is_pending(k):
1117 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1118 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1120 def delete_pending_account(self, k):
1121 self.wallet.delete_pending_account(k)
1122 self.update_receive_tab()
1124 def create_receive_menu(self, position):
1125 # fixme: this function apparently has a side effect.
1126 # if it is not called the menu pops up several times
1127 #self.receive_list.selectedIndexes()
1129 selected = self.receive_list.selectedItems()
1130 multi_select = len(selected) > 1
1131 addrs = [unicode(item.text(0)) for item in selected]
1132 if not multi_select:
1133 item = self.receive_list.itemAt(position)
1137 if not is_valid(addr):
1138 k = str(item.data(0,32).toString())
1140 self.create_account_menu(position, k, item)
1142 item.setExpanded(not item.isExpanded())
1146 if not multi_select:
1147 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1148 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1149 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1150 if self.wallet.seed:
1151 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1152 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1153 if addr in self.wallet.imported_keys:
1154 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1156 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1157 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1158 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1159 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1161 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1162 menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1164 run_hook('receive_menu', menu, addrs)
1165 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1168 def get_sendable_balance(self):
1169 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1172 def get_payment_sources(self):
1174 return self.pay_from
1176 return self.wallet.get_account_addresses(self.current_account)
1179 def send_from_addresses(self, addrs):
1180 self.set_pay_from( addrs )
1181 self.tabs.setCurrentIndex(1)
1184 def payto(self, addr):
1186 label = self.wallet.labels.get(addr)
1187 m_addr = label + ' <' + addr + '>' if label else addr
1188 self.tabs.setCurrentIndex(1)
1189 self.payto_e.setText(m_addr)
1190 self.amount_e.setFocus()
1193 def delete_contact(self, x):
1194 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1195 self.wallet.delete_contact(x)
1196 self.wallet.set_label(x, None)
1197 self.update_history_tab()
1198 self.update_contacts_tab()
1199 self.update_completions()
1202 def create_contact_menu(self, position):
1203 item = self.contacts_list.itemAt(position)
1206 menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1208 addr = unicode(item.text(0))
1209 label = unicode(item.text(1))
1210 is_editable = item.data(0,32).toBool()
1211 payto_addr = item.data(0,33).toString()
1212 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1213 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1214 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1216 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1217 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1219 run_hook('create_contact_menu', menu, item)
1220 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1223 def update_receive_item(self, item):
1224 item.setFont(0, QFont(MONOSPACE_FONT))
1225 address = str(item.data(0,0).toString())
1226 label = self.wallet.labels.get(address,'')
1227 item.setData(1,0,label)
1228 item.setData(0,32, True) # is editable
1230 run_hook('update_receive_item', address, item)
1232 if not self.wallet.is_mine(address): return
1234 c, u = self.wallet.get_addr_balance(address)
1235 balance = self.format_amount(c + u)
1236 item.setData(2,0,balance)
1238 if address in self.wallet.frozen_addresses:
1239 item.setBackgroundColor(0, QColor('lightblue'))
1242 def update_receive_tab(self):
1243 l = self.receive_list
1246 l.setColumnHidden(2, False)
1247 l.setColumnHidden(3, False)
1248 for i,width in enumerate(self.column_widths['receive']):
1249 l.setColumnWidth(i, width)
1251 if self.current_account is None:
1252 account_items = self.wallet.accounts.items()
1253 elif self.current_account != -1:
1254 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1258 for k, account in account_items:
1259 name = self.wallet.get_account_name(k)
1260 c,u = self.wallet.get_account_balance(k)
1261 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1262 l.addTopLevelItem(account_item)
1263 account_item.setExpanded(self.accounts_expanded.get(k, True))
1264 account_item.setData(0, 32, k)
1266 if not self.wallet.is_seeded(k):
1267 icon = QIcon(":icons/key.png")
1268 account_item.setIcon(0, icon)
1270 for is_change in ([0,1]):
1271 name = _("Receiving") if not is_change else _("Change")
1272 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1273 account_item.addChild(seq_item)
1274 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1276 if not is_change: seq_item.setExpanded(True)
1281 for address in account.get_addresses(is_change):
1282 h = self.wallet.history.get(address,[])
1286 if gap > self.wallet.gap_limit:
1291 c, u = self.wallet.get_addr_balance(address)
1292 num_tx = '*' if h == ['*'] else "%d"%len(h)
1293 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1294 self.update_receive_item(item)
1296 item.setBackgroundColor(1, QColor('red'))
1297 if len(h) > 0 and c == -u:
1299 seq_item.insertChild(0,used_item)
1301 used_item.addChild(item)
1303 seq_item.addChild(item)
1306 for k, addr in self.wallet.get_pending_accounts():
1307 name = self.wallet.labels.get(k,'')
1308 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1309 self.update_receive_item(item)
1310 l.addTopLevelItem(account_item)
1311 account_item.setExpanded(True)
1312 account_item.setData(0, 32, k)
1313 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1314 account_item.addChild(item)
1315 self.update_receive_item(item)
1318 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1319 c,u = self.wallet.get_imported_balance()
1320 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1321 l.addTopLevelItem(account_item)
1322 account_item.setExpanded(True)
1323 for address in self.wallet.imported_keys.keys():
1324 item = QTreeWidgetItem( [ address, '', '', ''] )
1325 self.update_receive_item(item)
1326 account_item.addChild(item)
1329 # we use column 1 because column 0 may be hidden
1330 l.setCurrentItem(l.topLevelItem(0),1)
1333 def update_contacts_tab(self):
1334 l = self.contacts_list
1337 for address in self.wallet.addressbook:
1338 label = self.wallet.labels.get(address,'')
1339 n = self.wallet.get_num_tx(address)
1340 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1341 item.setFont(0, QFont(MONOSPACE_FONT))
1342 # 32 = label can be edited (bool)
1343 item.setData(0,32, True)
1345 item.setData(0,33, address)
1346 l.addTopLevelItem(item)
1348 run_hook('update_contacts_tab', l)
1349 l.setCurrentItem(l.topLevelItem(0))
1353 def create_console_tab(self):
1354 from console import Console
1355 self.console = console = Console()
1359 def update_console(self):
1360 console = self.console
1361 console.history = self.config.get("console-history",[])
1362 console.history_index = len(console.history)
1364 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1365 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1367 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1369 def mkfunc(f, method):
1370 return lambda *args: apply( f, (method, args, self.password_dialog ))
1372 if m[0]=='_' or m in ['network','wallet']: continue
1373 methods[m] = mkfunc(c._run, m)
1375 console.updateNamespace(methods)
1378 def change_account(self,s):
1379 if s == _("All accounts"):
1380 self.current_account = None
1382 accounts = self.wallet.get_account_names()
1383 for k, v in accounts.items():
1385 self.current_account = k
1386 self.update_history_tab()
1387 self.update_status()
1388 self.update_receive_tab()
1390 def create_status_bar(self):
1393 sb.setFixedHeight(35)
1394 qtVersion = qVersion()
1396 self.balance_label = QLabel("")
1397 sb.addWidget(self.balance_label)
1399 from version_getter import UpdateLabel
1400 self.updatelabel = UpdateLabel(self.config, sb)
1402 self.account_selector = QComboBox()
1403 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1404 sb.addPermanentWidget(self.account_selector)
1406 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1407 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1409 self.lock_icon = QIcon()
1410 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1411 sb.addPermanentWidget( self.password_button )
1413 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1414 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1415 sb.addPermanentWidget( self.seed_button )
1416 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1417 sb.addPermanentWidget( self.status_button )
1419 run_hook('create_status_bar', (sb,))
1421 self.setStatusBar(sb)
1424 def update_lock_icon(self):
1425 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1426 self.password_button.setIcon( icon )
1429 def update_buttons_on_seed(self):
1430 if not self.wallet.is_watching_only():
1431 self.seed_button.show()
1432 self.password_button.show()
1433 self.send_button.setText(_("Send"))
1435 self.password_button.hide()
1436 self.seed_button.hide()
1437 self.send_button.setText(_("Create unsigned transaction"))
1440 def change_password_dialog(self):
1441 from password_dialog import PasswordDialog
1442 d = PasswordDialog(self.wallet, self)
1444 self.update_lock_icon()
1447 def new_contact_dialog(self):
1450 vbox = QVBoxLayout(d)
1451 vbox.addWidget(QLabel(_('New Contact')+':'))
1453 grid = QGridLayout()
1456 grid.addWidget(QLabel(_("Address")), 1, 0)
1457 grid.addWidget(line1, 1, 1)
1458 grid.addWidget(QLabel(_("Name")), 2, 0)
1459 grid.addWidget(line2, 2, 1)
1461 vbox.addLayout(grid)
1462 vbox.addLayout(ok_cancel_buttons(d))
1467 address = str(line1.text())
1468 label = unicode(line2.text())
1470 if not is_valid(address):
1471 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1474 self.wallet.add_contact(address)
1476 self.wallet.set_label(address, label)
1478 self.update_contacts_tab()
1479 self.update_history_tab()
1480 self.update_completions()
1481 self.tabs.setCurrentIndex(3)
1484 def new_account_dialog(self):
1486 dialog = QDialog(self)
1488 dialog.setWindowTitle(_("New Account"))
1490 vbox = QVBoxLayout()
1491 vbox.addWidget(QLabel(_('Account name')+':'))
1494 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1495 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1500 vbox.addLayout(ok_cancel_buttons(dialog))
1501 dialog.setLayout(vbox)
1505 name = str(e.text())
1508 self.wallet.create_pending_account('1', name)
1509 self.update_receive_tab()
1510 self.tabs.setCurrentIndex(2)
1514 def show_master_public_key_old(self):
1515 dialog = QDialog(self)
1517 dialog.setWindowTitle(_("Master Public Key"))
1519 main_text = QTextEdit()
1520 main_text.setText(self.wallet.get_master_public_key())
1521 main_text.setReadOnly(True)
1522 main_text.setMaximumHeight(170)
1523 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1525 ok_button = QPushButton(_("OK"))
1526 ok_button.setDefault(True)
1527 ok_button.clicked.connect(dialog.accept)
1529 main_layout = QGridLayout()
1530 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1532 main_layout.addWidget(main_text, 1, 0)
1533 main_layout.addWidget(qrw, 1, 1 )
1535 vbox = QVBoxLayout()
1536 vbox.addLayout(main_layout)
1537 vbox.addLayout(close_button(dialog))
1538 dialog.setLayout(vbox)
1542 def show_master_public_key(self):
1544 if self.wallet.seed_version == 4:
1545 self.show_master_public_key_old()
1548 dialog = QDialog(self)
1550 dialog.setWindowTitle(_("Master Public Keys"))
1552 chain_text = QTextEdit()
1553 chain_text.setReadOnly(True)
1554 chain_text.setMaximumHeight(170)
1555 chain_qrw = QRCodeWidget()
1557 mpk_text = QTextEdit()
1558 mpk_text.setReadOnly(True)
1559 mpk_text.setMaximumHeight(170)
1560 mpk_qrw = QRCodeWidget()
1562 main_layout = QGridLayout()
1564 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1565 main_layout.addWidget(mpk_text, 1, 1)
1566 main_layout.addWidget(mpk_qrw, 1, 2)
1568 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1569 main_layout.addWidget(chain_text, 2, 1)
1570 main_layout.addWidget(chain_qrw, 2, 2)
1573 c, K, cK = self.wallet.master_public_keys[str(key)]
1574 chain_text.setText(c)
1575 chain_qrw.set_addr(c)
1576 chain_qrw.update_qr()
1581 key_selector = QComboBox()
1582 keys = sorted(self.wallet.master_public_keys.keys())
1583 key_selector.addItems(keys)
1585 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1586 main_layout.addWidget(key_selector, 0, 1)
1587 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1591 vbox = QVBoxLayout()
1592 vbox.addLayout(main_layout)
1593 vbox.addLayout(close_button(dialog))
1595 dialog.setLayout(vbox)
1600 def show_seed_dialog(self, password):
1601 if self.wallet.is_watching_only():
1602 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1605 if self.wallet.seed:
1607 mnemonic = self.wallet.get_mnemonic(password)
1609 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1611 from seed_dialog import SeedDialog
1612 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1616 for k in self.wallet.master_private_keys.keys():
1617 pk = self.wallet.get_master_private_key(k, password)
1619 from seed_dialog import PrivateKeysDialog
1620 d = PrivateKeysDialog(self,l)
1627 def show_qrcode(self, data, title = _("QR code")):
1631 d.setWindowTitle(title)
1632 d.setMinimumSize(270, 300)
1633 vbox = QVBoxLayout()
1634 qrw = QRCodeWidget(data)
1635 vbox.addWidget(qrw, 1)
1636 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1637 hbox = QHBoxLayout()
1640 filename = os.path.join(self.config.path, "qrcode.bmp")
1643 bmp.save_qrcode(qrw.qr, filename)
1644 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1646 def copy_to_clipboard():
1647 bmp.save_qrcode(qrw.qr, filename)
1648 self.app.clipboard().setImage(QImage(filename))
1649 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1651 b = QPushButton(_("Copy"))
1653 b.clicked.connect(copy_to_clipboard)
1655 b = QPushButton(_("Save"))
1657 b.clicked.connect(print_qr)
1659 b = QPushButton(_("Close"))
1661 b.clicked.connect(d.accept)
1664 vbox.addLayout(hbox)
1669 def do_protect(self, func, args):
1670 if self.wallet.use_encryption:
1671 password = self.password_dialog()
1677 if args != (False,):
1678 args = (self,) + args + (password,)
1680 args = (self,password)
1685 def show_private_key(self, address, password):
1686 if not address: return
1688 pk_list = self.wallet.get_private_key(address, password)
1689 except Exception as e:
1690 self.show_message(str(e))
1694 d.setMinimumSize(600, 200)
1696 vbox = QVBoxLayout()
1697 vbox.addWidget( QLabel(_("Address") + ': ' + address))
1698 vbox.addWidget( QLabel(_("Private key") + ':'))
1700 keys.setReadOnly(True)
1701 keys.setText('\n'.join(pk_list))
1702 vbox.addWidget(keys)
1703 vbox.addWidget( QRCodeWidget('\n'.join(pk_list)) )
1704 vbox.addLayout(close_button(d))
1710 def do_sign(self, address, message, signature, password):
1711 message = unicode(message.toPlainText())
1712 message = message.encode('utf-8')
1714 sig = self.wallet.sign_message(str(address.text()), message, password)
1715 signature.setText(sig)
1716 except Exception as e:
1717 self.show_message(str(e))
1719 def sign_message(self, address):
1720 if not address: return
1723 d.setWindowTitle(_('Sign Message'))
1724 d.setMinimumSize(410, 290)
1726 tab_widget = QTabWidget()
1728 layout = QGridLayout(tab)
1730 sign_address = QLineEdit()
1732 sign_address.setText(address)
1733 layout.addWidget(QLabel(_('Address')), 1, 0)
1734 layout.addWidget(sign_address, 1, 1)
1736 sign_message = QTextEdit()
1737 layout.addWidget(QLabel(_('Message')), 2, 0)
1738 layout.addWidget(sign_message, 2, 1)
1739 layout.setRowStretch(2,3)
1741 sign_signature = QTextEdit()
1742 layout.addWidget(QLabel(_('Signature')), 3, 0)
1743 layout.addWidget(sign_signature, 3, 1)
1744 layout.setRowStretch(3,1)
1747 hbox = QHBoxLayout()
1748 b = QPushButton(_("Sign"))
1750 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1751 b = QPushButton(_("Close"))
1752 b.clicked.connect(d.accept)
1754 layout.addLayout(hbox, 4, 1)
1755 tab_widget.addTab(tab, _("Sign"))
1759 layout = QGridLayout(tab)
1761 verify_address = QLineEdit()
1762 layout.addWidget(QLabel(_('Address')), 1, 0)
1763 layout.addWidget(verify_address, 1, 1)
1765 verify_message = QTextEdit()
1766 layout.addWidget(QLabel(_('Message')), 2, 0)
1767 layout.addWidget(verify_message, 2, 1)
1768 layout.setRowStretch(2,3)
1770 verify_signature = QTextEdit()
1771 layout.addWidget(QLabel(_('Signature')), 3, 0)
1772 layout.addWidget(verify_signature, 3, 1)
1773 layout.setRowStretch(3,1)
1776 message = unicode(verify_message.toPlainText())
1777 message = message.encode('utf-8')
1778 if bitcoin.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1779 self.show_message(_("Signature verified"))
1781 self.show_message(_("Error: wrong signature"))
1783 hbox = QHBoxLayout()
1784 b = QPushButton(_("Verify"))
1785 b.clicked.connect(do_verify)
1787 b = QPushButton(_("Close"))
1788 b.clicked.connect(d.accept)
1790 layout.addLayout(hbox, 4, 1)
1791 tab_widget.addTab(tab, _("Verify"))
1793 vbox = QVBoxLayout()
1794 vbox.addWidget(tab_widget)
1801 def question(self, msg):
1802 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1804 def show_message(self, msg):
1805 QMessageBox.information(self, _('Message'), msg, _('OK'))
1807 def password_dialog(self ):
1814 vbox = QVBoxLayout()
1815 msg = _('Please enter your password')
1816 vbox.addWidget(QLabel(msg))
1818 grid = QGridLayout()
1820 grid.addWidget(QLabel(_('Password')), 1, 0)
1821 grid.addWidget(pw, 1, 1)
1822 vbox.addLayout(grid)
1824 vbox.addLayout(ok_cancel_buttons(d))
1827 run_hook('password_dialog', pw, grid, 1)
1828 if not d.exec_(): return
1829 return unicode(pw.text())
1838 def tx_from_text(self, txt):
1839 "json or raw hexadecimal"
1842 tx = Transaction(txt)
1848 tx_dict = json.loads(str(txt))
1849 assert "hex" in tx_dict.keys()
1850 assert "complete" in tx_dict.keys()
1851 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1852 if not tx_dict["complete"]:
1853 assert "input_info" in tx_dict.keys()
1854 input_info = json.loads(tx_dict['input_info'])
1855 tx.add_input_info(input_info)
1860 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1864 def read_tx_from_file(self):
1865 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1869 with open(fileName, "r") as f:
1870 file_content = f.read()
1871 except (ValueError, IOError, os.error), reason:
1872 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1874 return self.tx_from_text(file_content)
1878 def sign_raw_transaction(self, tx, input_info, password):
1879 self.wallet.signrawtransaction(tx, input_info, [], password)
1881 def do_process_from_text(self):
1882 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1885 tx = self.tx_from_text(text)
1887 self.show_transaction(tx)
1889 def do_process_from_file(self):
1890 tx = self.read_tx_from_file()
1892 self.show_transaction(tx)
1894 def do_process_from_txid(self):
1895 from electrum import transaction
1896 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1898 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1900 tx = transaction.Transaction(r)
1902 self.show_transaction(tx)
1904 self.show_message("unknown transaction")
1906 def do_process_from_csvReader(self, csvReader):
1911 for position, row in enumerate(csvReader):
1913 if not is_valid(address):
1914 errors.append((position, address))
1916 amount = Decimal(row[1])
1917 amount = int(100000000*amount)
1918 outputs.append((address, amount))
1919 except (ValueError, IOError, os.error), reason:
1920 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1924 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1925 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1929 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1930 except Exception as e:
1931 self.show_message(str(e))
1934 self.show_transaction(tx)
1936 def do_process_from_csv_file(self):
1937 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1941 with open(fileName, "r") as f:
1942 csvReader = csv.reader(f)
1943 self.do_process_from_csvReader(csvReader)
1944 except (ValueError, IOError, os.error), reason:
1945 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1948 def do_process_from_csv_text(self):
1949 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1950 + _("Format: address, amount. One output per line"), _("Load CSV"))
1953 f = StringIO.StringIO(text)
1954 csvReader = csv.reader(f)
1955 self.do_process_from_csvReader(csvReader)
1960 def do_export_privkeys(self, password):
1961 if not self.wallet.seed:
1962 self.show_message(_("This wallet has no seed"))
1965 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.")))
1968 select_export = _('Select file to export your private keys to')
1969 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1971 with open(fileName, "w+") as csvfile:
1972 transaction = csv.writer(csvfile)
1973 transaction.writerow(["address", "private_key"])
1975 addresses = self.wallet.addresses(True)
1977 for addr in addresses:
1978 pk = "".join(self.wallet.get_private_key(addr, password))
1979 transaction.writerow(["%34s"%addr,pk])
1981 self.show_message(_("Private keys exported."))
1983 except (IOError, os.error), reason:
1984 export_error_label = _("Electrum was unable to produce a private key-export.")
1985 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1987 except Exception as e:
1988 self.show_message(str(e))
1992 def do_import_labels(self):
1993 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1994 if not labelsFile: return
1996 f = open(labelsFile, 'r')
1999 for key, value in json.loads(data).items():
2000 self.wallet.set_label(key, value)
2001 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2002 except (IOError, os.error), reason:
2003 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2006 def do_export_labels(self):
2007 labels = self.wallet.labels
2009 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2011 with open(fileName, 'w+') as f:
2012 json.dump(labels, f)
2013 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2014 except (IOError, os.error), reason:
2015 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2018 def do_export_history(self):
2019 from lite_window import csv_transaction
2020 csv_transaction(self.wallet)
2024 def do_import_privkey(self, password):
2025 if not self.wallet.imported_keys:
2026 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2027 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2028 + _('Are you sure you understand what you are doing?'), 3, 4)
2031 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2034 text = str(text).split()
2039 addr = self.wallet.import_key(key, password)
2040 except Exception as e:
2046 addrlist.append(addr)
2048 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2050 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2051 self.update_receive_tab()
2052 self.update_history_tab()
2055 def settings_dialog(self):
2057 d.setWindowTitle(_('Electrum Settings'))
2059 vbox = QVBoxLayout()
2060 grid = QGridLayout()
2061 grid.setColumnStretch(0,1)
2063 nz_label = QLabel(_('Display zeros') + ':')
2064 grid.addWidget(nz_label, 0, 0)
2065 nz_e = AmountEdit(None,True)
2066 nz_e.setText("%d"% self.num_zeros)
2067 grid.addWidget(nz_e, 0, 1)
2068 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2069 grid.addWidget(HelpButton(msg), 0, 2)
2070 if not self.config.is_modifiable('num_zeros'):
2071 for w in [nz_e, nz_label]: w.setEnabled(False)
2073 lang_label=QLabel(_('Language') + ':')
2074 grid.addWidget(lang_label, 1, 0)
2075 lang_combo = QComboBox()
2076 from electrum.i18n import languages
2077 lang_combo.addItems(languages.values())
2079 index = languages.keys().index(self.config.get("language",''))
2082 lang_combo.setCurrentIndex(index)
2083 grid.addWidget(lang_combo, 1, 1)
2084 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2085 if not self.config.is_modifiable('language'):
2086 for w in [lang_combo, lang_label]: w.setEnabled(False)
2089 fee_label = QLabel(_('Transaction fee') + ':')
2090 grid.addWidget(fee_label, 2, 0)
2091 fee_e = AmountEdit(self.base_unit)
2092 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2093 grid.addWidget(fee_e, 2, 1)
2094 msg = _('Fee per kilobyte of transaction.') + ' ' \
2095 + _('Recommended value') + ': ' + self.format_amount(20000)
2096 grid.addWidget(HelpButton(msg), 2, 2)
2097 if not self.config.is_modifiable('fee_per_kb'):
2098 for w in [fee_e, fee_label]: w.setEnabled(False)
2100 units = ['BTC', 'mBTC']
2101 unit_label = QLabel(_('Base unit') + ':')
2102 grid.addWidget(unit_label, 3, 0)
2103 unit_combo = QComboBox()
2104 unit_combo.addItems(units)
2105 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2106 grid.addWidget(unit_combo, 3, 1)
2107 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2108 + '\n1BTC=1000mBTC.\n' \
2109 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2111 usechange_cb = QCheckBox(_('Use change addresses'))
2112 usechange_cb.setChecked(self.wallet.use_change)
2113 grid.addWidget(usechange_cb, 4, 0)
2114 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2115 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2117 grid.setRowStretch(5,1)
2119 vbox.addLayout(grid)
2120 vbox.addLayout(ok_cancel_buttons(d))
2124 if not d.exec_(): return
2126 fee = unicode(fee_e.text())
2128 fee = self.read_amount(fee)
2130 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2133 self.wallet.set_fee(fee)
2135 nz = unicode(nz_e.text())
2140 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2143 if self.num_zeros != nz:
2145 self.config.set_key('num_zeros', nz, True)
2146 self.update_history_tab()
2147 self.update_receive_tab()
2149 usechange_result = usechange_cb.isChecked()
2150 if self.wallet.use_change != usechange_result:
2151 self.wallet.use_change = usechange_result
2152 self.wallet.storage.put('use_change', self.wallet.use_change)
2154 unit_result = units[unit_combo.currentIndex()]
2155 if self.base_unit() != unit_result:
2156 self.decimal_point = 8 if unit_result == 'BTC' else 5
2157 self.config.set_key('decimal_point', self.decimal_point, True)
2158 self.update_history_tab()
2159 self.update_status()
2161 need_restart = False
2163 lang_request = languages.keys()[lang_combo.currentIndex()]
2164 if lang_request != self.config.get('language'):
2165 self.config.set_key("language", lang_request, True)
2168 run_hook('close_settings_dialog')
2171 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2174 def run_network_dialog(self):
2175 if not self.network:
2177 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2179 def closeEvent(self, event):
2182 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2183 self.save_column_widths()
2184 self.config.set_key("console-history", self.console.history[-50:], True)
2185 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2190 def plugins_dialog(self):
2191 from electrum.plugins import plugins
2194 d.setWindowTitle(_('Electrum Plugins'))
2197 vbox = QVBoxLayout(d)
2200 scroll = QScrollArea()
2201 scroll.setEnabled(True)
2202 scroll.setWidgetResizable(True)
2203 scroll.setMinimumSize(400,250)
2204 vbox.addWidget(scroll)
2208 w.setMinimumHeight(len(plugins)*35)
2210 grid = QGridLayout()
2211 grid.setColumnStretch(0,1)
2214 def do_toggle(cb, p, w):
2217 if w: w.setEnabled(r)
2219 def mk_toggle(cb, p, w):
2220 return lambda: do_toggle(cb,p,w)
2222 for i, p in enumerate(plugins):
2224 cb = QCheckBox(p.fullname())
2225 cb.setDisabled(not p.is_available())
2226 cb.setChecked(p.is_enabled())
2227 grid.addWidget(cb, i, 0)
2228 if p.requires_settings():
2229 w = p.settings_widget(self)
2230 w.setEnabled( p.is_enabled() )
2231 grid.addWidget(w, i, 1)
2234 cb.clicked.connect(mk_toggle(cb,p,w))
2235 grid.addWidget(HelpButton(p.description()), i, 2)
2237 print_msg(_("Error: cannot display plugin"), p)
2238 traceback.print_exc(file=sys.stdout)
2239 grid.setRowStretch(i+1,1)
2241 vbox.addLayout(close_button(d))
2246 def show_account_details(self, k):
2248 d.setWindowTitle(_('Account Details'))
2251 vbox = QVBoxLayout(d)
2252 roots = self.wallet.get_roots(k)
2254 name = self.wallet.get_account_name(k)
2255 label = QLabel('Name: ' + name)
2256 vbox.addWidget(label)
2258 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2259 vbox.addWidget(QLabel('Type: ' + acctype))
2261 label = QLabel('Derivation: ' + k)
2262 vbox.addWidget(label)
2265 # mpk = self.wallet.master_public_keys[root]
2266 # text = QTextEdit()
2267 # text.setReadOnly(True)
2268 # text.setMaximumHeight(120)
2269 # text.setText(repr(mpk))
2270 # vbox.addWidget(text)
2272 vbox.addLayout(close_button(d))