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 changeEvent(self, event):
102 flags = self.windowFlags();
103 if event and event.type() == QtCore.QEvent.WindowStateChange:
104 if self.windowState() & QtCore.Qt.WindowMinimized:
105 self.build_menu(True)
106 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
107 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
108 # Electrum from closing.
109 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
110 # self.setWindowFlags(flags & ~Qt.ToolTip)
111 elif event.oldState() & QtCore.Qt.WindowMinimized:
112 self.build_menu(False)
113 #self.setWindowFlags(flags | Qt.ToolTip)
115 def build_menu(self, is_hidden = False):
117 if self.isMinimized():
118 m.addAction(_("Show"), self.showNormal)
120 m.addAction(_("Hide"), self.showMinimized)
123 m.addAction(_("Exit Electrum"), self.close)
124 self.tray.setContextMenu(m)
126 def tray_activated(self, reason):
127 if reason == QSystemTrayIcon.DoubleClick:
130 def showNormal(self):
131 self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
133 def __init__(self, config, network):
134 QMainWindow.__init__(self)
137 self.network = network
139 self._close_electrum = False
142 if sys.platform == 'darwin':
143 self.icon = QIcon(":icons/electrum_dark_icon.png")
144 #self.icon = QIcon(":icons/lock.png")
146 self.icon = QIcon(':icons/electrum_light_icon.png')
148 self.tray = QSystemTrayIcon(self.icon, self)
149 self.tray.setToolTip('Electrum')
150 self.tray.activated.connect(self.tray_activated)
154 self.create_status_bar()
156 self.need_update = threading.Event()
158 self.decimal_point = config.get('decimal_point', 8)
159 self.num_zeros = int(config.get('num_zeros',0))
161 set_language(config.get('language'))
163 self.funds_error = False
164 self.completions = QStringListModel()
166 self.tabs = tabs = QTabWidget(self)
167 self.column_widths = self.config.get("column_widths_2", default_column_widths )
168 tabs.addTab(self.create_history_tab(), _('History') )
169 tabs.addTab(self.create_send_tab(), _('Send') )
170 tabs.addTab(self.create_receive_tab(), _('Receive') )
171 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
172 tabs.addTab(self.create_console_tab(), _('Console') )
173 tabs.setMinimumSize(600, 400)
174 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
175 self.setCentralWidget(tabs)
177 g = self.config.get("winpos-qt",[100, 100, 840, 400])
178 self.setGeometry(g[0], g[1], g[2], g[3])
180 self.setWindowIcon(QIcon(":icons/electrum.png"))
183 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
184 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
185 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
186 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
187 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
189 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
190 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
191 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
193 self.history_list.setFocus(True)
197 self.network.register_callback('updated', lambda: self.need_update.set())
198 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
199 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
200 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
201 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
203 # set initial message
204 self.console.showMessage(self.network.banner)
211 self.config.set_key('lite_mode', False, True)
216 self.config.set_key('lite_mode', True, True)
223 if not self.check_qt_version():
224 if self.config.get('lite_mode') is True:
225 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
226 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
227 self.config.set_key('lite_mode', False, True)
233 actuator = lite_window.MiniActuator(self)
235 # Should probably not modify the current path but instead
236 # change the behaviour of rsrc(...)
237 old_path = QDir.currentPath()
238 actuator.load_theme()
240 self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
242 driver = lite_window.MiniDriver(self, self.mini)
244 # Reset path back to original value now that loading the GUI
246 QDir.setCurrent(old_path)
248 if self.config.get('lite_mode') is True:
254 def check_qt_version(self):
255 qtVersion = qVersion()
256 return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
259 def update_account_selector(self):
261 accounts = self.wallet.get_account_names()
262 self.account_selector.clear()
263 if len(accounts) > 1:
264 self.account_selector.addItems([_("All accounts")] + accounts.values())
265 self.account_selector.setCurrentIndex(0)
266 self.account_selector.show()
268 self.account_selector.hide()
271 def load_wallet(self, wallet):
274 self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
275 self.current_account = self.wallet.storage.get("current_account", None)
277 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
278 if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
279 self.setWindowTitle( title )
281 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
282 self.notify_transactions()
283 self.update_account_selector()
284 self.new_account.setEnabled(self.wallet.seed_version>4)
285 self.update_lock_icon()
286 self.update_buttons_on_seed()
287 self.update_console()
289 run_hook('load_wallet', wallet)
292 def open_wallet(self):
293 wallet_folder = self.wallet.storage.path
294 filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
298 storage = WalletStorage({'wallet_path': filename})
299 if not storage.file_exists:
300 self.show_message("file not found "+ filename)
303 self.wallet.stop_threads()
306 wallet = Wallet(storage)
307 wallet.start_threads(self.network)
309 self.load_wallet(wallet)
313 def backup_wallet(self):
315 path = self.wallet.storage.path
316 wallet_folder = os.path.dirname(path)
317 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
321 new_path = os.path.join(wallet_folder, filename)
324 shutil.copy2(path, new_path)
325 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
326 except (IOError, os.error), reason:
327 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
330 def new_wallet(self):
333 wallet_folder = os.path.dirname(self.wallet.storage.path)
334 filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a new file name'), wallet_folder) )
337 filename = os.path.join(wallet_folder, filename)
339 storage = WalletStorage({'wallet_path': filename})
340 if storage.file_exists:
341 QMessageBox.critical(None, "Error", _("File exists"))
344 wizard = installwizard.InstallWizard(self.config, self.network, storage)
345 wallet = wizard.run()
347 self.load_wallet(wallet)
351 def init_menubar(self):
354 file_menu = menubar.addMenu(_("&File"))
355 open_wallet_action = file_menu.addAction(_("&Open"))
356 open_wallet_action.triggered.connect(self.open_wallet)
358 new_wallet_action = file_menu.addAction(_("&Create/Restore"))
359 new_wallet_action.triggered.connect(self.new_wallet)
361 wallet_backup = file_menu.addAction(_("&Copy"))
362 wallet_backup.triggered.connect(self.backup_wallet)
364 quit_item = file_menu.addAction(_("&Close"))
365 quit_item.triggered.connect(self.close)
367 wallet_menu = menubar.addMenu(_("&Wallet"))
369 new_contact = wallet_menu.addAction(_("&New contact"))
370 new_contact.triggered.connect(self.new_contact_dialog)
372 self.new_account = wallet_menu.addAction(_("&New account"))
373 self.new_account.triggered.connect(self.new_account_dialog)
375 wallet_menu.addSeparator()
377 pw = wallet_menu.addAction(_("&Password"))
378 pw.triggered.connect(self.change_password_dialog)
380 show_seed = wallet_menu.addAction(_("&Seed"))
381 show_seed.triggered.connect(self.show_seed_dialog)
383 show_mpk = wallet_menu.addAction(_("&Master Public Key"))
384 show_mpk.triggered.connect(self.show_master_public_key)
386 wallet_menu.addSeparator()
388 labels_menu = wallet_menu.addMenu(_("&Labels"))
389 import_labels = labels_menu.addAction(_("&Import"))
390 import_labels.triggered.connect(self.do_import_labels)
391 export_labels = labels_menu.addAction(_("&Export"))
392 export_labels.triggered.connect(self.do_export_labels)
394 keys_menu = wallet_menu.addMenu(_("&Private keys"))
395 import_keys = keys_menu.addAction(_("&Import"))
396 import_keys.triggered.connect(self.do_import_privkey)
397 export_keys = keys_menu.addAction(_("&Export"))
398 export_keys.triggered.connect(self.do_export_privkeys)
400 ex_history = wallet_menu.addAction(_("&Export History"))
401 ex_history.triggered.connect(self.do_export_history)
405 tools_menu = menubar.addMenu(_("&Tools"))
407 # Settings / Preferences are all reserved keywords in OSX using this as work around
408 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
409 preferences_menu = tools_menu.addAction(preferences_name)
410 preferences_menu.triggered.connect(self.settings_dialog)
412 network = tools_menu.addAction(_("&Network"))
413 network.triggered.connect(self.run_network_dialog)
415 plugins_labels = tools_menu.addAction(_("&Plugins"))
416 plugins_labels.triggered.connect(self.plugins_dialog)
418 tools_menu.addSeparator()
420 csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
422 csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
423 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
425 csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
426 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
428 raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
430 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
431 raw_transaction_file.triggered.connect(self.do_process_from_file)
433 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
434 raw_transaction_text.triggered.connect(self.do_process_from_text)
437 help_menu = menubar.addMenu(_("&Help"))
438 show_about = help_menu.addAction(_("&About"))
439 show_about.triggered.connect(self.show_about)
440 web_open = help_menu.addAction(_("&Official website"))
441 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
443 help_menu.addSeparator()
444 doc_open = help_menu.addAction(_("&Documentation"))
445 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
446 report_bug = help_menu.addAction(_("&Report Bug"))
447 report_bug.triggered.connect(self.show_report_bug)
449 self.setMenuBar(menubar)
451 def show_about(self):
452 QMessageBox.about(self, "Electrum",
453 _("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."))
455 def show_report_bug(self):
456 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
457 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
460 def notify_transactions(self):
461 if not self.network or not self.network.is_connected():
464 print_error("Notifying GUI")
465 if len(self.network.interface.pending_transactions_for_notifications) > 0:
466 # Combine the transactions if there are more then three
467 tx_amount = len(self.network.interface.pending_transactions_for_notifications)
470 for tx in self.network.interface.pending_transactions_for_notifications:
471 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
475 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
476 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
478 self.network.interface.pending_transactions_for_notifications = []
480 for tx in self.network.interface.pending_transactions_for_notifications:
482 self.network.interface.pending_transactions_for_notifications.remove(tx)
483 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
485 self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
487 def notify(self, message):
488 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
492 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
493 def getOpenFileName(self, title, filter = ""):
494 directory = self.config.get('io_dir', os.path.expanduser('~'))
495 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
496 if fileName and directory != os.path.dirname(fileName):
497 self.config.set_key('io_dir', os.path.dirname(fileName), True)
500 def getSaveFileName(self, title, filename, filter = ""):
501 directory = self.config.get('io_dir', os.path.expanduser('~'))
502 path = os.path.join( directory, filename )
503 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
504 if fileName and directory != os.path.dirname(fileName):
505 self.config.set_key('io_dir', os.path.dirname(fileName), True)
509 QMainWindow.close(self)
510 run_hook('close_main_window')
512 def connect_slots(self, sender):
513 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
514 self.previous_payto_e=''
516 def timer_actions(self):
517 if self.need_update.is_set():
519 self.need_update.clear()
520 run_hook('timer_actions')
522 def format_amount(self, x, is_diff=False, whitespaces=False):
523 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
525 def read_amount(self, x):
526 if x in['.', '']: return None
527 p = pow(10, self.decimal_point)
528 return int( p * Decimal(x) )
531 assert self.decimal_point in [5,8]
532 return "BTC" if self.decimal_point == 8 else "mBTC"
535 def update_status(self):
536 if self.network is None:
538 icon = QIcon(":icons/status_disconnected.png")
540 elif self.network.is_connected():
541 if not self.wallet.up_to_date:
542 text = _("Synchronizing...")
543 icon = QIcon(":icons/status_waiting.png")
544 elif self.network.server_lag > 1:
545 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
546 icon = QIcon(":icons/status_lagging.png")
548 c, u = self.wallet.get_account_balance(self.current_account)
549 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
550 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
553 run_hook('set_quote_text', c+u, r)
556 text += " (%s)"%quote
558 self.tray.setToolTip(text)
559 icon = QIcon(":icons/status_connected.png")
561 text = _("Not connected")
562 icon = QIcon(":icons/status_disconnected.png")
564 self.balance_label.setText(text)
565 self.status_button.setIcon( icon )
568 def update_wallet(self):
570 if self.wallet.up_to_date or not self.network or not self.network.is_connected():
571 self.update_history_tab()
572 self.update_receive_tab()
573 self.update_contacts_tab()
574 self.update_completions()
577 def create_history_tab(self):
578 self.history_list = l = MyTreeWidget(self)
580 for i,width in enumerate(self.column_widths['history']):
581 l.setColumnWidth(i, width)
582 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
583 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
584 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
586 l.customContextMenuRequested.connect(self.create_history_menu)
590 def create_history_menu(self, position):
591 self.history_list.selectedIndexes()
592 item = self.history_list.currentItem()
594 tx_hash = str(item.data(0, Qt.UserRole).toString())
595 if not tx_hash: return
597 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
598 menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
599 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
600 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
603 def show_transaction(self, tx):
604 import transaction_dialog
605 d = transaction_dialog.TxDialog(tx, self)
608 def tx_label_clicked(self, item, column):
609 if column==2 and item.isSelected():
611 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
612 self.history_list.editItem( item, column )
613 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
616 def tx_label_changed(self, item, column):
620 tx_hash = str(item.data(0, Qt.UserRole).toString())
621 tx = self.wallet.transactions.get(tx_hash)
622 text = unicode( item.text(2) )
623 self.wallet.set_label(tx_hash, text)
625 item.setForeground(2, QBrush(QColor('black')))
627 text = self.wallet.get_default_label(tx_hash)
628 item.setText(2, text)
629 item.setForeground(2, QBrush(QColor('gray')))
633 def edit_label(self, is_recv):
634 l = self.receive_list if is_recv else self.contacts_list
635 item = l.currentItem()
636 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
637 l.editItem( item, 1 )
638 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
642 def address_label_clicked(self, item, column, l, column_addr, column_label):
643 if column == column_label and item.isSelected():
644 is_editable = item.data(0, 32).toBool()
647 addr = unicode( item.text(column_addr) )
648 label = unicode( item.text(column_label) )
649 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
650 l.editItem( item, column )
651 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
654 def address_label_changed(self, item, column, l, column_addr, column_label):
655 if column == column_label:
656 addr = unicode( item.text(column_addr) )
657 text = unicode( item.text(column_label) )
658 is_editable = item.data(0, 32).toBool()
662 changed = self.wallet.set_label(addr, text)
664 self.update_history_tab()
665 self.update_completions()
667 self.current_item_changed(item)
669 run_hook('item_changed', item, column)
672 def current_item_changed(self, a):
673 run_hook('current_item_changed', a)
676 self.tabs.emit(SIGNAL('currentChanged(int)'), 1)
680 def update_history_tab(self):
682 self.history_list.clear()
683 for item in self.wallet.get_tx_history(self.current_account):
684 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
685 time_str = _("unknown")
688 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
690 time_str = _("error")
693 time_str = 'unverified'
694 icon = QIcon(":icons/unconfirmed.png")
697 icon = QIcon(":icons/unconfirmed.png")
699 icon = QIcon(":icons/clock%d.png"%conf)
701 icon = QIcon(":icons/confirmed.png")
703 if value is not None:
704 v_str = self.format_amount(value, True, whitespaces=True)
708 balance_str = self.format_amount(balance, whitespaces=True)
711 label, is_default_label = self.wallet.get_label(tx_hash)
713 label = _('Pruned transaction outputs')
714 is_default_label = False
716 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
717 item.setFont(2, QFont(MONOSPACE_FONT))
718 item.setFont(3, QFont(MONOSPACE_FONT))
719 item.setFont(4, QFont(MONOSPACE_FONT))
721 item.setForeground(3, QBrush(QColor("#BC1E1E")))
723 item.setData(0, Qt.UserRole, tx_hash)
724 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
726 item.setForeground(2, QBrush(QColor('grey')))
728 item.setIcon(0, icon)
729 self.history_list.insertTopLevelItem(0,item)
732 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
735 def create_send_tab(self):
740 grid.setColumnMinimumWidth(3,300)
741 grid.setColumnStretch(5,1)
744 self.payto_e = QLineEdit()
745 grid.addWidget(QLabel(_('Pay to')), 1, 0)
746 grid.addWidget(self.payto_e, 1, 1, 1, 3)
748 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)
750 completer = QCompleter()
751 completer.setCaseSensitivity(False)
752 self.payto_e.setCompleter(completer)
753 completer.setModel(self.completions)
755 self.message_e = QLineEdit()
756 grid.addWidget(QLabel(_('Description')), 2, 0)
757 grid.addWidget(self.message_e, 2, 1, 1, 3)
758 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)
761 grid.addWidget(QLabel(_('Selected\nInputs')), 3, 0)
762 self.from_list = QTreeWidget(self)
763 self.from_list.setColumnCount(2)
764 self.from_list.setColumnWidth(0, 350)
765 self.from_list.setColumnWidth(1, 50)
766 self.from_list.setHeaderHidden (True)
767 self.from_list.setMaximumHeight(80)
768 grid.addWidget(self.from_list, 3, 1, 1, 3)
769 self.connect(self.tabs, SIGNAL('currentChanged(int)'),
770 lambda: self.update_pay_from_list(grid))
772 self.amount_e = AmountEdit(self.base_unit)
773 grid.addWidget(QLabel(_('Amount')), 4, 0)
774 grid.addWidget(self.amount_e, 4, 1)
775 grid.addWidget(HelpButton(
776 _('Amount to be sent.') + '\n\n' \
777 + _('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.') \
778 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 4, 3)
780 self.fee_e = AmountEdit(self.base_unit)
781 grid.addWidget(QLabel(_('Fee')), 5, 0)
782 grid.addWidget(self.fee_e, 5, 1)
783 grid.addWidget(HelpButton(
784 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
785 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
786 + _('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)
789 self.send_button = EnterButton(_("Send"), self.do_send)
790 grid.addWidget(self.send_button, 6, 1)
792 b = EnterButton(_("Clear"),self.do_clear)
793 grid.addWidget(b, 6, 2)
795 self.payto_sig = QLabel('')
796 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
798 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
799 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
808 def entry_changed( is_fee ):
809 self.funds_error = False
811 if self.amount_e.is_shortcut:
812 self.amount_e.is_shortcut = False
813 c, u = self.wallet.get_account_balance(self.current_account)
814 inputs, total, fee = self.wallet.choose_tx_inputs_from_account( c + u, 0, self.current_account)
815 fee = self.wallet.estimated_fee(inputs)
817 self.amount_e.setText( self.format_amount(amount) )
818 self.fee_e.setText( self.format_amount( fee ) )
821 amount = self.read_amount(str(self.amount_e.text()))
822 fee = self.read_amount(str(self.fee_e.text()))
824 if not is_fee: fee = None
827 inputs, total, fee = self.wallet.choose_tx_inputs_from_account( amount, fee, self.current_account )
829 self.fee_e.setText( self.format_amount( fee ) )
832 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
836 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
837 self.funds_error = True
838 text = _( "Not enough funds" )
839 c, u = self.wallet.get_frozen_balance()
840 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
842 self.statusBar().showMessage(text)
843 self.amount_e.setPalette(palette)
844 self.fee_e.setPalette(palette)
846 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
847 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
849 run_hook('create_send_tab', grid)
853 def update_pay_from_list(self, grid):
854 self.from_list.clear()
855 grid.itemAtPosition(3,0).widget().setHidden(len(self.pay_from) == 0)
856 grid.itemAtPosition(3,1).widget().setHidden(len(self.pay_from) == 0)
857 for addr in self.pay_from:
858 c, u = self.wallet.get_addr_balance(addr)
859 balance = self.format_amount(c + u)
860 self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
863 def update_completions(self):
865 for addr,label in self.wallet.labels.items():
866 if addr in self.wallet.addressbook:
867 l.append( label + ' <' + addr + '>')
869 run_hook('update_completions', l)
870 self.completions.setStringList(l)
874 return lambda s, *args: s.do_protect(func, args)
879 label = unicode( self.message_e.text() )
880 r = unicode( self.payto_e.text() )
883 # label or alias, with address in brackets
884 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
885 to_address = m.group(2) if m else r
887 if not is_valid(to_address):
888 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
892 amount = self.read_amount(unicode( self.amount_e.text()))
894 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
897 fee = self.read_amount(unicode( self.fee_e.text()))
899 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
902 confirm_amount = self.config.get('confirm_amount', 100000000)
903 if amount >= confirm_amount:
904 if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
907 self.send_tx(to_address, amount, fee, label)
911 def send_tx(self, to_address, amount, fee, label, password):
914 tx = self.wallet.mktx_from_account( [(to_address, amount)],
915 password, fee, self.current_account, self.pay_from)
916 except Exception as e:
917 traceback.print_exc(file=sys.stdout)
918 self.show_message(str(e))
921 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
922 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
926 self.wallet.set_label(tx.hash(), label)
929 h = self.wallet.send_tx(tx)
930 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
931 status, msg = self.wallet.receive_tx( h )
933 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
935 self.update_contacts_tab()
937 QMessageBox.warning(self, _('Error'), msg, _('OK'))
940 self.show_transaction(tx)
942 # add recipient to addressbook
943 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
944 self.wallet.addressbook.append(to_address)
949 def set_url(self, url):
950 address, amount, label, message, signature, identity, url = util.parse_url(url)
953 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
954 elif amount: amount = str(Decimal(amount))
957 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
960 self.mini.set_payment_fields(address, amount)
962 if label and self.wallet.labels.get(address) != label:
963 if self.question('Give label "%s" to address %s ?'%(label,address)):
964 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
965 self.wallet.addressbook.append(address)
966 self.wallet.set_label(address, label)
968 run_hook('set_url', url, self.show_message, self.question)
970 self.tabs.setCurrentIndex(1)
971 label = self.wallet.labels.get(address)
972 m_addr = label + ' <'+ address +'>' if label else address
973 self.payto_e.setText(m_addr)
975 self.message_e.setText(message)
977 self.amount_e.setText(amount)
980 self.set_frozen(self.payto_e,True)
981 self.set_frozen(self.amount_e,True)
982 self.set_frozen(self.message_e,True)
983 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
985 self.payto_sig.setVisible(False)
988 self.payto_sig.setVisible(False)
989 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
991 self.set_frozen(e,False)
994 self.tabs.emit(SIGNAL('currentChanged(int)'), 1)
998 def set_frozen(self,entry,frozen):
1000 entry.setReadOnly(True)
1001 entry.setFrame(False)
1002 palette = QPalette()
1003 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1004 entry.setPalette(palette)
1006 entry.setReadOnly(False)
1007 entry.setFrame(True)
1008 palette = QPalette()
1009 palette.setColor(entry.backgroundRole(), QColor('white'))
1010 entry.setPalette(palette)
1013 def set_addrs_frozen(self,addrs,freeze):
1015 if not addr: continue
1016 if addr in self.wallet.frozen_addresses and not freeze:
1017 self.wallet.unfreeze(addr)
1018 elif addr not in self.wallet.frozen_addresses and freeze:
1019 self.wallet.freeze(addr)
1020 self.update_receive_tab()
1022 def set_addrs_prioritized(self,addrs,prioritize):
1024 if not addr: continue
1025 if addr in self.wallet.prioritized_addresses and not prioritize:
1026 self.wallet.unprioritize(addr)
1027 elif addr not in self.wallet.prioritized_addresses and prioritize:
1028 self.wallet.prioritize(addr)
1029 self.update_receive_tab()
1032 def create_list_tab(self, headers):
1033 "generic tab creation method"
1034 l = MyTreeWidget(self)
1035 l.setColumnCount( len(headers) )
1036 l.setHeaderLabels( headers )
1039 vbox = QVBoxLayout()
1046 vbox.addWidget(buttons)
1048 hbox = QHBoxLayout()
1051 buttons.setLayout(hbox)
1056 def create_receive_tab(self):
1057 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1058 l.setContextMenuPolicy(Qt.CustomContextMenu)
1059 l.customContextMenuRequested.connect(self.create_receive_menu)
1060 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1061 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1062 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1063 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1064 self.receive_list = l
1065 self.receive_buttons_hbox = hbox
1072 def save_column_widths(self):
1073 self.column_widths["receive"] = []
1074 for i in range(self.receive_list.columnCount() -1):
1075 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1077 self.column_widths["history"] = []
1078 for i in range(self.history_list.columnCount() - 1):
1079 self.column_widths["history"].append(self.history_list.columnWidth(i))
1081 self.column_widths["contacts"] = []
1082 for i in range(self.contacts_list.columnCount() - 1):
1083 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1085 self.config.set_key("column_widths_2", self.column_widths, True)
1088 def create_contacts_tab(self):
1089 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1090 l.setContextMenuPolicy(Qt.CustomContextMenu)
1091 l.customContextMenuRequested.connect(self.create_contact_menu)
1092 for i,width in enumerate(self.column_widths['contacts']):
1093 l.setColumnWidth(i, width)
1095 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1096 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1097 self.contacts_list = l
1098 self.contacts_buttons_hbox = hbox
1103 def delete_imported_key(self, addr):
1104 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1105 self.wallet.delete_imported_key(addr)
1106 self.update_receive_tab()
1107 self.update_history_tab()
1109 def edit_account_label(self, k):
1110 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1112 label = unicode(text)
1113 self.wallet.set_label(k,label)
1114 self.update_receive_tab()
1116 def account_set_expanded(self, item, k, b):
1118 self.accounts_expanded[k] = b
1120 def create_account_menu(self, position, k, item):
1122 if item.isExpanded():
1123 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1125 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1126 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1127 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1128 if self.wallet.account_is_pending(k):
1129 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1130 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1132 def delete_pending_account(self, k):
1133 self.wallet.delete_pending_account(k)
1134 self.update_receive_tab()
1136 def create_receive_menu(self, position):
1137 # fixme: this function apparently has a side effect.
1138 # if it is not called the menu pops up several times
1139 #self.receive_list.selectedIndexes()
1141 selected = self.receive_list.selectedItems()
1142 multi_select = len(selected) > 1
1143 addrs = [unicode(item.text(0)) for item in selected]
1144 if not multi_select:
1145 item = self.receive_list.itemAt(position)
1149 if not is_valid(addr):
1150 k = str(item.data(0,32).toString())
1152 self.create_account_menu(position, k, item)
1154 item.setExpanded(not item.isExpanded())
1158 if not multi_select:
1159 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1160 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1161 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1162 if self.wallet.seed:
1163 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1164 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1165 if addr in self.wallet.imported_keys:
1166 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1168 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1169 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1170 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1171 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1172 if any(addr not in self.wallet.prioritized_addresses for addr in addrs):
1173 menu.addAction(_("Prioritize"),
1174 lambda: self.set_addrs_prioritized(addrs, True))
1175 if any(addr in self.wallet.prioritized_addresses for addr in addrs):
1176 menu.addAction(_("Unprioritize"),
1177 lambda: self.set_addrs_prioritized(addrs, False))
1180 for item in self.receive_list.selectedItems():
1181 c, u = self.wallet.get_addr_balance(unicode(item.text(0)))
1183 balance = " [%s]" % self.format_amount(total)
1184 menu.addAction(_("Send From")+balance,
1185 lambda: self.send_from_addresses(self.receive_list))
1187 run_hook('receive_menu', menu)
1188 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1191 def send_from_addresses(self, addrs):
1192 for item in addrs.selectedItems():
1193 self.pay_from.append(unicode(item.text(0)))
1194 self.tabs.setCurrentIndex(1)
1197 def payto(self, addr):
1199 label = self.wallet.labels.get(addr)
1200 m_addr = label + ' <' + addr + '>' if label else addr
1201 self.tabs.setCurrentIndex(1)
1202 self.payto_e.setText(m_addr)
1203 self.amount_e.setFocus()
1206 def delete_contact(self, x):
1207 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1208 self.wallet.delete_contact(x)
1209 self.wallet.set_label(x, None)
1210 self.update_history_tab()
1211 self.update_contacts_tab()
1212 self.update_completions()
1215 def create_contact_menu(self, position):
1216 item = self.contacts_list.itemAt(position)
1218 addr = unicode(item.text(0))
1219 label = unicode(item.text(1))
1220 is_editable = item.data(0,32).toBool()
1221 payto_addr = item.data(0,33).toString()
1223 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1224 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1225 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1227 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1228 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1230 run_hook('create_contact_menu', menu, item)
1231 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1234 def update_receive_item(self, item, num_tx = 0):
1235 item.setFont(0, QFont(MONOSPACE_FONT))
1236 address = str(item.data(0,0).toString())
1237 label = self.wallet.labels.get(address,'')
1238 item.setData(1,0,label)
1239 item.setData(0,32, True) # is editable
1241 run_hook('update_receive_item', address, item)
1243 if not self.wallet.is_mine(address): return
1245 c, u = self.wallet.get_addr_balance(address)
1246 balance = self.format_amount(c + u)
1247 item.setData(2,0,balance)
1249 if (num_tx > 1) and (c == -u):
1250 item.setForeground(0,QColor('lightgray'))
1251 item.setForeground(1,QColor('gray'))
1252 item.setForeground(2,QColor('gray'))
1253 item.setForeground(3,QColor('gray'))
1254 elif address in self.wallet.frozen_addresses:
1255 item.setBackgroundColor(0, QColor('lightblue'))
1256 elif address in self.wallet.prioritized_addresses:
1257 item.setBackgroundColor(0, QColor('lightgreen'))
1260 def update_receive_tab(self):
1261 l = self.receive_list
1264 l.setColumnHidden(2, False)
1265 l.setColumnHidden(3, False)
1266 for i,width in enumerate(self.column_widths['receive']):
1267 l.setColumnWidth(i, width)
1269 if self.current_account is None:
1270 account_items = self.wallet.accounts.items()
1271 elif self.current_account != -1:
1272 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1276 for k, account in account_items:
1277 name = self.wallet.get_account_name(k)
1278 c,u = self.wallet.get_account_balance(k)
1279 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1280 l.addTopLevelItem(account_item)
1281 account_item.setExpanded(self.accounts_expanded.get(k, True))
1282 account_item.setData(0, 32, k)
1284 if not self.wallet.is_seeded(k):
1285 icon = QIcon(":icons/key.png")
1286 account_item.setIcon(0, icon)
1288 for is_change in ([0,1]):
1289 name = _("Receiving") if not is_change else _("Change")
1290 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1291 account_item.addChild(seq_item)
1292 if not is_change: seq_item.setExpanded(True)
1297 for address in account.get_addresses(is_change):
1298 h = self.wallet.history.get(address,[])
1302 if gap > self.wallet.gap_limit:
1307 num_tx = '*' if h == ['*'] else "%d"%len(h)
1308 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1309 self.update_receive_item(item, len(h))
1311 item.setBackgroundColor(1, QColor('red'))
1312 seq_item.addChild(item)
1315 for k, addr in self.wallet.get_pending_accounts():
1316 name = self.wallet.labels.get(k,'')
1317 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1318 self.update_receive_item(item)
1319 l.addTopLevelItem(account_item)
1320 account_item.setExpanded(True)
1321 account_item.setData(0, 32, k)
1322 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1323 account_item.addChild(item)
1324 self.update_receive_item(item)
1327 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1328 c,u = self.wallet.get_imported_balance()
1329 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1330 l.addTopLevelItem(account_item)
1331 account_item.setExpanded(True)
1332 for address in self.wallet.imported_keys.keys():
1333 item = QTreeWidgetItem( [ address, '', '', ''] )
1334 self.update_receive_item(item)
1335 account_item.addChild(item)
1338 # we use column 1 because column 0 may be hidden
1339 l.setCurrentItem(l.topLevelItem(0),1)
1342 def update_contacts_tab(self):
1343 l = self.contacts_list
1346 for address in self.wallet.addressbook:
1347 label = self.wallet.labels.get(address,'')
1348 n = self.wallet.get_num_tx(address)
1349 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1350 item.setFont(0, QFont(MONOSPACE_FONT))
1351 # 32 = label can be edited (bool)
1352 item.setData(0,32, True)
1354 item.setData(0,33, address)
1355 l.addTopLevelItem(item)
1357 run_hook('update_contacts_tab', l)
1358 l.setCurrentItem(l.topLevelItem(0))
1362 def create_console_tab(self):
1363 from console import Console
1364 self.console = console = Console()
1368 def update_console(self):
1369 console = self.console
1370 console.history = self.config.get("console-history",[])
1371 console.history_index = len(console.history)
1373 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1374 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1376 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1378 def mkfunc(f, method):
1379 return lambda *args: apply( f, (method, args, self.password_dialog ))
1381 if m[0]=='_' or m in ['network','wallet']: continue
1382 methods[m] = mkfunc(c._run, m)
1384 console.updateNamespace(methods)
1387 def change_account(self,s):
1388 if s == _("All accounts"):
1389 self.current_account = None
1391 accounts = self.wallet.get_account_names()
1392 for k, v in accounts.items():
1394 self.current_account = k
1395 self.update_history_tab()
1396 self.update_status()
1397 self.update_receive_tab()
1399 def create_status_bar(self):
1402 sb.setFixedHeight(35)
1403 qtVersion = qVersion()
1405 self.balance_label = QLabel("")
1406 sb.addWidget(self.balance_label)
1408 from version_getter import UpdateLabel
1409 self.updatelabel = UpdateLabel(self.config, sb)
1411 self.account_selector = QComboBox()
1412 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1413 sb.addPermanentWidget(self.account_selector)
1415 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1416 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1418 self.lock_icon = QIcon()
1419 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1420 sb.addPermanentWidget( self.password_button )
1422 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1423 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1424 sb.addPermanentWidget( self.seed_button )
1425 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1426 sb.addPermanentWidget( self.status_button )
1428 run_hook('create_status_bar', (sb,))
1430 self.setStatusBar(sb)
1433 def update_lock_icon(self):
1434 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1435 self.password_button.setIcon( icon )
1438 def update_buttons_on_seed(self):
1439 if not self.wallet.is_watching_only():
1440 self.seed_button.show()
1441 self.password_button.show()
1442 self.send_button.setText(_("Send"))
1444 self.password_button.hide()
1445 self.seed_button.hide()
1446 self.send_button.setText(_("Create unsigned transaction"))
1449 def change_password_dialog(self):
1450 from password_dialog import PasswordDialog
1451 d = PasswordDialog(self.wallet, self)
1453 self.update_lock_icon()
1456 def new_contact_dialog(self):
1459 vbox = QVBoxLayout(d)
1460 vbox.addWidget(QLabel(_('New Contact')+':'))
1462 grid = QGridLayout()
1465 grid.addWidget(QLabel(_("Address")), 1, 0)
1466 grid.addWidget(line1, 1, 1)
1467 grid.addWidget(QLabel(_("Name")), 2, 0)
1468 grid.addWidget(line2, 2, 1)
1470 vbox.addLayout(grid)
1471 vbox.addLayout(ok_cancel_buttons(d))
1476 address = str(line1.text())
1477 label = unicode(line2.text())
1479 if not is_valid(address):
1480 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1483 self.wallet.add_contact(address)
1485 self.wallet.set_label(address, label)
1487 self.update_contacts_tab()
1488 self.update_history_tab()
1489 self.update_completions()
1490 self.tabs.setCurrentIndex(3)
1493 def new_account_dialog(self):
1495 dialog = QDialog(self)
1497 dialog.setWindowTitle(_("New Account"))
1499 vbox = QVBoxLayout()
1500 vbox.addWidget(QLabel(_('Account name')+':'))
1503 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1504 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1509 vbox.addLayout(ok_cancel_buttons(dialog))
1510 dialog.setLayout(vbox)
1514 name = str(e.text())
1517 self.wallet.create_pending_account('1', name)
1518 self.update_receive_tab()
1519 self.tabs.setCurrentIndex(2)
1523 def show_master_public_key_old(self):
1524 dialog = QDialog(self)
1526 dialog.setWindowTitle(_("Master Public Key"))
1528 main_text = QTextEdit()
1529 main_text.setText(self.wallet.get_master_public_key())
1530 main_text.setReadOnly(True)
1531 main_text.setMaximumHeight(170)
1532 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1534 ok_button = QPushButton(_("OK"))
1535 ok_button.setDefault(True)
1536 ok_button.clicked.connect(dialog.accept)
1538 main_layout = QGridLayout()
1539 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1541 main_layout.addWidget(main_text, 1, 0)
1542 main_layout.addWidget(qrw, 1, 1 )
1544 vbox = QVBoxLayout()
1545 vbox.addLayout(main_layout)
1546 vbox.addLayout(close_button(dialog))
1547 dialog.setLayout(vbox)
1551 def show_master_public_key(self):
1553 if self.wallet.seed_version == 4:
1554 self.show_master_public_key_old()
1557 dialog = QDialog(self)
1559 dialog.setWindowTitle(_("Master Public Keys"))
1561 chain_text = QTextEdit()
1562 chain_text.setReadOnly(True)
1563 chain_text.setMaximumHeight(170)
1564 chain_qrw = QRCodeWidget()
1566 mpk_text = QTextEdit()
1567 mpk_text.setReadOnly(True)
1568 mpk_text.setMaximumHeight(170)
1569 mpk_qrw = QRCodeWidget()
1571 main_layout = QGridLayout()
1573 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1574 main_layout.addWidget(mpk_text, 1, 1)
1575 main_layout.addWidget(mpk_qrw, 1, 2)
1577 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1578 main_layout.addWidget(chain_text, 2, 1)
1579 main_layout.addWidget(chain_qrw, 2, 2)
1582 c, K, cK = self.wallet.master_public_keys[str(key)]
1583 chain_text.setText(c)
1584 chain_qrw.set_addr(c)
1585 chain_qrw.update_qr()
1590 key_selector = QComboBox()
1591 keys = sorted(self.wallet.master_public_keys.keys())
1592 key_selector.addItems(keys)
1594 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1595 main_layout.addWidget(key_selector, 0, 1)
1596 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1600 vbox = QVBoxLayout()
1601 vbox.addLayout(main_layout)
1602 vbox.addLayout(close_button(dialog))
1604 dialog.setLayout(vbox)
1609 def show_seed_dialog(self, password):
1610 if self.wallet.is_watching_only():
1611 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1614 if self.wallet.seed:
1616 mnemonic = self.wallet.get_mnemonic(password)
1618 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1620 from seed_dialog import SeedDialog
1621 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1625 for k in self.wallet.master_private_keys.keys():
1626 pk = self.wallet.get_master_private_key(k, password)
1628 from seed_dialog import PrivateKeysDialog
1629 d = PrivateKeysDialog(self,l)
1636 def show_qrcode(self, data, title = _("QR code")):
1640 d.setWindowTitle(title)
1641 d.setMinimumSize(270, 300)
1642 vbox = QVBoxLayout()
1643 qrw = QRCodeWidget(data)
1644 vbox.addWidget(qrw, 1)
1645 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1646 hbox = QHBoxLayout()
1649 filename = os.path.join(self.config.path, "qrcode.bmp")
1652 bmp.save_qrcode(qrw.qr, filename)
1653 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1655 def copy_to_clipboard():
1656 bmp.save_qrcode(qrw.qr, filename)
1657 self.app.clipboard().setImage(QImage(filename))
1658 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1660 b = QPushButton(_("Copy"))
1662 b.clicked.connect(copy_to_clipboard)
1664 b = QPushButton(_("Save"))
1666 b.clicked.connect(print_qr)
1668 b = QPushButton(_("Close"))
1670 b.clicked.connect(d.accept)
1673 vbox.addLayout(hbox)
1678 def do_protect(self, func, args):
1679 if self.wallet.use_encryption:
1680 password = self.password_dialog()
1686 if args != (False,):
1687 args = (self,) + args + (password,)
1689 args = (self,password)
1694 def show_private_key(self, address, password):
1695 if not address: return
1697 pk_list = self.wallet.get_private_key(address, password)
1698 except Exception as e:
1699 self.show_message(str(e))
1701 QMessageBox.information(self, _('Private key'), _('Address')+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1705 def do_sign(self, address, message, signature, password):
1706 message = unicode(message.toPlainText())
1707 message = message.encode('utf-8')
1709 sig = self.wallet.sign_message(str(address.text()), message, password)
1710 signature.setText(sig)
1711 except Exception as e:
1712 self.show_message(str(e))
1714 def sign_message(self, address):
1715 if not address: return
1718 d.setWindowTitle(_('Sign Message'))
1719 d.setMinimumSize(410, 290)
1721 tab_widget = QTabWidget()
1723 layout = QGridLayout(tab)
1725 sign_address = QLineEdit()
1727 sign_address.setText(address)
1728 layout.addWidget(QLabel(_('Address')), 1, 0)
1729 layout.addWidget(sign_address, 1, 1)
1731 sign_message = QTextEdit()
1732 layout.addWidget(QLabel(_('Message')), 2, 0)
1733 layout.addWidget(sign_message, 2, 1)
1734 layout.setRowStretch(2,3)
1736 sign_signature = QTextEdit()
1737 layout.addWidget(QLabel(_('Signature')), 3, 0)
1738 layout.addWidget(sign_signature, 3, 1)
1739 layout.setRowStretch(3,1)
1742 hbox = QHBoxLayout()
1743 b = QPushButton(_("Sign"))
1745 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1746 b = QPushButton(_("Close"))
1747 b.clicked.connect(d.accept)
1749 layout.addLayout(hbox, 4, 1)
1750 tab_widget.addTab(tab, _("Sign"))
1754 layout = QGridLayout(tab)
1756 verify_address = QLineEdit()
1757 layout.addWidget(QLabel(_('Address')), 1, 0)
1758 layout.addWidget(verify_address, 1, 1)
1760 verify_message = QTextEdit()
1761 layout.addWidget(QLabel(_('Message')), 2, 0)
1762 layout.addWidget(verify_message, 2, 1)
1763 layout.setRowStretch(2,3)
1765 verify_signature = QTextEdit()
1766 layout.addWidget(QLabel(_('Signature')), 3, 0)
1767 layout.addWidget(verify_signature, 3, 1)
1768 layout.setRowStretch(3,1)
1771 message = unicode(verify_message.toPlainText())
1772 message = message.encode('utf-8')
1773 if bitcoin.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1774 self.show_message(_("Signature verified"))
1776 self.show_message(_("Error: wrong signature"))
1778 hbox = QHBoxLayout()
1779 b = QPushButton(_("Verify"))
1780 b.clicked.connect(do_verify)
1782 b = QPushButton(_("Close"))
1783 b.clicked.connect(d.accept)
1785 layout.addLayout(hbox, 4, 1)
1786 tab_widget.addTab(tab, _("Verify"))
1788 vbox = QVBoxLayout()
1789 vbox.addWidget(tab_widget)
1796 def question(self, msg):
1797 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1799 def show_message(self, msg):
1800 QMessageBox.information(self, _('Message'), msg, _('OK'))
1802 def password_dialog(self ):
1809 vbox = QVBoxLayout()
1810 msg = _('Please enter your password')
1811 vbox.addWidget(QLabel(msg))
1813 grid = QGridLayout()
1815 grid.addWidget(QLabel(_('Password')), 1, 0)
1816 grid.addWidget(pw, 1, 1)
1817 vbox.addLayout(grid)
1819 vbox.addLayout(ok_cancel_buttons(d))
1822 run_hook('password_dialog', pw, grid, 1)
1823 if not d.exec_(): return
1824 return unicode(pw.text())
1833 def tx_from_text(self, txt):
1834 "json or raw hexadecimal"
1837 tx = Transaction(txt)
1843 tx_dict = json.loads(str(txt))
1844 assert "hex" in tx_dict.keys()
1845 assert "complete" in tx_dict.keys()
1846 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1847 if not tx_dict["complete"]:
1848 assert "input_info" in tx_dict.keys()
1849 input_info = json.loads(tx_dict['input_info'])
1850 tx.add_input_info(input_info)
1855 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1859 def read_tx_from_file(self):
1860 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1864 with open(fileName, "r") as f:
1865 file_content = f.read()
1866 except (ValueError, IOError, os.error), reason:
1867 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1869 return self.tx_from_text(file_content)
1873 def sign_raw_transaction(self, tx, input_info, password):
1874 self.wallet.signrawtransaction(tx, input_info, [], password)
1876 def do_process_from_text(self):
1877 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1880 tx = self.tx_from_text(text)
1882 self.show_transaction(tx)
1884 def do_process_from_file(self):
1885 tx = self.read_tx_from_file()
1887 self.show_transaction(tx)
1889 def do_process_from_csvReader(self, csvReader):
1892 for row in csvReader:
1894 amount = float(row[1])
1895 amount = int(100000000*amount)
1896 outputs.append((address, amount))
1897 except (ValueError, IOError, os.error), reason:
1898 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1902 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1903 except Exception as e:
1904 self.show_message(str(e))
1907 self.show_transaction(tx)
1909 def do_process_from_csv_file(self):
1910 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1914 with open(fileName, "r") as f:
1915 csvReader = csv.reader(f)
1916 self.do_process_from_csvReader(csvReader)
1917 except (ValueError, IOError, os.error), reason:
1918 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1921 def do_process_from_csv_text(self):
1922 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1923 + _("Format: address, amount. One output per line"), _("Load CSV"))
1926 f = StringIO.StringIO(text)
1927 csvReader = csv.reader(f)
1928 self.do_process_from_csvReader(csvReader)
1933 def do_export_privkeys(self, password):
1934 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.")))
1937 select_export = _('Select file to export your private keys to')
1938 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1940 with open(fileName, "w+") as csvfile:
1941 transaction = csv.writer(csvfile)
1942 transaction.writerow(["address", "private_key"])
1944 addresses = self.wallet.addresses(True)
1946 for addr in addresses:
1947 pk = "".join(self.wallet.get_private_key(addr, password))
1948 transaction.writerow(["%34s"%addr,pk])
1950 self.show_message(_("Private keys exported."))
1952 except (IOError, os.error), reason:
1953 export_error_label = _("Electrum was unable to produce a private key-export.")
1954 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1956 except Exception as e:
1957 self.show_message(str(e))
1961 def do_import_labels(self):
1962 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1963 if not labelsFile: return
1965 f = open(labelsFile, 'r')
1968 for key, value in json.loads(data).items():
1969 self.wallet.set_label(key, value)
1970 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1971 except (IOError, os.error), reason:
1972 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1975 def do_export_labels(self):
1976 labels = self.wallet.labels
1978 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1980 with open(fileName, 'w+') as f:
1981 json.dump(labels, f)
1982 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1983 except (IOError, os.error), reason:
1984 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1987 def do_export_history(self):
1988 from lite_window import csv_transaction
1989 csv_transaction(self.wallet)
1993 def do_import_privkey(self, password):
1994 if not self.wallet.imported_keys:
1995 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1996 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1997 + _('Are you sure you understand what you are doing?'), 3, 4)
2000 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2003 text = str(text).split()
2008 addr = self.wallet.import_key(key, password)
2009 except Exception as e:
2015 addrlist.append(addr)
2017 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2019 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2020 self.update_receive_tab()
2021 self.update_history_tab()
2024 def settings_dialog(self):
2026 d.setWindowTitle(_('Electrum Settings'))
2028 vbox = QVBoxLayout()
2029 grid = QGridLayout()
2030 grid.setColumnStretch(0,1)
2032 nz_label = QLabel(_('Display zeros') + ':')
2033 grid.addWidget(nz_label, 0, 0)
2034 nz_e = AmountEdit(None,True)
2035 nz_e.setText("%d"% self.num_zeros)
2036 grid.addWidget(nz_e, 0, 1)
2037 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2038 grid.addWidget(HelpButton(msg), 0, 2)
2039 if not self.config.is_modifiable('num_zeros'):
2040 for w in [nz_e, nz_label]: w.setEnabled(False)
2042 lang_label=QLabel(_('Language') + ':')
2043 grid.addWidget(lang_label, 1, 0)
2044 lang_combo = QComboBox()
2045 from electrum.i18n import languages
2046 lang_combo.addItems(languages.values())
2048 index = languages.keys().index(self.config.get("language",''))
2051 lang_combo.setCurrentIndex(index)
2052 grid.addWidget(lang_combo, 1, 1)
2053 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2054 if not self.config.is_modifiable('language'):
2055 for w in [lang_combo, lang_label]: w.setEnabled(False)
2058 fee_label = QLabel(_('Transaction fee') + ':')
2059 grid.addWidget(fee_label, 2, 0)
2060 fee_e = AmountEdit(self.base_unit)
2061 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2062 grid.addWidget(fee_e, 2, 1)
2063 msg = _('Fee per kilobyte of transaction.') + ' ' \
2064 + _('Recommended value') + ': ' + self.format_amount(20000)
2065 grid.addWidget(HelpButton(msg), 2, 2)
2066 if not self.config.is_modifiable('fee_per_kb'):
2067 for w in [fee_e, fee_label]: w.setEnabled(False)
2069 units = ['BTC', 'mBTC']
2070 unit_label = QLabel(_('Base unit') + ':')
2071 grid.addWidget(unit_label, 3, 0)
2072 unit_combo = QComboBox()
2073 unit_combo.addItems(units)
2074 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2075 grid.addWidget(unit_combo, 3, 1)
2076 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2077 + '\n1BTC=1000mBTC.\n' \
2078 + _(' This settings affects the fields in the Send tab')+' '), 3, 2)
2080 usechange_cb = QCheckBox(_('Use change addresses'))
2081 usechange_cb.setChecked(self.wallet.use_change)
2082 grid.addWidget(usechange_cb, 4, 0)
2083 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2084 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2086 grid.setRowStretch(5,1)
2088 vbox.addLayout(grid)
2089 vbox.addLayout(ok_cancel_buttons(d))
2093 if not d.exec_(): return
2095 fee = unicode(fee_e.text())
2097 fee = self.read_amount(fee)
2099 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2102 self.wallet.set_fee(fee)
2104 nz = unicode(nz_e.text())
2109 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2112 if self.num_zeros != nz:
2114 self.config.set_key('num_zeros', nz, True)
2115 self.update_history_tab()
2116 self.update_receive_tab()
2118 usechange_result = usechange_cb.isChecked()
2119 if self.wallet.use_change != usechange_result:
2120 self.wallet.use_change = usechange_result
2121 self.wallet.storage.put('use_change', self.wallet.use_change)
2123 unit_result = units[unit_combo.currentIndex()]
2124 if self.base_unit() != unit_result:
2125 self.decimal_point = 8 if unit_result == 'BTC' else 5
2126 self.config.set_key('decimal_point', self.decimal_point, True)
2127 self.update_history_tab()
2128 self.update_status()
2130 need_restart = False
2132 lang_request = languages.keys()[lang_combo.currentIndex()]
2133 if lang_request != self.config.get('language'):
2134 self.config.set_key("language", lang_request, True)
2137 run_hook('close_settings_dialog')
2140 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2143 def run_network_dialog(self):
2144 if not self.network:
2146 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2148 def closeEvent(self, event):
2150 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2151 self.save_column_widths()
2152 self.config.set_key("console-history", self.console.history[-50:], True)
2153 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2158 def plugins_dialog(self):
2159 from electrum.plugins import plugins
2162 d.setWindowTitle(_('Electrum Plugins'))
2165 vbox = QVBoxLayout(d)
2168 scroll = QScrollArea()
2169 scroll.setEnabled(True)
2170 scroll.setWidgetResizable(True)
2171 scroll.setMinimumSize(400,250)
2172 vbox.addWidget(scroll)
2176 w.setMinimumHeight(len(plugins)*35)
2178 grid = QGridLayout()
2179 grid.setColumnStretch(0,1)
2182 def do_toggle(cb, p, w):
2185 if w: w.setEnabled(r)
2187 def mk_toggle(cb, p, w):
2188 return lambda: do_toggle(cb,p,w)
2190 for i, p in enumerate(plugins):
2192 cb = QCheckBox(p.fullname())
2193 cb.setDisabled(not p.is_available())
2194 cb.setChecked(p.is_enabled())
2195 grid.addWidget(cb, i, 0)
2196 if p.requires_settings():
2197 w = p.settings_widget(self)
2198 w.setEnabled( p.is_enabled() )
2199 grid.addWidget(w, i, 1)
2202 cb.clicked.connect(mk_toggle(cb,p,w))
2203 grid.addWidget(HelpButton(p.description()), i, 2)
2205 print_msg(_("Error: cannot display plugin"), p)
2206 traceback.print_exc(file=sys.stdout)
2207 grid.setRowStretch(i+1,1)
2209 vbox.addLayout(close_button(d))
2214 def show_account_details(self, k):
2216 d.setWindowTitle(_('Account Details'))
2219 vbox = QVBoxLayout(d)
2220 roots = self.wallet.get_roots(k)
2222 name = self.wallet.get_account_name(k)
2223 label = QLabel('Name: ' + name)
2224 vbox.addWidget(label)
2226 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2227 vbox.addWidget(QLabel('Type: ' + acctype))
2229 label = QLabel('Derivation: ' + k)
2230 vbox.addWidget(label)
2233 # mpk = self.wallet.master_public_keys[root]
2234 # text = QTextEdit()
2235 # text.setReadOnly(True)
2236 # text.setMaximumHeight(120)
2237 # text.setText(repr(mpk))
2238 # vbox.addWidget(text)
2240 vbox.addLayout(close_button(d))