From 656c109336d9aaf69740a761c5a02d701b736dde Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 14 Mar 2025 15:33:58 +0000 Subject: [PATCH] qt: refactor TxEditor preferences: use QMenuWithConfig --- electrum/gui/qt/confirm_tx_dialog.py | 133 +++++++++------------------ electrum/gui/qt/my_treeview.py | 43 ++++++--- 2 files changed, 70 insertions(+), 106 deletions(-) diff --git a/electrum/gui/qt/confirm_tx_dialog.py b/electrum/gui/qt/confirm_tx_dialog.py index 6e85e5ff9..61da43266 100644 --- a/electrum/gui/qt/confirm_tx_dialog.py +++ b/electrum/gui/qt/confirm_tx_dialog.py @@ -29,7 +29,6 @@ from typing import TYPE_CHECKING, Optional, Union, Callable from PyQt6.QtCore import Qt from PyQt6.QtGui import QIcon - from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QGridLayout, QPushButton, QToolButton, QMenu, QComboBox from electrum.i18n import _ @@ -38,21 +37,19 @@ from electrum.util import quantize_feerate from electrum.plugin import run_hook from electrum.transaction import Transaction, PartialTransaction from electrum.wallet import InternalAddressCorruption -from electrum.simple_config import SimpleConfig from electrum.bitcoin import DummyAddress from electrum.fee_policy import FeePolicy, FixedFeePolicy from .util import (WindowModalDialog, ColorScheme, HelpLabel, Buttons, CancelButton, WWLabel, read_QIcon) - -if TYPE_CHECKING: - from electrum.simple_config import ConfigVarWithConfig - from .main_window import ElectrumWindow - from .transaction_dialog import TxSizeLabel, TxFiatLabel, TxInOutWidget from .fee_slider import FeeSlider, FeeComboBox from .amountedit import FeerateEdit, BTCAmountEdit from .locktimeedit import LockTimeEdit +from .my_treeview import QMenuWithConfig + +if TYPE_CHECKING: + from .main_window import ElectrumWindow class TxEditor(WindowModalDialog): @@ -112,8 +109,8 @@ class TxEditor(WindowModalDialog): vbox.addLayout(buttons) self.set_io_visible() - self.set_fee_edit_visible(self.config.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS) - self.set_locktime_visible(self.config.GUI_QT_TX_EDITOR_SHOW_LOCKTIME) + self.set_fee_edit_visible() + self.set_locktime_visible() self.update_fee_target() self.resize(self.layout().sizeHint()) @@ -380,42 +377,37 @@ class TxEditor(WindowModalDialog): buttons.insertWidget(0, batching_combo) def on_batching_combo(x): self._base_tx = self.batching_candidates[x - 1] if x > 0 else None - self.update_batching() + self.trigger_update() batching_combo.currentIndexChanged.connect(on_batching_combo) return buttons def create_top_bar(self, text): - self.pref_menu = QMenu() - self.pref_menu.setToolTipsVisible(True) + self.pref_menu = QMenuWithConfig(self.config) - def add_pref_action(b, action, text, tooltip): - m = self.pref_menu.addAction(text, action) - m.setCheckable(True) - m.setChecked(b) - m.setToolTip(tooltip) - return m - - def add_cv_action(configvar: 'ConfigVarWithConfig', action: Callable[[], None]): - b = configvar.get() - short_desc = configvar.get_short_desc() - assert short_desc is not None, f"short_desc missing for {configvar}" - tooltip = configvar.get_long_desc() or "" - return add_pref_action(b, action, short_desc, tooltip) - - add_cv_action(self.config.cv.GUI_QT_TX_EDITOR_SHOW_IO, self.toggle_io_visibility) - add_cv_action(self.config.cv.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS, self.toggle_fee_details) - add_cv_action(self.config.cv.GUI_QT_TX_EDITOR_SHOW_LOCKTIME, self.toggle_locktime) + def cb(): + self.set_io_visible() + self.resize_to_fit_content() + self.pref_menu.addConfig(self.config.cv.GUI_QT_TX_EDITOR_SHOW_IO, callback=cb) + def cb(): + self.set_fee_edit_visible() + self.resize_to_fit_content() + self.pref_menu.addConfig(self.config.cv.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS, callback=cb) + def cb(): + self.set_locktime_visible() + self.resize_to_fit_content() + self.pref_menu.addConfig(self.config.cv.GUI_QT_TX_EDITOR_SHOW_LOCKTIME, callback=cb) self.pref_menu.addSeparator() - add_cv_action(self.config.cv.WALLET_SEND_CHANGE_TO_LIGHTNING, self.toggle_send_change_to_lightning) - add_pref_action( - self.wallet.use_change, - self.toggle_use_change, + self.pref_menu.addConfig(self.config.cv.WALLET_SEND_CHANGE_TO_LIGHTNING, callback=self.trigger_update) + self.pref_menu.addToggle( _('Use change addresses'), - _('Using change addresses makes it more difficult for other people to track your transactions.')) - self.use_multi_change_menu = add_pref_action( - self.wallet.multiple_change, self.toggle_multiple_change, - _('Use multiple change addresses',), - '\n'.join([ + self.toggle_use_change, + default_state=self.wallet.use_change, + tooltip=_('Using change addresses makes it more difficult for other people to track your transactions.')) + self.use_multi_change_menu = self.pref_menu.addToggle( + _('Use multiple change addresses'), + self.toggle_multiple_change, + default_state=self.wallet.multiple_change, + tooltip='\n'.join([ _('In some cases, use up to 3 change addresses in order to break ' 'up large coin amounts and obfuscate the recipient address.'), _('This may result in higher transactions fees.') @@ -423,10 +415,14 @@ class TxEditor(WindowModalDialog): self.use_multi_change_menu.setEnabled(self.wallet.use_change) # fixme: some of these options (WALLET_SEND_CHANGE_TO_LIGHTNING, WALLET_MERGE_DUPLICATE_OUTPUTS) # only make sense when we create a new tx, and should not be visible/enabled in rbf dialog - add_cv_action(self.config.cv.WALLET_MERGE_DUPLICATE_OUTPUTS, self.toggle_merge_duplicate_outputs) - add_cv_action(self.config.cv.WALLET_SPEND_CONFIRMED_ONLY, self.toggle_confirmed_only) - add_cv_action(self.config.cv.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING, self.toggle_output_rounding) - add_cv_action(self.config.cv.WALLET_FREEZE_REUSED_ADDRESS_UTXOS, self.toggle_freeze_reused_address_utxos) + self.pref_menu.addConfig(self.config.cv.WALLET_MERGE_DUPLICATE_OUTPUTS, callback=self.trigger_update) + self.pref_menu.addConfig(self.config.cv.WALLET_SPEND_CONFIRMED_ONLY, callback=self.trigger_update) + self.pref_menu.addConfig(self.config.cv.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING, callback=self.trigger_update) + def cb(): + self.trigger_update() + self.main_window.utxo_list.refresh_all() # for coin frozen status + self.main_window.update_status() # frozen balance + self.pref_menu.addConfig(self.config.cv.WALLET_FREEZE_REUSED_ADDRESS_UTXOS, callback=cb) self.pref_button = QToolButton() self.pref_button.setIcon(read_QIcon("preferences.png")) self.pref_button.setMenu(self.pref_menu) @@ -444,18 +440,6 @@ class TxEditor(WindowModalDialog): self.resize(size) self.resize(size) - def toggle_output_rounding(self): - b = not self.config.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING - self.config.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING = b - self.trigger_update() - - def toggle_freeze_reused_address_utxos(self): - b = not self.config.WALLET_FREEZE_REUSED_ADDRESS_UTXOS - self.config.WALLET_FREEZE_REUSED_ADDRESS_UTXOS = b - self.trigger_update() - self.main_window.utxo_list.refresh_all() # for coin frozen status - self.main_window.update_status() # frozen balance - def toggle_use_change(self): self.wallet.use_change = not self.wallet.use_change self.wallet.db.put('use_change', self.wallet.use_change) @@ -467,45 +451,11 @@ class TxEditor(WindowModalDialog): self.wallet.db.put('multiple_change', self.wallet.multiple_change) self.trigger_update() - def update_batching(self): - self.trigger_update() - - def toggle_merge_duplicate_outputs(self): - b = not self.config.WALLET_MERGE_DUPLICATE_OUTPUTS - self.config.WALLET_MERGE_DUPLICATE_OUTPUTS = b - self.trigger_update() - - def toggle_send_change_to_lightning(self): - b = not self.config.WALLET_SEND_CHANGE_TO_LIGHTNING - self.config.WALLET_SEND_CHANGE_TO_LIGHTNING = b - self.trigger_update() - - def toggle_confirmed_only(self): - b = not self.config.WALLET_SPEND_CONFIRMED_ONLY - self.config.WALLET_SPEND_CONFIRMED_ONLY = b - self.trigger_update() - - def toggle_io_visibility(self): - self.config.GUI_QT_TX_EDITOR_SHOW_IO = not self.config.GUI_QT_TX_EDITOR_SHOW_IO - self.set_io_visible() - self.resize_to_fit_content() - - def toggle_fee_details(self): - b = not self.config.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS - self.config.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS = b - self.set_fee_edit_visible(b) - self.resize_to_fit_content() - - def toggle_locktime(self): - b = not self.config.GUI_QT_TX_EDITOR_SHOW_LOCKTIME - self.config.GUI_QT_TX_EDITOR_SHOW_LOCKTIME = b - self.set_locktime_visible(b) - self.resize_to_fit_content() - def set_io_visible(self): self.io_widget.setVisible(self.config.GUI_QT_TX_EDITOR_SHOW_IO) - def set_fee_edit_visible(self, b): + def set_fee_edit_visible(self): + b = self.config.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS detailed = [self.feerounding_icon, self.feerate_e, self.fee_e] basic = [self.fee_label, self.feerate_label] # first hide, then show @@ -514,7 +464,8 @@ class TxEditor(WindowModalDialog): for w in (detailed if b else basic): w.show() - def set_locktime_visible(self, b): + def set_locktime_visible(self): + b = self.config.GUI_QT_TX_EDITOR_SHOW_LOCKTIME for w in [ self.locktime_e, self.locktime_label]: diff --git a/electrum/gui/qt/my_treeview.py b/electrum/gui/qt/my_treeview.py index 791033778..363a15198 100644 --- a/electrum/gui/qt/my_treeview.py +++ b/electrum/gui/qt/my_treeview.py @@ -26,7 +26,7 @@ import enum from decimal import Decimal from typing import (Optional, TYPE_CHECKING, Union, List, Dict, Any, - Sequence, Iterable, Type) + Sequence, Iterable, Type, Callable) from PyQt6.QtGui import (QStandardItem, QStandardItemModel, QShowEvent, QPainter, QHelpEvent, QMouseEvent, QAction) @@ -36,12 +36,7 @@ from PyQt6.QtWidgets import (QLabel, QHBoxLayout, QAbstractItemView, QLineEdit, QWidget, QToolButton, QTreeView, QHeaderView, QStyledItemDelegate, QMenu, QStyleOptionViewItem) -from electrum.i18n import _, languages -from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, resource_path -from electrum.util import EventListener, event_listener -from electrum.invoices import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING, PR_UNCONFIRMED -from electrum.logging import Logger -from electrum.qrreader import MissingQrDetectionLib +from electrum.i18n import _ from electrum.simple_config import ConfigVarWithConfig from electrum.gui import messages @@ -60,9 +55,18 @@ class QMenuWithConfig(QMenu): self.setToolTipsVisible(True) self.config = config - def addToggle(self, text: str, callback, *, tooltip='') -> QAction: + def addToggle( + self, + text: str, + callback: Callable[[], None], + *, + tooltip: Optional[str] = None, + default_state: bool = False, + ) -> QAction: m = self.addAction(text, callback) m.setCheckable(True) + m.setChecked(default_state) + tooltip = tooltip or "" m.setToolTip(tooltip) return m @@ -70,7 +74,7 @@ class QMenuWithConfig(QMenu): self, configvar: 'ConfigVarWithConfig', *, - callback=None, + callback: Optional[Callable[[], None]] = None, checked: Optional[bool] = None, # to override initial state of checkbox short_desc: Optional[str] = None, ) -> QAction: @@ -80,16 +84,25 @@ class QMenuWithConfig(QMenu): assert short_desc is not None, f"short_desc missing for {configvar}" if checked is None: checked = bool(configvar.get()) - m = self.addAction(short_desc, lambda: self._do_toggle_config(configvar, callback=callback)) - m.setCheckable(True) - m.setChecked(checked) + tooltip = None if (long_desc := configvar.get_long_desc()) is not None: - m.setToolTip(messages.to_rtf(long_desc)) - return m + tooltip = messages.to_rtf(long_desc) + return self.addToggle( + short_desc, + lambda: self._do_toggle_config(configvar, callback=callback), + tooltip=tooltip, + default_state=checked, + ) - def _do_toggle_config(self, configvar: 'ConfigVarWithConfig', *, callback): + def _do_toggle_config( + self, + configvar: 'ConfigVarWithConfig', + *, + callback: Optional[Callable[[], None]] = None, + ): b = configvar.get() configvar.set(not b) + # call cb after configvar state is updated: if callback: callback()