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.addLayout(close_button(d))
1709 def do_sign(self, address, message, signature, password):
1710 message = unicode(message.toPlainText())
1711 message = message.encode('utf-8')
1713 sig = self.wallet.sign_message(str(address.text()), message, password)
1714 signature.setText(sig)
1715 except Exception as e:
1716 self.show_message(str(e))
1718 def sign_message(self, address):
1719 if not address: return
1722 d.setWindowTitle(_('Sign Message'))
1723 d.setMinimumSize(410, 290)
1725 tab_widget = QTabWidget()
1727 layout = QGridLayout(tab)
1729 sign_address = QLineEdit()
1731 sign_address.setText(address)
1732 layout.addWidget(QLabel(_('Address')), 1, 0)
1733 layout.addWidget(sign_address, 1, 1)
1735 sign_message = QTextEdit()
1736 layout.addWidget(QLabel(_('Message')), 2, 0)
1737 layout.addWidget(sign_message, 2, 1)
1738 layout.setRowStretch(2,3)
1740 sign_signature = QTextEdit()
1741 layout.addWidget(QLabel(_('Signature')), 3, 0)
1742 layout.addWidget(sign_signature, 3, 1)
1743 layout.setRowStretch(3,1)
1746 hbox = QHBoxLayout()
1747 b = QPushButton(_("Sign"))
1749 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1750 b = QPushButton(_("Close"))
1751 b.clicked.connect(d.accept)
1753 layout.addLayout(hbox, 4, 1)
1754 tab_widget.addTab(tab, _("Sign"))
1758 layout = QGridLayout(tab)
1760 verify_address = QLineEdit()
1761 layout.addWidget(QLabel(_('Address')), 1, 0)
1762 layout.addWidget(verify_address, 1, 1)
1764 verify_message = QTextEdit()
1765 layout.addWidget(QLabel(_('Message')), 2, 0)
1766 layout.addWidget(verify_message, 2, 1)
1767 layout.setRowStretch(2,3)
1769 verify_signature = QTextEdit()
1770 layout.addWidget(QLabel(_('Signature')), 3, 0)
1771 layout.addWidget(verify_signature, 3, 1)
1772 layout.setRowStretch(3,1)
1775 message = unicode(verify_message.toPlainText())
1776 message = message.encode('utf-8')
1777 if bitcoin.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1778 self.show_message(_("Signature verified"))
1780 self.show_message(_("Error: wrong signature"))
1782 hbox = QHBoxLayout()
1783 b = QPushButton(_("Verify"))
1784 b.clicked.connect(do_verify)
1786 b = QPushButton(_("Close"))
1787 b.clicked.connect(d.accept)
1789 layout.addLayout(hbox, 4, 1)
1790 tab_widget.addTab(tab, _("Verify"))
1792 vbox = QVBoxLayout()
1793 vbox.addWidget(tab_widget)
1800 def question(self, msg):
1801 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1803 def show_message(self, msg):
1804 QMessageBox.information(self, _('Message'), msg, _('OK'))
1806 def password_dialog(self ):
1813 vbox = QVBoxLayout()
1814 msg = _('Please enter your password')
1815 vbox.addWidget(QLabel(msg))
1817 grid = QGridLayout()
1819 grid.addWidget(QLabel(_('Password')), 1, 0)
1820 grid.addWidget(pw, 1, 1)
1821 vbox.addLayout(grid)
1823 vbox.addLayout(ok_cancel_buttons(d))
1826 run_hook('password_dialog', pw, grid, 1)
1827 if not d.exec_(): return
1828 return unicode(pw.text())
1837 def tx_from_text(self, txt):
1838 "json or raw hexadecimal"
1841 tx = Transaction(txt)
1847 tx_dict = json.loads(str(txt))
1848 assert "hex" in tx_dict.keys()
1849 assert "complete" in tx_dict.keys()
1850 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1851 if not tx_dict["complete"]:
1852 assert "input_info" in tx_dict.keys()
1853 input_info = json.loads(tx_dict['input_info'])
1854 tx.add_input_info(input_info)
1859 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1863 def read_tx_from_file(self):
1864 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1868 with open(fileName, "r") as f:
1869 file_content = f.read()
1870 except (ValueError, IOError, os.error), reason:
1871 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1873 return self.tx_from_text(file_content)
1877 def sign_raw_transaction(self, tx, input_info, password):
1878 self.wallet.signrawtransaction(tx, input_info, [], password)
1880 def do_process_from_text(self):
1881 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1884 tx = self.tx_from_text(text)
1886 self.show_transaction(tx)
1888 def do_process_from_file(self):
1889 tx = self.read_tx_from_file()
1891 self.show_transaction(tx)
1893 def do_process_from_txid(self):
1894 from electrum import transaction
1895 txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
1897 r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
1899 tx = transaction.Transaction(r)
1901 self.show_transaction(tx)
1903 self.show_message("unknown transaction")
1905 def do_process_from_csvReader(self, csvReader):
1910 for position, row in enumerate(csvReader):
1912 if not is_valid(address):
1913 errors.append((position, address))
1915 amount = Decimal(row[1])
1916 amount = int(100000000*amount)
1917 outputs.append((address, amount))
1918 except (ValueError, IOError, os.error), reason:
1919 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1923 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
1924 QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
1928 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1929 except Exception as e:
1930 self.show_message(str(e))
1933 self.show_transaction(tx)
1935 def do_process_from_csv_file(self):
1936 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1940 with open(fileName, "r") as f:
1941 csvReader = csv.reader(f)
1942 self.do_process_from_csvReader(csvReader)
1943 except (ValueError, IOError, os.error), reason:
1944 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1947 def do_process_from_csv_text(self):
1948 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1949 + _("Format: address, amount. One output per line"), _("Load CSV"))
1952 f = StringIO.StringIO(text)
1953 csvReader = csv.reader(f)
1954 self.do_process_from_csvReader(csvReader)
1959 def do_export_privkeys(self, password):
1960 if not self.wallet.seed:
1961 self.show_message(_("This wallet has no seed"))
1964 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.")))
1967 select_export = _('Select file to export your private keys to')
1968 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1970 with open(fileName, "w+") as csvfile:
1971 transaction = csv.writer(csvfile)
1972 transaction.writerow(["address", "private_key"])
1974 addresses = self.wallet.addresses(True)
1976 for addr in addresses:
1977 pk = "".join(self.wallet.get_private_key(addr, password))
1978 transaction.writerow(["%34s"%addr,pk])
1980 self.show_message(_("Private keys exported."))
1982 except (IOError, os.error), reason:
1983 export_error_label = _("Electrum was unable to produce a private key-export.")
1984 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1986 except Exception as e:
1987 self.show_message(str(e))
1991 def do_import_labels(self):
1992 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1993 if not labelsFile: return
1995 f = open(labelsFile, 'r')
1998 for key, value in json.loads(data).items():
1999 self.wallet.set_label(key, value)
2000 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2001 except (IOError, os.error), reason:
2002 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2005 def do_export_labels(self):
2006 labels = self.wallet.labels
2008 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2010 with open(fileName, 'w+') as f:
2011 json.dump(labels, f)
2012 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2013 except (IOError, os.error), reason:
2014 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2017 def do_export_history(self):
2018 from lite_window import csv_transaction
2019 csv_transaction(self.wallet)
2023 def do_import_privkey(self, password):
2024 if not self.wallet.imported_keys:
2025 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2026 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2027 + _('Are you sure you understand what you are doing?'), 3, 4)
2030 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2033 text = str(text).split()
2038 addr = self.wallet.import_key(key, password)
2039 except Exception as e:
2045 addrlist.append(addr)
2047 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2049 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2050 self.update_receive_tab()
2051 self.update_history_tab()
2054 def settings_dialog(self):
2056 d.setWindowTitle(_('Electrum Settings'))
2058 vbox = QVBoxLayout()
2059 grid = QGridLayout()
2060 grid.setColumnStretch(0,1)
2062 nz_label = QLabel(_('Display zeros') + ':')
2063 grid.addWidget(nz_label, 0, 0)
2064 nz_e = AmountEdit(None,True)
2065 nz_e.setText("%d"% self.num_zeros)
2066 grid.addWidget(nz_e, 0, 1)
2067 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2068 grid.addWidget(HelpButton(msg), 0, 2)
2069 if not self.config.is_modifiable('num_zeros'):
2070 for w in [nz_e, nz_label]: w.setEnabled(False)
2072 lang_label=QLabel(_('Language') + ':')
2073 grid.addWidget(lang_label, 1, 0)
2074 lang_combo = QComboBox()
2075 from electrum.i18n import languages
2076 lang_combo.addItems(languages.values())
2078 index = languages.keys().index(self.config.get("language",''))
2081 lang_combo.setCurrentIndex(index)
2082 grid.addWidget(lang_combo, 1, 1)
2083 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2084 if not self.config.is_modifiable('language'):
2085 for w in [lang_combo, lang_label]: w.setEnabled(False)
2088 fee_label = QLabel(_('Transaction fee') + ':')
2089 grid.addWidget(fee_label, 2, 0)
2090 fee_e = AmountEdit(self.base_unit)
2091 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2092 grid.addWidget(fee_e, 2, 1)
2093 msg = _('Fee per kilobyte of transaction.') + ' ' \
2094 + _('Recommended value') + ': ' + self.format_amount(20000)
2095 grid.addWidget(HelpButton(msg), 2, 2)
2096 if not self.config.is_modifiable('fee_per_kb'):
2097 for w in [fee_e, fee_label]: w.setEnabled(False)
2099 units = ['BTC', 'mBTC']
2100 unit_label = QLabel(_('Base unit') + ':')
2101 grid.addWidget(unit_label, 3, 0)
2102 unit_combo = QComboBox()
2103 unit_combo.addItems(units)
2104 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2105 grid.addWidget(unit_combo, 3, 1)
2106 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2107 + '\n1BTC=1000mBTC.\n' \
2108 + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2110 usechange_cb = QCheckBox(_('Use change addresses'))
2111 usechange_cb.setChecked(self.wallet.use_change)
2112 grid.addWidget(usechange_cb, 4, 0)
2113 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2114 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2116 grid.setRowStretch(5,1)
2118 vbox.addLayout(grid)
2119 vbox.addLayout(ok_cancel_buttons(d))
2123 if not d.exec_(): return
2125 fee = unicode(fee_e.text())
2127 fee = self.read_amount(fee)
2129 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2132 self.wallet.set_fee(fee)
2134 nz = unicode(nz_e.text())
2139 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2142 if self.num_zeros != nz:
2144 self.config.set_key('num_zeros', nz, True)
2145 self.update_history_tab()
2146 self.update_receive_tab()
2148 usechange_result = usechange_cb.isChecked()
2149 if self.wallet.use_change != usechange_result:
2150 self.wallet.use_change = usechange_result
2151 self.wallet.storage.put('use_change', self.wallet.use_change)
2153 unit_result = units[unit_combo.currentIndex()]
2154 if self.base_unit() != unit_result:
2155 self.decimal_point = 8 if unit_result == 'BTC' else 5
2156 self.config.set_key('decimal_point', self.decimal_point, True)
2157 self.update_history_tab()
2158 self.update_status()
2160 need_restart = False
2162 lang_request = languages.keys()[lang_combo.currentIndex()]
2163 if lang_request != self.config.get('language'):
2164 self.config.set_key("language", lang_request, True)
2167 run_hook('close_settings_dialog')
2170 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2173 def run_network_dialog(self):
2174 if not self.network:
2176 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2178 def closeEvent(self, event):
2181 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2182 self.save_column_widths()
2183 self.config.set_key("console-history", self.console.history[-50:], True)
2184 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2189 def plugins_dialog(self):
2190 from electrum.plugins import plugins
2193 d.setWindowTitle(_('Electrum Plugins'))
2196 vbox = QVBoxLayout(d)
2199 scroll = QScrollArea()
2200 scroll.setEnabled(True)
2201 scroll.setWidgetResizable(True)
2202 scroll.setMinimumSize(400,250)
2203 vbox.addWidget(scroll)
2207 w.setMinimumHeight(len(plugins)*35)
2209 grid = QGridLayout()
2210 grid.setColumnStretch(0,1)
2213 def do_toggle(cb, p, w):
2216 if w: w.setEnabled(r)
2218 def mk_toggle(cb, p, w):
2219 return lambda: do_toggle(cb,p,w)
2221 for i, p in enumerate(plugins):
2223 cb = QCheckBox(p.fullname())
2224 cb.setDisabled(not p.is_available())
2225 cb.setChecked(p.is_enabled())
2226 grid.addWidget(cb, i, 0)
2227 if p.requires_settings():
2228 w = p.settings_widget(self)
2229 w.setEnabled( p.is_enabled() )
2230 grid.addWidget(w, i, 1)
2233 cb.clicked.connect(mk_toggle(cb,p,w))
2234 grid.addWidget(HelpButton(p.description()), i, 2)
2236 print_msg(_("Error: cannot display plugin"), p)
2237 traceback.print_exc(file=sys.stdout)
2238 grid.setRowStretch(i+1,1)
2240 vbox.addLayout(close_button(d))
2245 def show_account_details(self, k):
2247 d.setWindowTitle(_('Account Details'))
2250 vbox = QVBoxLayout(d)
2251 roots = self.wallet.get_roots(k)
2253 name = self.wallet.get_account_name(k)
2254 label = QLabel('Name: ' + name)
2255 vbox.addWidget(label)
2257 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2258 vbox.addWidget(QLabel('Type: ' + acctype))
2260 label = QLabel('Derivation: ' + k)
2261 vbox.addWidget(label)
2264 # mpk = self.wallet.master_public_keys[root]
2265 # text = QTextEdit()
2266 # text.setReadOnly(True)
2267 # text.setMaximumHeight(120)
2268 # text.setText(repr(mpk))
2269 # vbox.addWidget(text)
2271 vbox.addLayout(close_button(d))