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 sendable = self.get_sendable_balance()
814 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, self.get_payment_sources())
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(amount, 0, self.get_payment_sources())
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):
913 tx = self.wallet.mktx( [(to_address, amount)], password, fee,
914 domain=self.get_payment_sources())
915 except Exception as e:
916 traceback.print_exc(file=sys.stdout)
917 self.show_message(str(e))
920 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
921 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
925 self.wallet.set_label(tx.hash(), label)
928 h = self.wallet.send_tx(tx)
929 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
930 status, msg = self.wallet.receive_tx( h )
932 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
934 self.update_contacts_tab()
936 QMessageBox.warning(self, _('Error'), msg, _('OK'))
939 self.show_transaction(tx)
941 # add recipient to addressbook
942 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
943 self.wallet.addressbook.append(to_address)
948 def set_url(self, url):
949 address, amount, label, message, signature, identity, url = util.parse_url(url)
952 if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
953 elif amount: amount = str(Decimal(amount))
956 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
959 self.mini.set_payment_fields(address, amount)
961 if label and self.wallet.labels.get(address) != label:
962 if self.question('Give label "%s" to address %s ?'%(label,address)):
963 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
964 self.wallet.addressbook.append(address)
965 self.wallet.set_label(address, label)
967 run_hook('set_url', url, self.show_message, self.question)
969 self.tabs.setCurrentIndex(1)
970 label = self.wallet.labels.get(address)
971 m_addr = label + ' <'+ address +'>' if label else address
972 self.payto_e.setText(m_addr)
974 self.message_e.setText(message)
976 self.amount_e.setText(amount)
979 self.set_frozen(self.payto_e,True)
980 self.set_frozen(self.amount_e,True)
981 self.set_frozen(self.message_e,True)
982 self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
984 self.payto_sig.setVisible(False)
987 self.payto_sig.setVisible(False)
988 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
990 self.set_frozen(e,False)
993 self.tabs.emit(SIGNAL('currentChanged(int)'), 1)
997 def set_frozen(self,entry,frozen):
999 entry.setReadOnly(True)
1000 entry.setFrame(False)
1001 palette = QPalette()
1002 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1003 entry.setPalette(palette)
1005 entry.setReadOnly(False)
1006 entry.setFrame(True)
1007 palette = QPalette()
1008 palette.setColor(entry.backgroundRole(), QColor('white'))
1009 entry.setPalette(palette)
1012 def set_addrs_frozen(self,addrs,freeze):
1014 if not addr: continue
1015 if addr in self.wallet.frozen_addresses and not freeze:
1016 self.wallet.unfreeze(addr)
1017 elif addr not in self.wallet.frozen_addresses and freeze:
1018 self.wallet.freeze(addr)
1019 self.update_receive_tab()
1021 def set_addrs_prioritized(self,addrs,prioritize):
1023 if not addr: continue
1024 if addr in self.wallet.prioritized_addresses and not prioritize:
1025 self.wallet.unprioritize(addr)
1026 elif addr not in self.wallet.prioritized_addresses and prioritize:
1027 self.wallet.prioritize(addr)
1028 self.update_receive_tab()
1031 def create_list_tab(self, headers):
1032 "generic tab creation method"
1033 l = MyTreeWidget(self)
1034 l.setColumnCount( len(headers) )
1035 l.setHeaderLabels( headers )
1038 vbox = QVBoxLayout()
1045 vbox.addWidget(buttons)
1047 hbox = QHBoxLayout()
1050 buttons.setLayout(hbox)
1055 def create_receive_tab(self):
1056 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1057 l.setContextMenuPolicy(Qt.CustomContextMenu)
1058 l.customContextMenuRequested.connect(self.create_receive_menu)
1059 l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1060 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1061 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1062 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1063 self.receive_list = l
1064 self.receive_buttons_hbox = hbox
1071 def save_column_widths(self):
1072 self.column_widths["receive"] = []
1073 for i in range(self.receive_list.columnCount() -1):
1074 self.column_widths["receive"].append(self.receive_list.columnWidth(i))
1076 self.column_widths["history"] = []
1077 for i in range(self.history_list.columnCount() - 1):
1078 self.column_widths["history"].append(self.history_list.columnWidth(i))
1080 self.column_widths["contacts"] = []
1081 for i in range(self.contacts_list.columnCount() - 1):
1082 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1084 self.config.set_key("column_widths_2", self.column_widths, True)
1087 def create_contacts_tab(self):
1088 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1089 l.setContextMenuPolicy(Qt.CustomContextMenu)
1090 l.customContextMenuRequested.connect(self.create_contact_menu)
1091 for i,width in enumerate(self.column_widths['contacts']):
1092 l.setColumnWidth(i, width)
1094 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1095 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1096 self.contacts_list = l
1097 self.contacts_buttons_hbox = hbox
1102 def delete_imported_key(self, addr):
1103 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1104 self.wallet.delete_imported_key(addr)
1105 self.update_receive_tab()
1106 self.update_history_tab()
1108 def edit_account_label(self, k):
1109 text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1111 label = unicode(text)
1112 self.wallet.set_label(k,label)
1113 self.update_receive_tab()
1115 def account_set_expanded(self, item, k, b):
1117 self.accounts_expanded[k] = b
1119 def create_account_menu(self, position, k, item):
1121 if item.isExpanded():
1122 menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1124 menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1125 menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1126 menu.addAction(_("View details"), lambda: self.show_account_details(k))
1127 if self.wallet.account_is_pending(k):
1128 menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1129 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1131 def delete_pending_account(self, k):
1132 self.wallet.delete_pending_account(k)
1133 self.update_receive_tab()
1135 def create_receive_menu(self, position):
1136 # fixme: this function apparently has a side effect.
1137 # if it is not called the menu pops up several times
1138 #self.receive_list.selectedIndexes()
1140 selected = self.receive_list.selectedItems()
1141 multi_select = len(selected) > 1
1142 addrs = [unicode(item.text(0)) for item in selected]
1143 if not multi_select:
1144 item = self.receive_list.itemAt(position)
1148 if not is_valid(addr):
1149 k = str(item.data(0,32).toString())
1151 self.create_account_menu(position, k, item)
1153 item.setExpanded(not item.isExpanded())
1157 if not multi_select:
1158 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1159 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1160 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1161 if self.wallet.seed:
1162 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1163 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1164 if addr in self.wallet.imported_keys:
1165 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1167 if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1168 menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1169 if any(addr in self.wallet.frozen_addresses for addr in addrs):
1170 menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1171 if any(addr not in self.wallet.prioritized_addresses for addr in addrs):
1172 menu.addAction(_("Prioritize"),
1173 lambda: self.set_addrs_prioritized(addrs, True))
1174 if any(addr in self.wallet.prioritized_addresses for addr in addrs):
1175 menu.addAction(_("Unprioritize"),
1176 lambda: self.set_addrs_prioritized(addrs, False))
1178 balance = " [%s]" % self.format_amount(self.get_sendable_balance())
1179 menu.addAction(_("Send From")+balance,
1180 lambda: self.send_from_addresses(addrs))
1182 run_hook('receive_menu', menu)
1183 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1186 def get_sendable_balance(self):
1187 return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
1190 def get_payment_sources(self):
1192 return self.pay_from
1194 return self.wallet.get_account_addresses(self.current_account)
1197 def send_from_addresses(self, addrs):
1198 self.pay_from = addrs[:]
1199 self.tabs.setCurrentIndex(1)
1202 def payto(self, addr):
1204 label = self.wallet.labels.get(addr)
1205 m_addr = label + ' <' + addr + '>' if label else addr
1206 self.tabs.setCurrentIndex(1)
1207 self.payto_e.setText(m_addr)
1208 self.amount_e.setFocus()
1211 def delete_contact(self, x):
1212 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1213 self.wallet.delete_contact(x)
1214 self.wallet.set_label(x, None)
1215 self.update_history_tab()
1216 self.update_contacts_tab()
1217 self.update_completions()
1220 def create_contact_menu(self, position):
1221 item = self.contacts_list.itemAt(position)
1223 addr = unicode(item.text(0))
1224 label = unicode(item.text(1))
1225 is_editable = item.data(0,32).toBool()
1226 payto_addr = item.data(0,33).toString()
1228 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1229 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1230 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1232 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1233 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1235 run_hook('create_contact_menu', menu, item)
1236 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1239 def update_receive_item(self, item, num_tx = 0):
1240 item.setFont(0, QFont(MONOSPACE_FONT))
1241 address = str(item.data(0,0).toString())
1242 label = self.wallet.labels.get(address,'')
1243 item.setData(1,0,label)
1244 item.setData(0,32, True) # is editable
1246 run_hook('update_receive_item', address, item)
1248 if not self.wallet.is_mine(address): return
1250 c, u = self.wallet.get_addr_balance(address)
1251 balance = self.format_amount(c + u)
1252 item.setData(2,0,balance)
1254 if (num_tx > 1) and (c == -u):
1255 item.setForeground(0,QColor('lightgray'))
1256 item.setForeground(1,QColor('gray'))
1257 item.setForeground(2,QColor('gray'))
1258 item.setForeground(3,QColor('gray'))
1259 elif address in self.wallet.frozen_addresses:
1260 item.setBackgroundColor(0, QColor('lightblue'))
1261 elif address in self.wallet.prioritized_addresses:
1262 item.setBackgroundColor(0, QColor('lightgreen'))
1265 def update_receive_tab(self):
1266 l = self.receive_list
1269 l.setColumnHidden(2, False)
1270 l.setColumnHidden(3, False)
1271 for i,width in enumerate(self.column_widths['receive']):
1272 l.setColumnWidth(i, width)
1274 if self.current_account is None:
1275 account_items = self.wallet.accounts.items()
1276 elif self.current_account != -1:
1277 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1281 for k, account in account_items:
1282 name = self.wallet.get_account_name(k)
1283 c,u = self.wallet.get_account_balance(k)
1284 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1285 l.addTopLevelItem(account_item)
1286 account_item.setExpanded(self.accounts_expanded.get(k, True))
1287 account_item.setData(0, 32, k)
1289 if not self.wallet.is_seeded(k):
1290 icon = QIcon(":icons/key.png")
1291 account_item.setIcon(0, icon)
1293 for is_change in ([0,1]):
1294 name = _("Receiving") if not is_change else _("Change")
1295 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1296 account_item.addChild(seq_item)
1297 if not is_change: seq_item.setExpanded(True)
1302 for address in account.get_addresses(is_change):
1303 h = self.wallet.history.get(address,[])
1307 if gap > self.wallet.gap_limit:
1312 num_tx = '*' if h == ['*'] else "%d"%len(h)
1313 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1314 self.update_receive_item(item, len(h))
1316 item.setBackgroundColor(1, QColor('red'))
1317 seq_item.addChild(item)
1320 for k, addr in self.wallet.get_pending_accounts():
1321 name = self.wallet.labels.get(k,'')
1322 account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
1323 self.update_receive_item(item)
1324 l.addTopLevelItem(account_item)
1325 account_item.setExpanded(True)
1326 account_item.setData(0, 32, k)
1327 item = QTreeWidgetItem( [ addr, '', '', '', ''] )
1328 account_item.addChild(item)
1329 self.update_receive_item(item)
1332 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1333 c,u = self.wallet.get_imported_balance()
1334 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1335 l.addTopLevelItem(account_item)
1336 account_item.setExpanded(True)
1337 for address in self.wallet.imported_keys.keys():
1338 item = QTreeWidgetItem( [ address, '', '', ''] )
1339 self.update_receive_item(item)
1340 account_item.addChild(item)
1343 # we use column 1 because column 0 may be hidden
1344 l.setCurrentItem(l.topLevelItem(0),1)
1347 def update_contacts_tab(self):
1348 l = self.contacts_list
1351 for address in self.wallet.addressbook:
1352 label = self.wallet.labels.get(address,'')
1353 n = self.wallet.get_num_tx(address)
1354 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1355 item.setFont(0, QFont(MONOSPACE_FONT))
1356 # 32 = label can be edited (bool)
1357 item.setData(0,32, True)
1359 item.setData(0,33, address)
1360 l.addTopLevelItem(item)
1362 run_hook('update_contacts_tab', l)
1363 l.setCurrentItem(l.topLevelItem(0))
1367 def create_console_tab(self):
1368 from console import Console
1369 self.console = console = Console()
1373 def update_console(self):
1374 console = self.console
1375 console.history = self.config.get("console-history",[])
1376 console.history_index = len(console.history)
1378 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1379 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1381 c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1383 def mkfunc(f, method):
1384 return lambda *args: apply( f, (method, args, self.password_dialog ))
1386 if m[0]=='_' or m in ['network','wallet']: continue
1387 methods[m] = mkfunc(c._run, m)
1389 console.updateNamespace(methods)
1392 def change_account(self,s):
1393 if s == _("All accounts"):
1394 self.current_account = None
1396 accounts = self.wallet.get_account_names()
1397 for k, v in accounts.items():
1399 self.current_account = k
1400 self.update_history_tab()
1401 self.update_status()
1402 self.update_receive_tab()
1404 def create_status_bar(self):
1407 sb.setFixedHeight(35)
1408 qtVersion = qVersion()
1410 self.balance_label = QLabel("")
1411 sb.addWidget(self.balance_label)
1413 from version_getter import UpdateLabel
1414 self.updatelabel = UpdateLabel(self.config, sb)
1416 self.account_selector = QComboBox()
1417 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1418 sb.addPermanentWidget(self.account_selector)
1420 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1421 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1423 self.lock_icon = QIcon()
1424 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1425 sb.addPermanentWidget( self.password_button )
1427 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1428 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1429 sb.addPermanentWidget( self.seed_button )
1430 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1431 sb.addPermanentWidget( self.status_button )
1433 run_hook('create_status_bar', (sb,))
1435 self.setStatusBar(sb)
1438 def update_lock_icon(self):
1439 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1440 self.password_button.setIcon( icon )
1443 def update_buttons_on_seed(self):
1444 if not self.wallet.is_watching_only():
1445 self.seed_button.show()
1446 self.password_button.show()
1447 self.send_button.setText(_("Send"))
1449 self.password_button.hide()
1450 self.seed_button.hide()
1451 self.send_button.setText(_("Create unsigned transaction"))
1454 def change_password_dialog(self):
1455 from password_dialog import PasswordDialog
1456 d = PasswordDialog(self.wallet, self)
1458 self.update_lock_icon()
1461 def new_contact_dialog(self):
1464 vbox = QVBoxLayout(d)
1465 vbox.addWidget(QLabel(_('New Contact')+':'))
1467 grid = QGridLayout()
1470 grid.addWidget(QLabel(_("Address")), 1, 0)
1471 grid.addWidget(line1, 1, 1)
1472 grid.addWidget(QLabel(_("Name")), 2, 0)
1473 grid.addWidget(line2, 2, 1)
1475 vbox.addLayout(grid)
1476 vbox.addLayout(ok_cancel_buttons(d))
1481 address = str(line1.text())
1482 label = unicode(line2.text())
1484 if not is_valid(address):
1485 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1488 self.wallet.add_contact(address)
1490 self.wallet.set_label(address, label)
1492 self.update_contacts_tab()
1493 self.update_history_tab()
1494 self.update_completions()
1495 self.tabs.setCurrentIndex(3)
1498 def new_account_dialog(self):
1500 dialog = QDialog(self)
1502 dialog.setWindowTitle(_("New Account"))
1504 vbox = QVBoxLayout()
1505 vbox.addWidget(QLabel(_('Account name')+':'))
1508 msg = _("Note: Newly created accounts are 'pending' until they receive bitcoins.") + " " \
1509 + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1514 vbox.addLayout(ok_cancel_buttons(dialog))
1515 dialog.setLayout(vbox)
1519 name = str(e.text())
1522 self.wallet.create_pending_account('1', name)
1523 self.update_receive_tab()
1524 self.tabs.setCurrentIndex(2)
1528 def show_master_public_key_old(self):
1529 dialog = QDialog(self)
1531 dialog.setWindowTitle(_("Master Public Key"))
1533 main_text = QTextEdit()
1534 main_text.setText(self.wallet.get_master_public_key())
1535 main_text.setReadOnly(True)
1536 main_text.setMaximumHeight(170)
1537 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1539 ok_button = QPushButton(_("OK"))
1540 ok_button.setDefault(True)
1541 ok_button.clicked.connect(dialog.accept)
1543 main_layout = QGridLayout()
1544 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1546 main_layout.addWidget(main_text, 1, 0)
1547 main_layout.addWidget(qrw, 1, 1 )
1549 vbox = QVBoxLayout()
1550 vbox.addLayout(main_layout)
1551 vbox.addLayout(close_button(dialog))
1552 dialog.setLayout(vbox)
1556 def show_master_public_key(self):
1558 if self.wallet.seed_version == 4:
1559 self.show_master_public_key_old()
1562 dialog = QDialog(self)
1564 dialog.setWindowTitle(_("Master Public Keys"))
1566 chain_text = QTextEdit()
1567 chain_text.setReadOnly(True)
1568 chain_text.setMaximumHeight(170)
1569 chain_qrw = QRCodeWidget()
1571 mpk_text = QTextEdit()
1572 mpk_text.setReadOnly(True)
1573 mpk_text.setMaximumHeight(170)
1574 mpk_qrw = QRCodeWidget()
1576 main_layout = QGridLayout()
1578 main_layout.addWidget(QLabel(_('Key')), 1, 0)
1579 main_layout.addWidget(mpk_text, 1, 1)
1580 main_layout.addWidget(mpk_qrw, 1, 2)
1582 main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1583 main_layout.addWidget(chain_text, 2, 1)
1584 main_layout.addWidget(chain_qrw, 2, 2)
1587 c, K, cK = self.wallet.master_public_keys[str(key)]
1588 chain_text.setText(c)
1589 chain_qrw.set_addr(c)
1590 chain_qrw.update_qr()
1595 key_selector = QComboBox()
1596 keys = sorted(self.wallet.master_public_keys.keys())
1597 key_selector.addItems(keys)
1599 main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1600 main_layout.addWidget(key_selector, 0, 1)
1601 dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
1605 vbox = QVBoxLayout()
1606 vbox.addLayout(main_layout)
1607 vbox.addLayout(close_button(dialog))
1609 dialog.setLayout(vbox)
1614 def show_seed_dialog(self, password):
1615 if self.wallet.is_watching_only():
1616 QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1619 if self.wallet.seed:
1621 mnemonic = self.wallet.get_mnemonic(password)
1623 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1625 from seed_dialog import SeedDialog
1626 d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
1630 for k in self.wallet.master_private_keys.keys():
1631 pk = self.wallet.get_master_private_key(k, password)
1633 from seed_dialog import PrivateKeysDialog
1634 d = PrivateKeysDialog(self,l)
1641 def show_qrcode(self, data, title = _("QR code")):
1645 d.setWindowTitle(title)
1646 d.setMinimumSize(270, 300)
1647 vbox = QVBoxLayout()
1648 qrw = QRCodeWidget(data)
1649 vbox.addWidget(qrw, 1)
1650 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1651 hbox = QHBoxLayout()
1654 filename = os.path.join(self.config.path, "qrcode.bmp")
1657 bmp.save_qrcode(qrw.qr, filename)
1658 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1660 def copy_to_clipboard():
1661 bmp.save_qrcode(qrw.qr, filename)
1662 self.app.clipboard().setImage(QImage(filename))
1663 QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
1665 b = QPushButton(_("Copy"))
1667 b.clicked.connect(copy_to_clipboard)
1669 b = QPushButton(_("Save"))
1671 b.clicked.connect(print_qr)
1673 b = QPushButton(_("Close"))
1675 b.clicked.connect(d.accept)
1678 vbox.addLayout(hbox)
1683 def do_protect(self, func, args):
1684 if self.wallet.use_encryption:
1685 password = self.password_dialog()
1691 if args != (False,):
1692 args = (self,) + args + (password,)
1694 args = (self,password)
1699 def show_private_key(self, address, password):
1700 if not address: return
1702 pk_list = self.wallet.get_private_key(address, password)
1703 except Exception as e:
1704 self.show_message(str(e))
1706 QMessageBox.information(self, _('Private key'), _('Address')+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1710 def do_sign(self, address, message, signature, password):
1711 message = unicode(message.toPlainText())
1712 message = message.encode('utf-8')
1714 sig = self.wallet.sign_message(str(address.text()), message, password)
1715 signature.setText(sig)
1716 except Exception as e:
1717 self.show_message(str(e))
1719 def sign_message(self, address):
1720 if not address: return
1723 d.setWindowTitle(_('Sign Message'))
1724 d.setMinimumSize(410, 290)
1726 tab_widget = QTabWidget()
1728 layout = QGridLayout(tab)
1730 sign_address = QLineEdit()
1732 sign_address.setText(address)
1733 layout.addWidget(QLabel(_('Address')), 1, 0)
1734 layout.addWidget(sign_address, 1, 1)
1736 sign_message = QTextEdit()
1737 layout.addWidget(QLabel(_('Message')), 2, 0)
1738 layout.addWidget(sign_message, 2, 1)
1739 layout.setRowStretch(2,3)
1741 sign_signature = QTextEdit()
1742 layout.addWidget(QLabel(_('Signature')), 3, 0)
1743 layout.addWidget(sign_signature, 3, 1)
1744 layout.setRowStretch(3,1)
1747 hbox = QHBoxLayout()
1748 b = QPushButton(_("Sign"))
1750 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1751 b = QPushButton(_("Close"))
1752 b.clicked.connect(d.accept)
1754 layout.addLayout(hbox, 4, 1)
1755 tab_widget.addTab(tab, _("Sign"))
1759 layout = QGridLayout(tab)
1761 verify_address = QLineEdit()
1762 layout.addWidget(QLabel(_('Address')), 1, 0)
1763 layout.addWidget(verify_address, 1, 1)
1765 verify_message = QTextEdit()
1766 layout.addWidget(QLabel(_('Message')), 2, 0)
1767 layout.addWidget(verify_message, 2, 1)
1768 layout.setRowStretch(2,3)
1770 verify_signature = QTextEdit()
1771 layout.addWidget(QLabel(_('Signature')), 3, 0)
1772 layout.addWidget(verify_signature, 3, 1)
1773 layout.setRowStretch(3,1)
1776 message = unicode(verify_message.toPlainText())
1777 message = message.encode('utf-8')
1778 if bitcoin.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1779 self.show_message(_("Signature verified"))
1781 self.show_message(_("Error: wrong signature"))
1783 hbox = QHBoxLayout()
1784 b = QPushButton(_("Verify"))
1785 b.clicked.connect(do_verify)
1787 b = QPushButton(_("Close"))
1788 b.clicked.connect(d.accept)
1790 layout.addLayout(hbox, 4, 1)
1791 tab_widget.addTab(tab, _("Verify"))
1793 vbox = QVBoxLayout()
1794 vbox.addWidget(tab_widget)
1801 def question(self, msg):
1802 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1804 def show_message(self, msg):
1805 QMessageBox.information(self, _('Message'), msg, _('OK'))
1807 def password_dialog(self ):
1814 vbox = QVBoxLayout()
1815 msg = _('Please enter your password')
1816 vbox.addWidget(QLabel(msg))
1818 grid = QGridLayout()
1820 grid.addWidget(QLabel(_('Password')), 1, 0)
1821 grid.addWidget(pw, 1, 1)
1822 vbox.addLayout(grid)
1824 vbox.addLayout(ok_cancel_buttons(d))
1827 run_hook('password_dialog', pw, grid, 1)
1828 if not d.exec_(): return
1829 return unicode(pw.text())
1838 def tx_from_text(self, txt):
1839 "json or raw hexadecimal"
1842 tx = Transaction(txt)
1848 tx_dict = json.loads(str(txt))
1849 assert "hex" in tx_dict.keys()
1850 assert "complete" in tx_dict.keys()
1851 tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1852 if not tx_dict["complete"]:
1853 assert "input_info" in tx_dict.keys()
1854 input_info = json.loads(tx_dict['input_info'])
1855 tx.add_input_info(input_info)
1860 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1864 def read_tx_from_file(self):
1865 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1869 with open(fileName, "r") as f:
1870 file_content = f.read()
1871 except (ValueError, IOError, os.error), reason:
1872 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1874 return self.tx_from_text(file_content)
1878 def sign_raw_transaction(self, tx, input_info, password):
1879 self.wallet.signrawtransaction(tx, input_info, [], password)
1881 def do_process_from_text(self):
1882 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1885 tx = self.tx_from_text(text)
1887 self.show_transaction(tx)
1889 def do_process_from_file(self):
1890 tx = self.read_tx_from_file()
1892 self.show_transaction(tx)
1894 def do_process_from_csvReader(self, csvReader):
1897 for row in csvReader:
1899 amount = float(row[1])
1900 amount = int(100000000*amount)
1901 outputs.append((address, amount))
1902 except (ValueError, IOError, os.error), reason:
1903 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1907 tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1908 except Exception as e:
1909 self.show_message(str(e))
1912 self.show_transaction(tx)
1914 def do_process_from_csv_file(self):
1915 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1919 with open(fileName, "r") as f:
1920 csvReader = csv.reader(f)
1921 self.do_process_from_csvReader(csvReader)
1922 except (ValueError, IOError, os.error), reason:
1923 QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1926 def do_process_from_csv_text(self):
1927 text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1928 + _("Format: address, amount. One output per line"), _("Load CSV"))
1931 f = StringIO.StringIO(text)
1932 csvReader = csv.reader(f)
1933 self.do_process_from_csvReader(csvReader)
1938 def do_export_privkeys(self, password):
1939 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.")))
1942 select_export = _('Select file to export your private keys to')
1943 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1945 with open(fileName, "w+") as csvfile:
1946 transaction = csv.writer(csvfile)
1947 transaction.writerow(["address", "private_key"])
1949 addresses = self.wallet.addresses(True)
1951 for addr in addresses:
1952 pk = "".join(self.wallet.get_private_key(addr, password))
1953 transaction.writerow(["%34s"%addr,pk])
1955 self.show_message(_("Private keys exported."))
1957 except (IOError, os.error), reason:
1958 export_error_label = _("Electrum was unable to produce a private key-export.")
1959 QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1961 except Exception as e:
1962 self.show_message(str(e))
1966 def do_import_labels(self):
1967 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1968 if not labelsFile: return
1970 f = open(labelsFile, 'r')
1973 for key, value in json.loads(data).items():
1974 self.wallet.set_label(key, value)
1975 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1976 except (IOError, os.error), reason:
1977 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1980 def do_export_labels(self):
1981 labels = self.wallet.labels
1983 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1985 with open(fileName, 'w+') as f:
1986 json.dump(labels, f)
1987 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1988 except (IOError, os.error), reason:
1989 QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1992 def do_export_history(self):
1993 from lite_window import csv_transaction
1994 csv_transaction(self.wallet)
1998 def do_import_privkey(self, password):
1999 if not self.wallet.imported_keys:
2000 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2001 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2002 + _('Are you sure you understand what you are doing?'), 3, 4)
2005 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2008 text = str(text).split()
2013 addr = self.wallet.import_key(key, password)
2014 except Exception as e:
2020 addrlist.append(addr)
2022 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2024 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2025 self.update_receive_tab()
2026 self.update_history_tab()
2029 def settings_dialog(self):
2031 d.setWindowTitle(_('Electrum Settings'))
2033 vbox = QVBoxLayout()
2034 grid = QGridLayout()
2035 grid.setColumnStretch(0,1)
2037 nz_label = QLabel(_('Display zeros') + ':')
2038 grid.addWidget(nz_label, 0, 0)
2039 nz_e = AmountEdit(None,True)
2040 nz_e.setText("%d"% self.num_zeros)
2041 grid.addWidget(nz_e, 0, 1)
2042 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2043 grid.addWidget(HelpButton(msg), 0, 2)
2044 if not self.config.is_modifiable('num_zeros'):
2045 for w in [nz_e, nz_label]: w.setEnabled(False)
2047 lang_label=QLabel(_('Language') + ':')
2048 grid.addWidget(lang_label, 1, 0)
2049 lang_combo = QComboBox()
2050 from electrum.i18n import languages
2051 lang_combo.addItems(languages.values())
2053 index = languages.keys().index(self.config.get("language",''))
2056 lang_combo.setCurrentIndex(index)
2057 grid.addWidget(lang_combo, 1, 1)
2058 grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2059 if not self.config.is_modifiable('language'):
2060 for w in [lang_combo, lang_label]: w.setEnabled(False)
2063 fee_label = QLabel(_('Transaction fee') + ':')
2064 grid.addWidget(fee_label, 2, 0)
2065 fee_e = AmountEdit(self.base_unit)
2066 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2067 grid.addWidget(fee_e, 2, 1)
2068 msg = _('Fee per kilobyte of transaction.') + ' ' \
2069 + _('Recommended value') + ': ' + self.format_amount(20000)
2070 grid.addWidget(HelpButton(msg), 2, 2)
2071 if not self.config.is_modifiable('fee_per_kb'):
2072 for w in [fee_e, fee_label]: w.setEnabled(False)
2074 units = ['BTC', 'mBTC']
2075 unit_label = QLabel(_('Base unit') + ':')
2076 grid.addWidget(unit_label, 3, 0)
2077 unit_combo = QComboBox()
2078 unit_combo.addItems(units)
2079 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2080 grid.addWidget(unit_combo, 3, 1)
2081 grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2082 + '\n1BTC=1000mBTC.\n' \
2083 + _(' This settings affects the fields in the Send tab')+' '), 3, 2)
2085 usechange_cb = QCheckBox(_('Use change addresses'))
2086 usechange_cb.setChecked(self.wallet.use_change)
2087 grid.addWidget(usechange_cb, 4, 0)
2088 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2089 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2091 grid.setRowStretch(5,1)
2093 vbox.addLayout(grid)
2094 vbox.addLayout(ok_cancel_buttons(d))
2098 if not d.exec_(): return
2100 fee = unicode(fee_e.text())
2102 fee = self.read_amount(fee)
2104 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2107 self.wallet.set_fee(fee)
2109 nz = unicode(nz_e.text())
2114 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2117 if self.num_zeros != nz:
2119 self.config.set_key('num_zeros', nz, True)
2120 self.update_history_tab()
2121 self.update_receive_tab()
2123 usechange_result = usechange_cb.isChecked()
2124 if self.wallet.use_change != usechange_result:
2125 self.wallet.use_change = usechange_result
2126 self.wallet.storage.put('use_change', self.wallet.use_change)
2128 unit_result = units[unit_combo.currentIndex()]
2129 if self.base_unit() != unit_result:
2130 self.decimal_point = 8 if unit_result == 'BTC' else 5
2131 self.config.set_key('decimal_point', self.decimal_point, True)
2132 self.update_history_tab()
2133 self.update_status()
2135 need_restart = False
2137 lang_request = languages.keys()[lang_combo.currentIndex()]
2138 if lang_request != self.config.get('language'):
2139 self.config.set_key("language", lang_request, True)
2142 run_hook('close_settings_dialog')
2145 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2148 def run_network_dialog(self):
2149 if not self.network:
2151 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2153 def closeEvent(self, event):
2155 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2156 self.save_column_widths()
2157 self.config.set_key("console-history", self.console.history[-50:], True)
2158 self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2163 def plugins_dialog(self):
2164 from electrum.plugins import plugins
2167 d.setWindowTitle(_('Electrum Plugins'))
2170 vbox = QVBoxLayout(d)
2173 scroll = QScrollArea()
2174 scroll.setEnabled(True)
2175 scroll.setWidgetResizable(True)
2176 scroll.setMinimumSize(400,250)
2177 vbox.addWidget(scroll)
2181 w.setMinimumHeight(len(plugins)*35)
2183 grid = QGridLayout()
2184 grid.setColumnStretch(0,1)
2187 def do_toggle(cb, p, w):
2190 if w: w.setEnabled(r)
2192 def mk_toggle(cb, p, w):
2193 return lambda: do_toggle(cb,p,w)
2195 for i, p in enumerate(plugins):
2197 cb = QCheckBox(p.fullname())
2198 cb.setDisabled(not p.is_available())
2199 cb.setChecked(p.is_enabled())
2200 grid.addWidget(cb, i, 0)
2201 if p.requires_settings():
2202 w = p.settings_widget(self)
2203 w.setEnabled( p.is_enabled() )
2204 grid.addWidget(w, i, 1)
2207 cb.clicked.connect(mk_toggle(cb,p,w))
2208 grid.addWidget(HelpButton(p.description()), i, 2)
2210 print_msg(_("Error: cannot display plugin"), p)
2211 traceback.print_exc(file=sys.stdout)
2212 grid.setRowStretch(i+1,1)
2214 vbox.addLayout(close_button(d))
2219 def show_account_details(self, k):
2221 d.setWindowTitle(_('Account Details'))
2224 vbox = QVBoxLayout(d)
2225 roots = self.wallet.get_roots(k)
2227 name = self.wallet.get_account_name(k)
2228 label = QLabel('Name: ' + name)
2229 vbox.addWidget(label)
2231 acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2232 vbox.addWidget(QLabel('Type: ' + acctype))
2234 label = QLabel('Derivation: ' + k)
2235 vbox.addWidget(label)
2238 # mpk = self.wallet.master_public_keys[root]
2239 # text = QTextEdit()
2240 # text.setReadOnly(True)
2241 # text.setMaximumHeight(120)
2242 # text.setText(repr(mpk))
2243 # vbox.addWidget(text)
2245 vbox.addLayout(close_button(d))