1
0

qt: refactor TxEditor preferences: use QMenuWithConfig

This commit is contained in:
SomberNight
2025-03-14 15:33:58 +00:00
parent e62a2c43c5
commit 656c109336
2 changed files with 70 additions and 106 deletions

View File

@@ -29,7 +29,6 @@ from typing import TYPE_CHECKING, Optional, Union, Callable
from PyQt6.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt6.QtGui import QIcon from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QGridLayout, QPushButton, QToolButton, QMenu, QComboBox from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QGridLayout, QPushButton, QToolButton, QMenu, QComboBox
from electrum.i18n import _ from electrum.i18n import _
@@ -38,21 +37,19 @@ from electrum.util import quantize_feerate
from electrum.plugin import run_hook from electrum.plugin import run_hook
from electrum.transaction import Transaction, PartialTransaction from electrum.transaction import Transaction, PartialTransaction
from electrum.wallet import InternalAddressCorruption from electrum.wallet import InternalAddressCorruption
from electrum.simple_config import SimpleConfig
from electrum.bitcoin import DummyAddress from electrum.bitcoin import DummyAddress
from electrum.fee_policy import FeePolicy, FixedFeePolicy from electrum.fee_policy import FeePolicy, FixedFeePolicy
from .util import (WindowModalDialog, ColorScheme, HelpLabel, Buttons, CancelButton, from .util import (WindowModalDialog, ColorScheme, HelpLabel, Buttons, CancelButton,
WWLabel, read_QIcon) 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 .transaction_dialog import TxSizeLabel, TxFiatLabel, TxInOutWidget
from .fee_slider import FeeSlider, FeeComboBox from .fee_slider import FeeSlider, FeeComboBox
from .amountedit import FeerateEdit, BTCAmountEdit from .amountedit import FeerateEdit, BTCAmountEdit
from .locktimeedit import LockTimeEdit from .locktimeedit import LockTimeEdit
from .my_treeview import QMenuWithConfig
if TYPE_CHECKING:
from .main_window import ElectrumWindow
class TxEditor(WindowModalDialog): class TxEditor(WindowModalDialog):
@@ -112,8 +109,8 @@ class TxEditor(WindowModalDialog):
vbox.addLayout(buttons) vbox.addLayout(buttons)
self.set_io_visible() self.set_io_visible()
self.set_fee_edit_visible(self.config.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS) self.set_fee_edit_visible()
self.set_locktime_visible(self.config.GUI_QT_TX_EDITOR_SHOW_LOCKTIME) self.set_locktime_visible()
self.update_fee_target() self.update_fee_target()
self.resize(self.layout().sizeHint()) self.resize(self.layout().sizeHint())
@@ -380,42 +377,37 @@ class TxEditor(WindowModalDialog):
buttons.insertWidget(0, batching_combo) buttons.insertWidget(0, batching_combo)
def on_batching_combo(x): def on_batching_combo(x):
self._base_tx = self.batching_candidates[x - 1] if x > 0 else None 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) batching_combo.currentIndexChanged.connect(on_batching_combo)
return buttons return buttons
def create_top_bar(self, text): def create_top_bar(self, text):
self.pref_menu = QMenu() self.pref_menu = QMenuWithConfig(self.config)
self.pref_menu.setToolTipsVisible(True)
def add_pref_action(b, action, text, tooltip): def cb():
m = self.pref_menu.addAction(text, action) self.set_io_visible()
m.setCheckable(True) self.resize_to_fit_content()
m.setChecked(b) self.pref_menu.addConfig(self.config.cv.GUI_QT_TX_EDITOR_SHOW_IO, callback=cb)
m.setToolTip(tooltip) def cb():
return m self.set_fee_edit_visible()
self.resize_to_fit_content()
def add_cv_action(configvar: 'ConfigVarWithConfig', action: Callable[[], None]): self.pref_menu.addConfig(self.config.cv.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS, callback=cb)
b = configvar.get() def cb():
short_desc = configvar.get_short_desc() self.set_locktime_visible()
assert short_desc is not None, f"short_desc missing for {configvar}" self.resize_to_fit_content()
tooltip = configvar.get_long_desc() or "" self.pref_menu.addConfig(self.config.cv.GUI_QT_TX_EDITOR_SHOW_LOCKTIME, callback=cb)
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)
self.pref_menu.addSeparator() self.pref_menu.addSeparator()
add_cv_action(self.config.cv.WALLET_SEND_CHANGE_TO_LIGHTNING, self.toggle_send_change_to_lightning) self.pref_menu.addConfig(self.config.cv.WALLET_SEND_CHANGE_TO_LIGHTNING, callback=self.trigger_update)
add_pref_action( self.pref_menu.addToggle(
self.wallet.use_change,
self.toggle_use_change,
_('Use change addresses'), _('Use change addresses'),
_('Using change addresses makes it more difficult for other people to track your transactions.')) self.toggle_use_change,
self.use_multi_change_menu = add_pref_action( default_state=self.wallet.use_change,
self.wallet.multiple_change, self.toggle_multiple_change, tooltip=_('Using change addresses makes it more difficult for other people to track your transactions.'))
_('Use multiple change addresses',), self.use_multi_change_menu = self.pref_menu.addToggle(
'\n'.join([ _('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 ' _('In some cases, use up to 3 change addresses in order to break '
'up large coin amounts and obfuscate the recipient address.'), 'up large coin amounts and obfuscate the recipient address.'),
_('This may result in higher transactions fees.') _('This may result in higher transactions fees.')
@@ -423,10 +415,14 @@ class TxEditor(WindowModalDialog):
self.use_multi_change_menu.setEnabled(self.wallet.use_change) self.use_multi_change_menu.setEnabled(self.wallet.use_change)
# fixme: some of these options (WALLET_SEND_CHANGE_TO_LIGHTNING, WALLET_MERGE_DUPLICATE_OUTPUTS) # 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 # 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) self.pref_menu.addConfig(self.config.cv.WALLET_MERGE_DUPLICATE_OUTPUTS, callback=self.trigger_update)
add_cv_action(self.config.cv.WALLET_SPEND_CONFIRMED_ONLY, self.toggle_confirmed_only) self.pref_menu.addConfig(self.config.cv.WALLET_SPEND_CONFIRMED_ONLY, callback=self.trigger_update)
add_cv_action(self.config.cv.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING, self.toggle_output_rounding) self.pref_menu.addConfig(self.config.cv.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING, callback=self.trigger_update)
add_cv_action(self.config.cv.WALLET_FREEZE_REUSED_ADDRESS_UTXOS, self.toggle_freeze_reused_address_utxos) 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 = QToolButton()
self.pref_button.setIcon(read_QIcon("preferences.png")) self.pref_button.setIcon(read_QIcon("preferences.png"))
self.pref_button.setMenu(self.pref_menu) self.pref_button.setMenu(self.pref_menu)
@@ -444,18 +440,6 @@ class TxEditor(WindowModalDialog):
self.resize(size) self.resize(size)
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): def toggle_use_change(self):
self.wallet.use_change = not self.wallet.use_change self.wallet.use_change = not self.wallet.use_change
self.wallet.db.put('use_change', 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.wallet.db.put('multiple_change', self.wallet.multiple_change)
self.trigger_update() 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): def set_io_visible(self):
self.io_widget.setVisible(self.config.GUI_QT_TX_EDITOR_SHOW_IO) 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] detailed = [self.feerounding_icon, self.feerate_e, self.fee_e]
basic = [self.fee_label, self.feerate_label] basic = [self.fee_label, self.feerate_label]
# first hide, then show # first hide, then show
@@ -514,7 +464,8 @@ class TxEditor(WindowModalDialog):
for w in (detailed if b else basic): for w in (detailed if b else basic):
w.show() 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 [ for w in [
self.locktime_e, self.locktime_e,
self.locktime_label]: self.locktime_label]:

View File

@@ -26,7 +26,7 @@
import enum import enum
from decimal import Decimal from decimal import Decimal
from typing import (Optional, TYPE_CHECKING, Union, List, Dict, Any, from typing import (Optional, TYPE_CHECKING, Union, List, Dict, Any,
Sequence, Iterable, Type) Sequence, Iterable, Type, Callable)
from PyQt6.QtGui import (QStandardItem, QStandardItemModel, from PyQt6.QtGui import (QStandardItem, QStandardItemModel,
QShowEvent, QPainter, QHelpEvent, QMouseEvent, QAction) QShowEvent, QPainter, QHelpEvent, QMouseEvent, QAction)
@@ -36,12 +36,7 @@ from PyQt6.QtWidgets import (QLabel, QHBoxLayout, QAbstractItemView, QLineEdit,
QWidget, QToolButton, QTreeView, QHeaderView, QStyledItemDelegate, QWidget, QToolButton, QTreeView, QHeaderView, QStyledItemDelegate,
QMenu, QStyleOptionViewItem) QMenu, QStyleOptionViewItem)
from electrum.i18n import _, languages from electrum.i18n import _
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.simple_config import ConfigVarWithConfig from electrum.simple_config import ConfigVarWithConfig
from electrum.gui import messages from electrum.gui import messages
@@ -60,9 +55,18 @@ class QMenuWithConfig(QMenu):
self.setToolTipsVisible(True) self.setToolTipsVisible(True)
self.config = config 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 = self.addAction(text, callback)
m.setCheckable(True) m.setCheckable(True)
m.setChecked(default_state)
tooltip = tooltip or ""
m.setToolTip(tooltip) m.setToolTip(tooltip)
return m return m
@@ -70,7 +74,7 @@ class QMenuWithConfig(QMenu):
self, self,
configvar: 'ConfigVarWithConfig', configvar: 'ConfigVarWithConfig',
*, *,
callback=None, callback: Optional[Callable[[], None]] = None,
checked: Optional[bool] = None, # to override initial state of checkbox checked: Optional[bool] = None, # to override initial state of checkbox
short_desc: Optional[str] = None, short_desc: Optional[str] = None,
) -> QAction: ) -> QAction:
@@ -80,16 +84,25 @@ class QMenuWithConfig(QMenu):
assert short_desc is not None, f"short_desc missing for {configvar}" assert short_desc is not None, f"short_desc missing for {configvar}"
if checked is None: if checked is None:
checked = bool(configvar.get()) checked = bool(configvar.get())
m = self.addAction(short_desc, lambda: self._do_toggle_config(configvar, callback=callback)) tooltip = None
m.setCheckable(True)
m.setChecked(checked)
if (long_desc := configvar.get_long_desc()) is not None: if (long_desc := configvar.get_long_desc()) is not None:
m.setToolTip(messages.to_rtf(long_desc)) tooltip = messages.to_rtf(long_desc)
return m 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() b = configvar.get()
configvar.set(not b) configvar.set(not b)
# call cb after configvar state is updated:
if callback: if callback:
callback() callback()