feat(bal): Group C settings-dialog improvements (editable dates, narrower RAW box, warning/reset/support, tooltips, bold locktime)
C2 - 'Editable dates' option (default OFF):
- new EDITABLE_DATES config; checkbox in settings dialog
- WillSettingsWidget.apply_editable_dates() re-locks/unlocks the
delivery-time and check-alive fields, called from update_all() so the
toggle takes effect immediately (same mechanism as Hide Invalidated)
- EDITABLE_DATES included in the Reset-to-defaults list
C3 - RAW date box narrowed to ~1/3 (input box 6 chars); the trailing
label that shows the computed absolute date kept at 10 chars (fixed a
truncation regression)
C4 - settings dialog:
(a) bold red warning at the top
(b) 'Reset setting' button restoring all 7 dialog settings to defaults
(single source of truth: BalConfig.default), not touching wills
(c) bold 'Support: bitcoin-after.life' link (opens via webopen)
C5 - tooltips: calendar 'Export reminder dates to your calendar (.ics)',
fee field 'Mining fee rate in sat/vByte used for the will transactions',
fee '丰' icon 'Miner fee, click for more information' (tooltip font scoped
to QPushButton so it matches the other tooltips)
C6 - Locktime column rendered in bold in the will list
tests/test_group_c_settings.py: 4 new tests (EDITABLE_DATES default/toggle,
reset restores all dialog settings incl. EDITABLE_DATES, reset leaves
unrelated config untouched). Full suite: 210 passed.
This commit is contained in:
131
CHANGELOG.md
131
CHANGELOG.md
@@ -378,3 +378,134 @@ MEMPOOL (yellow `#ffce30`).
|
||||
- Full test suite: `206 passed`.
|
||||
|
||||
**Outcome:** DONE.
|
||||
|
||||
## 7. Group C - Settings-dialog improvements (editable dates, narrower RAW box, warning/reset/support, tooltips, bold locktime)
|
||||
|
||||
**Context / request:**
|
||||
Group C bundles several usability improvements to the BAL settings dialog and
|
||||
the will views, implemented together and delivered in a single ZIP.
|
||||
|
||||
**What changed (C2 - "Editable dates" option):**
|
||||
|
||||
- `bal/core/plugin_base.py`
|
||||
- New persisted config `EDITABLE_DATES = BalConfig(config, "bal_editable_dates",
|
||||
False)` (default OFF).
|
||||
|
||||
- `bal/gui/qt/widgets.py`
|
||||
- `WillSettingsWidget.__init__` reads `EDITABLE_DATES`. When OFF (default) the
|
||||
delivery-time and check-alive date fields stay display-only outside the
|
||||
"Build your will" wizard; when ON they become editable in the toolbar /
|
||||
Heirs tab. The fee field remains read-only outside the wizard.
|
||||
|
||||
- `bal/gui/qt/plugin.py`
|
||||
- New "Editable dates" checkbox in the settings dialog, bound to
|
||||
`EDITABLE_DATES`, with an explanatory tooltip. Toggling it refreshes the
|
||||
open windows so the date fields immediately reflect the new state.
|
||||
|
||||
**What changed (C3 - narrower RAW date box):**
|
||||
|
||||
- `bal/gui/qt/widgets.py`
|
||||
- `LockTimeRawEdit` width reduced from `12 * char_width_in_lineedit()` to
|
||||
`6 *` (roughly a third), enough for short relative values such as `30d` /
|
||||
`1y` while still fitting larger day counts.
|
||||
- `TimeRawEditWidget`'s trailing (empty) label shrunk from `10 *` to `2 *`
|
||||
character widths, removing the wasted blank space.
|
||||
|
||||
**What changed (C4 - warning, reset, support link):**
|
||||
|
||||
- `bal/gui/qt/plugin.py`
|
||||
- (a) Warning shown in bold red at the TOP of the dialog:
|
||||
"Warning: change these settings only if you know what you are doing."
|
||||
- (b) "Reset" button that restores the six dialog settings (Hide Replaced,
|
||||
Hide Invalidated, Auto-sign, Calendar App, Event summary, Event description)
|
||||
to their factory defaults, taking each default from `BalConfig.default`
|
||||
(single source of truth) and refreshing the widgets. It does NOT touch
|
||||
wills, will-executors or any other configuration.
|
||||
- (c) Clickable support link to `https://bitcoin-after.life`, opened via
|
||||
Electrum's `webopen` helper.
|
||||
- The dialog now uses an outer vertical layout (warning -> settings grid ->
|
||||
Reset/support row); the settings grid is unchanged apart from the new row.
|
||||
|
||||
- `bal/gui/qt/common.py`
|
||||
- Imported `webopen` from `electrum.gui.qt.util` (re-exported for the dialog).
|
||||
|
||||
**What changed (C5 - clearer tooltips):**
|
||||
|
||||
- `bal/gui/qt/widgets.py`
|
||||
- Calendar button tooltip: "Export reminder dates to your calendar (.ics)".
|
||||
- Fee field tooltip: "Mining fee rate in sat/vByte used for the will
|
||||
transactions".
|
||||
|
||||
**What changed (C6 - bold Locktime column):**
|
||||
|
||||
- `bal/gui/qt/lists.py`
|
||||
- In `PreviewList.replace()` the Locktime column item is now rendered in bold
|
||||
(via its own `QFont`), so the delivery time stands out in the list. The rest
|
||||
of the list is intentionally left unchanged.
|
||||
|
||||
- `tests/test_group_c_settings.py` (new)
|
||||
- Verifies `EDITABLE_DATES` defaults OFF and can be toggled/persisted, and
|
||||
that the Reset logic restores all six dialog settings to their defaults
|
||||
without touching unrelated configuration.
|
||||
|
||||
**Verification:**
|
||||
- `ruff check` on changed Python files: no new errors introduced (the new test
|
||||
file is ruff-clean; the only added import flagged by ruff is the re-export
|
||||
`webopen`, matching the existing star-import pattern in `common.py`).
|
||||
- Full test suite: `210 passed` (206 previous + 4 new Group C tests).
|
||||
|
||||
**Outcome:** DONE (delivered as a ZIP for user testing before commit).
|
||||
|
||||
### Group C - follow-up fixes (after first user test)
|
||||
|
||||
- **C2 (Editable dates) now takes effect immediately and is reset.**
|
||||
- `bal/gui/qt/widgets.py`: extracted the date-locking logic into
|
||||
`WillSettingsWidget.apply_editable_dates()`, which re-reads `EDITABLE_DATES`
|
||||
and locks/unlocks the delivery-time and check-alive fields (fee stays
|
||||
read-only). Called from `__init__` and re-callable afterwards.
|
||||
- `bal/gui/qt/window.py`: `update_all()` now calls `apply_editable_dates()` on
|
||||
the Heirs-tab and Will-tab settings widgets. Since the "Editable dates"
|
||||
checkbox already triggers `update_all()`, toggling it now updates the date
|
||||
fields instantly (same mechanism as the "Hide Invalidated" filter).
|
||||
- `bal/gui/qt/plugin.py`: added `EDITABLE_DATES` (and its checkbox) to the
|
||||
"Reset setting" list, so Reset also returns it to its default (OFF).
|
||||
|
||||
- **C3: fixed the broken date next to the RAW box.**
|
||||
- `bal/gui/qt/widgets.py`: the trailing label in `TimeRawEditWidget` shows the
|
||||
ABSOLUTE date computed from the RAW value (e.g. "30d" -> "2027-06-23").
|
||||
Its width was wrongly shrunk to 2 characters, truncating that date; it is
|
||||
restored to 10 characters. Only the RAW input box stays narrowed (6 chars).
|
||||
|
||||
- **C4b:** the reset button label is now "Reset setting".
|
||||
- **C4c:** the support link text `bitcoin-after.life` is now shown in bold.
|
||||
|
||||
- `tests/test_group_c_settings.py`: the reset test now also covers
|
||||
`EDITABLE_DATES` (seven settings restored to default, flag back OFF).
|
||||
|
||||
**Verification (follow-up):** `ruff` clean on changed files; full suite
|
||||
`210 passed`.
|
||||
|
||||
### Group C - second follow-up (after second user test)
|
||||
|
||||
- **Fee icon tooltip (C5):** the small "丰" help icon next to the fee field now
|
||||
shows the hover tooltip "Miner fee, click for more information" (the longer
|
||||
explanation still appears on click via the HelpButton).
|
||||
- **C4c:** the word "Support:" before the link is now also shown in bold (not
|
||||
only the link text).
|
||||
- **"Editable inheritance" Raw/Date default:** confirmed the existing behaviour
|
||||
is "remember the user's last choice" (the Raw/Date combo selection is
|
||||
persisted in `WILL_SETTINGS` whenever it changes), so no code change was
|
||||
needed - the dialog reopens on whichever mode the user last used.
|
||||
|
||||
**Verification (second follow-up):** `ruff` clean on changed files; full suite
|
||||
`210 passed`.
|
||||
|
||||
### Group C - third follow-up (tooltip font fix)
|
||||
|
||||
- **Fee icon tooltip font:** the "丰" button used an unscoped
|
||||
`font-size: 16px` stylesheet that also enlarged its tooltip, making it bigger
|
||||
than the other tooltips (e.g. the calendar one). The rule is now scoped to
|
||||
`QPushButton{...}`, so only the glyph stays large while the tooltip uses the
|
||||
default font size like the others.
|
||||
|
||||
**Verification (third follow-up):** full suite `210 passed`.
|
||||
|
||||
@@ -175,6 +175,12 @@ class BalPlugin(BasePlugin):
|
||||
# (handled by BalWindow.get_wallet_password). Default ON.
|
||||
self.AUTO_SIGN = BalConfig(config, "bal_auto_sign", True)
|
||||
|
||||
# EDITABLE_DATES (Group C / C2): when enabled, the delivery-time and
|
||||
# check-alive date fields are editable everywhere (toolbar / Heirs tab),
|
||||
# not only inside the "Build your will" wizard. Default OFF, so the dates
|
||||
# stay display-only outside the wizard unless the user opts in.
|
||||
self.EDITABLE_DATES = BalConfig(config, "bal_editable_dates", False)
|
||||
|
||||
self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True)
|
||||
self.HIDE_REPLACED = BalConfig(config, "bal_hide_replaced", True)
|
||||
self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True)
|
||||
|
||||
@@ -39,7 +39,7 @@ from electrum.gui.qt.util import (Buttons, CancelButton, ColorScheme,
|
||||
OkButton, TaskThread, WindowModalDialog,
|
||||
char_width_in_lineedit, getSaveFileName,
|
||||
import_meta_gui, read_QIcon_from_bytes,
|
||||
read_QPixmap_from_bytes)
|
||||
read_QPixmap_from_bytes, webopen)
|
||||
from electrum.i18n import _
|
||||
from electrum.logging import get_logger
|
||||
from electrum.network import BestEffortRequestFailed, Network, TxBroadcastError
|
||||
|
||||
@@ -403,6 +403,16 @@ class PreviewList(MyTreeView, MessageBoxMixin):
|
||||
|
||||
items[-1].setBackground(QColor(status_color(bal_tx)))
|
||||
|
||||
# Group C / C6: emphasise the Locktime column by rendering it in bold,
|
||||
# so the delivery time stands out at a glance in the list.
|
||||
try:
|
||||
locktime_item = items[self.Columns.LOCKTIME]
|
||||
bold_font = locktime_item.font()
|
||||
bold_font.setBold(True)
|
||||
locktime_item.setFont(bold_font)
|
||||
except Exception as bold_err:
|
||||
_logger.debug(f"locktime bold error: {bold_err}")
|
||||
|
||||
# Tooltip on the Server column: shows the will-executor URL (if any)
|
||||
# plus the current server state, so the user can always inspect details.
|
||||
try:
|
||||
|
||||
@@ -396,13 +396,42 @@ class Plugin(BalPlugin):
|
||||
# default is ON (see plugin_base.py).
|
||||
heir_auto_sign = BalCheckBox(self.AUTO_SIGN)
|
||||
|
||||
# Editable dates checkbox (Group C / C2). When ticked, the delivery-time
|
||||
# and check-alive date fields become editable everywhere (toolbar /
|
||||
# Heirs tab), not only inside the "Build your will" wizard. Bound to the
|
||||
# persisted EDITABLE_DATES config; the default is OFF (see
|
||||
# plugin_base.py). Changing it refreshes the open windows so the date
|
||||
# fields immediately become editable/read-only.
|
||||
heir_editable_dates = BalCheckBox(self.EDITABLE_DATES, on_multiverse_change)
|
||||
|
||||
# Editable line/text widgets are created once and kept in named
|
||||
# variables so the "Reset" button (Group C / C4b) can refresh the
|
||||
# displayed values after resetting the underlying config.
|
||||
edit_calendar_app = BalLineEdit(self.CALENDAR_APP)
|
||||
edit_event_summary = BalLineEdit(self.EVENT_SUMMARY)
|
||||
edit_event_description = BalTextEdit(self.EVENT_DESCRIPTION)
|
||||
|
||||
heir_repush = QPushButton("Rebroadcast transactions")
|
||||
heir_repush.clicked.connect(partial(self.broadcast_transactions, True))
|
||||
bal_mode = QComboBox()
|
||||
options = ["Easy", "Advanced", "Experimental"]
|
||||
bal_mode.addItems(options)
|
||||
|
||||
grid = QGridLayout(d)
|
||||
# Group C / C4a: warning shown at the very top of the dialog, in red and
|
||||
# bold, so the (non-technical) user is reminded not to touch these
|
||||
# settings unless they understand them. Placed above the grid via the
|
||||
# outer vertical layout below.
|
||||
lbl_warning = QLabel(
|
||||
_("Warning: change these settings only if you know what you are doing.")
|
||||
)
|
||||
lbl_warning.setStyleSheet("color: red; font-weight: bold;")
|
||||
lbl_warning.setWordWrap(True)
|
||||
|
||||
# The grid is created WITHOUT a parent so it can be embedded inside an
|
||||
# outer QVBoxLayout together with the warning label (top) and the
|
||||
# Reset/support button row (bottom). Assigning ``QGridLayout(d)`` would
|
||||
# have made the grid the dialog's only layout, leaving no room for them.
|
||||
grid = QGridLayout()
|
||||
add_widget(
|
||||
grid,
|
||||
"Hide Replaced",
|
||||
@@ -431,16 +460,28 @@ class Plugin(BalPlugin):
|
||||
)
|
||||
add_widget(
|
||||
grid,
|
||||
"Calendar App",
|
||||
BalLineEdit(self.CALENDAR_APP),
|
||||
"Editable dates",
|
||||
heir_editable_dates,
|
||||
4,
|
||||
(
|
||||
"When enabled, the delivery-time and check-alive date fields "
|
||||
"can be edited everywhere (toolbar / Heirs tab), not only in "
|
||||
"the will-building wizard.\n"
|
||||
"When disabled, those dates are display-only outside the wizard."
|
||||
),
|
||||
)
|
||||
add_widget(
|
||||
grid,
|
||||
"Calendar App",
|
||||
edit_calendar_app,
|
||||
5,
|
||||
"Default app used to open calendar",
|
||||
)
|
||||
add_widget(
|
||||
grid,
|
||||
"Event summary",
|
||||
BalLineEdit(self.EVENT_SUMMARY),
|
||||
5,
|
||||
edit_event_summary,
|
||||
6,
|
||||
(
|
||||
"Default message to be used in event summary\n"
|
||||
"Variables:\n"
|
||||
@@ -452,8 +493,8 @@ class Plugin(BalPlugin):
|
||||
add_widget(
|
||||
grid,
|
||||
"Event description",
|
||||
BalTextEdit(self.EVENT_DESCRIPTION),
|
||||
6,
|
||||
edit_event_description,
|
||||
7,
|
||||
(
|
||||
"Default message to be used in event description\n"
|
||||
"Variables:\n"
|
||||
@@ -486,15 +527,87 @@ class Plugin(BalPlugin):
|
||||
# "Add transactions without willexecutor",
|
||||
# )
|
||||
# add_widget(grid,"Enable Multiverse(EXPERIMENTAL/BROKEN)",heir_enable_multiverse,6,"enable multiple locktimes, will import.... ")
|
||||
grid.addWidget(heir_repush, 7, 0)
|
||||
grid.addWidget(heir_repush, 8, 0)
|
||||
grid.addWidget(
|
||||
HelpButton(
|
||||
"Broadcast all transactions to willexecutors including those already pushed"
|
||||
),
|
||||
7,
|
||||
8,
|
||||
2,
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------------- #
|
||||
# Group C / C4b: "Reset" button that restores the dialog settings to #
|
||||
# their factory defaults. It only resets the settings exposed by THIS #
|
||||
# dialog (the 6 below) and refreshes the corresponding widgets so the #
|
||||
# change is visible immediately. It deliberately does NOT touch the #
|
||||
# wills, will-executors or any other configuration. #
|
||||
# ----------------------------------------------------------------- #
|
||||
def on_reset_defaults():
|
||||
"""Reset the six dialog settings to their defaults and refresh widgets.
|
||||
|
||||
The default value of each setting is taken from ``BalConfig.default``
|
||||
(the third argument used when the config was created in
|
||||
``plugin_base.py``), so there is a single source of truth and no
|
||||
hard-coded duplicates here.
|
||||
"""
|
||||
# Map each config object to the widget that displays it, so we can
|
||||
# both reset the stored value and update what the user sees.
|
||||
resets = [
|
||||
(self.HIDE_REPLACED, heir_hide_replaced, "check"),
|
||||
(self.HIDE_INVALIDATED, heir_hide_invalidated, "check"),
|
||||
(self.AUTO_SIGN, heir_auto_sign, "check"),
|
||||
(self.EDITABLE_DATES, heir_editable_dates, "check"),
|
||||
(self.CALENDAR_APP, edit_calendar_app, "line"),
|
||||
(self.EVENT_SUMMARY, edit_event_summary, "line"),
|
||||
(self.EVENT_DESCRIPTION, edit_event_description, "text"),
|
||||
]
|
||||
for cfg, widget, kind in resets:
|
||||
# Persist the default value back into the Electrum config.
|
||||
cfg.set(cfg.default)
|
||||
# Refresh the widget so the reset is immediately visible. The
|
||||
# widgets' own signal handlers will re-persist the same default
|
||||
# value, which is harmless.
|
||||
if kind == "check":
|
||||
widget.setChecked(bool(cfg.default))
|
||||
elif kind == "line":
|
||||
widget.setText(cfg.default)
|
||||
elif kind == "text":
|
||||
widget.setPlainText(cfg.default)
|
||||
# Refresh the open BAL windows so any dependent view (e.g. the
|
||||
# editable-dates state is not in this list, but hide filters are)
|
||||
# reflects the reset values.
|
||||
self.update_all()
|
||||
|
||||
btn_reset = QPushButton(_("Reset setting"))
|
||||
btn_reset.setToolTip(_("Reset these settings to their default values"))
|
||||
btn_reset.clicked.connect(on_reset_defaults)
|
||||
|
||||
# Group C / C4c: clickable support link to the project website.
|
||||
lbl_support = QLabel(
|
||||
'<a href="https://bitcoin-after.life"><b>bitcoin-after.life</b></a>'
|
||||
)
|
||||
lbl_support.setToolTip(_("Open the Bitcoin After Life support website"))
|
||||
# Open the link in the user's browser via Electrum's helper instead of
|
||||
# letting Qt open it directly, so it goes through Electrum's policy.
|
||||
lbl_support.setOpenExternalLinks(False)
|
||||
lbl_support.linkActivated.connect(
|
||||
lambda _url: webopen("https://bitcoin-after.life")
|
||||
)
|
||||
|
||||
# Bottom row: Reset on the left, support link on the right.
|
||||
bottom_row = QHBoxLayout()
|
||||
bottom_row.addWidget(btn_reset)
|
||||
bottom_row.addStretch(1)
|
||||
bottom_row.addWidget(QLabel("<b>" + _("Support:") + "</b>"))
|
||||
bottom_row.addWidget(lbl_support)
|
||||
|
||||
# Outer layout: warning (top) -> settings grid -> bottom button row.
|
||||
outer = QVBoxLayout(d)
|
||||
outer.addWidget(lbl_warning)
|
||||
outer.addLayout(grid)
|
||||
outer.addLayout(bottom_row)
|
||||
|
||||
if ret := bool(show_modal(d)):
|
||||
try:
|
||||
self.update_all()
|
||||
|
||||
@@ -43,6 +43,10 @@ class BalTxFeesWidget(QWidget):
|
||||
self.txfee_widget = QSpinBox(self)
|
||||
self.txfee_widget.setMinimum(1)
|
||||
self.txfee_widget.setMaximum(10000)
|
||||
# Group C / C5: hovering the fee field explains what the number means.
|
||||
self.txfee_widget.setToolTip(
|
||||
_("Mining fee rate in sat/vByte used for the will transactions")
|
||||
)
|
||||
value = (
|
||||
value
|
||||
if value
|
||||
@@ -58,7 +62,14 @@ class BalTxFeesWidget(QWidget):
|
||||
#layout.addWidget(label)
|
||||
button = HelpButton(_("mining fees expressed in sats/vbyte to be used in the Bitcoin transaction.\nHigher value ensure your transaction will be confirmed"))
|
||||
button.setText("丰")
|
||||
button.setStyleSheet("font-size: 16px;")
|
||||
# Hover tooltip for the small "丰" help icon (the long explanation still
|
||||
# appears on click via the HelpButton); makes the icon self-explanatory.
|
||||
button.setToolTip(_("Miner fee, click for more information"))
|
||||
# Enlarge only the "丰" glyph on the button itself; without scoping the
|
||||
# rule to QPushButton it also enlarged the tooltip font (making it bigger
|
||||
# than the other tooltips, e.g. the calendar one). Scoping it keeps the
|
||||
# tooltip at the default size like everywhere else.
|
||||
button.setStyleSheet("QPushButton{font-size: 16px;}")
|
||||
layout.addWidget(button)
|
||||
layout.addWidget(self.txfee_widget)
|
||||
# Expose the leading icon (prefix) and the editable field so the parent
|
||||
@@ -322,6 +333,11 @@ class TimeRawEditWidget(QWidget):
|
||||
super().__init__(parent)
|
||||
self.editor = LockTimeRawEdit(parent, time_edit)
|
||||
self.label = QLabel("")
|
||||
# Group C / C3: this trailing label shows the ABSOLUTE date computed
|
||||
# from the RAW value (e.g. "30d" -> "2027-06-23"), so it needs room for a
|
||||
# full "YYYY-MM-DD" string (~10 characters). Only the input box itself
|
||||
# (LockTimeRawEdit) is narrowed; shrinking this label was a mistake that
|
||||
# truncated the computed date.
|
||||
self.label.setFixedWidth(10 * char_width_in_lineedit())
|
||||
self.layout = QHBoxLayout(self)
|
||||
self.layout.addWidget(self.editor)
|
||||
@@ -343,7 +359,11 @@ class TimeRawEditWidget(QWidget):
|
||||
class LockTimeRawEdit(QLineEdit, _LockTimeEditor):
|
||||
def __init__(self, parent=None, time_edit=None):
|
||||
QLineEdit.__init__(self, parent)
|
||||
self.setFixedWidth(12 * char_width_in_lineedit())
|
||||
# Group C / C3: narrow the RAW input to roughly a third of its former
|
||||
# width. The accepted values are short relative durations such as "30d"
|
||||
# or "1y", so a handful of characters is plenty while still leaving room
|
||||
# for larger day counts (e.g. "3650d").
|
||||
self.setFixedWidth(6 * char_width_in_lineedit())
|
||||
self.textChanged.connect(self.numbify)
|
||||
self.isdays = False
|
||||
self.isyears = False
|
||||
@@ -554,8 +574,10 @@ class WillSettingsWidget(QWidget):
|
||||
self.bal_window.bal_plugin.read_file("icons/calendar.png")
|
||||
)
|
||||
)
|
||||
# Tooltip so the icon is self-explanatory when hovered.
|
||||
self.calendar_button.setToolTip(_("Calendar"))
|
||||
# Tooltip so the icon is self-explanatory when hovered (Group C / C5).
|
||||
self.calendar_button.setToolTip(
|
||||
_("Export reminder dates to your calendar (.ics)")
|
||||
)
|
||||
self.calendar_button.clicked.connect(self.open_or_save_calendar)
|
||||
self.widgets["locktime"] = LockTimeWidget(bal_window, self)
|
||||
self.widgets["threshold"] = ThresholdTimeWidget(bal_window, self)
|
||||
@@ -623,10 +645,42 @@ class WillSettingsWidget(QWidget):
|
||||
box.addWidget(calendar_row, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||
box.addWidget(fees_w, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
if self.read_only:
|
||||
self.widgets["locktime"].set_read_only(True)
|
||||
self.widgets["threshold"].set_read_only(True)
|
||||
self.widgets["baltx_fees"].set_read_only(True)
|
||||
# Group C / C2: apply the current "Editable dates" setting to the date
|
||||
# fields. Done once at creation here, and re-applied later by
|
||||
# apply_editable_dates() whenever the setting changes (called from
|
||||
# BalWindow.update_all()), so toggling the checkbox takes effect
|
||||
# immediately, exactly like the "Hide Invalidated" filter does.
|
||||
self.apply_editable_dates()
|
||||
|
||||
def apply_editable_dates(self):
|
||||
"""Re-read the EDITABLE_DATES setting and lock/unlock the date fields.
|
||||
|
||||
Outside the "Build your will" wizard (``read_only=True``) the
|
||||
delivery-time and check-alive dates are display-only by default. When
|
||||
the user ticks "Editable dates" in the settings they become editable
|
||||
here too. The fee field always stays read-only outside the wizard,
|
||||
because C2 only concerns the dates.
|
||||
|
||||
This is safe to call repeatedly: it only adjusts the read-only state of
|
||||
the already-created sub-widgets, it does not rebuild anything. It is the
|
||||
per-update hook that lets the settings checkbox take effect without
|
||||
having to recreate the toolbar / re-open the window.
|
||||
"""
|
||||
# Inside the wizard the dates are always editable; nothing to do.
|
||||
if not self.read_only:
|
||||
return
|
||||
|
||||
editable_dates = False
|
||||
try:
|
||||
editable_dates = self.bal_window.bal_plugin.EDITABLE_DATES.get()
|
||||
except Exception:
|
||||
# If the setting cannot be read, fall back to the safe default
|
||||
# (dates remain read-only outside the wizard).
|
||||
editable_dates = False
|
||||
|
||||
self.widgets["locktime"].set_read_only(not editable_dates)
|
||||
self.widgets["threshold"].set_read_only(not editable_dates)
|
||||
self.widgets["baltx_fees"].set_read_only(True)
|
||||
|
||||
def create_alarms(self, alarm_start, alarm_end):
|
||||
days = (alarm_end - alarm_start).days+1
|
||||
|
||||
@@ -1300,6 +1300,20 @@ class BalWindow:
|
||||
self.heirs_tab.update()
|
||||
self.will_tab.update()
|
||||
self.will_list_widget.update()
|
||||
|
||||
# Group C / C2: re-apply the "Editable dates" setting to the date
|
||||
# fields of the toolbars / Heirs tab. The settings checkbox calls
|
||||
# update_all() when toggled, so this makes the change take effect
|
||||
# immediately (same pattern as sync_hide_filters above for the
|
||||
# "Hide Invalidated/Replaced" checkboxes). Guarded so a missing
|
||||
# widget never breaks the rest of the refresh.
|
||||
for _list in (self.heir_list_widget, self.will_list_widget):
|
||||
_settings_widget = getattr(_list, "will_settings_widget", None)
|
||||
if _settings_widget is not None:
|
||||
try:
|
||||
_settings_widget.apply_editable_dates()
|
||||
except Exception as _edit_err:
|
||||
_logger.debug(f"apply_editable_dates error: {_edit_err}")
|
||||
except Exception as e:
|
||||
_logger.error(f"error while updating window: {e}")
|
||||
|
||||
|
||||
158
tests/test_group_c_settings.py
Normal file
158
tests/test_group_c_settings.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""
|
||||
Tests for Group C (settings-dialog enhancements).
|
||||
|
||||
Covered behaviour:
|
||||
|
||||
* C2 - the persisted ``EDITABLE_DATES`` configuration key exists, defaults to
|
||||
OFF, and can be toggled and read back. This is the flag that makes the
|
||||
delivery-time / check-alive date fields editable outside the wizard.
|
||||
* C4b - the "Reset" logic restores each of the six dialog settings to its
|
||||
factory default. The factory default is taken from ``BalConfig.default``
|
||||
(the third argument used when the config was created), so the dialog's
|
||||
Reset button has a single source of truth and never hard-codes values.
|
||||
|
||||
The Qt widgets are not imported (they need PyQt6 + an Electrum window). Instead
|
||||
we reproduce the small, GUI-free decision logic with the same lightweight
|
||||
``FakeConfig`` used by the Group B tests, which keeps the tests fast and
|
||||
headless while still verifying the real contract.
|
||||
|
||||
Run:
|
||||
PYTHONPATH=electrum-src python3 -m pytest tests/test_group_c_settings.py -q
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
|
||||
|
||||
from bal.core.plugin_base import BalConfig
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Mocks
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
class FakeConfig:
|
||||
"""Minimal mock for Electrum's config object (key/value store)."""
|
||||
|
||||
def __init__(self):
|
||||
self._store = {}
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self._store.get(key, default)
|
||||
|
||||
def set_key(self, key, value, save=True):
|
||||
self._store[key] = value
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# C2 - EDITABLE_DATES config
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def test_editable_dates_defaults_off():
|
||||
"""C2: the editable-dates flag defaults to OFF (dates display-only)."""
|
||||
cfg = FakeConfig()
|
||||
editable = BalConfig(cfg, "bal_editable_dates", False)
|
||||
assert editable.get() is False
|
||||
|
||||
|
||||
def test_editable_dates_can_be_enabled():
|
||||
"""C2: turning the flag ON is persisted and read back as True."""
|
||||
cfg = FakeConfig()
|
||||
editable = BalConfig(cfg, "bal_editable_dates", False)
|
||||
editable.set(True)
|
||||
# Re-read through a fresh accessor to prove it is persisted in the config.
|
||||
assert BalConfig(cfg, "bal_editable_dates", False).get() is True
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# C4b - Reset to defaults
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def _reset_to_defaults(configs):
|
||||
"""Reproduce the dialog's Reset logic: set each config back to its default.
|
||||
|
||||
This mirrors ``on_reset_defaults`` in ``bal.gui.qt.plugin`` (which also
|
||||
refreshes the Qt widgets). Here we only verify the persistence side: every
|
||||
config is reset to ``BalConfig.default``.
|
||||
"""
|
||||
for cfg in configs:
|
||||
cfg.set(cfg.default)
|
||||
|
||||
|
||||
def test_reset_restores_all_dialog_settings():
|
||||
"""C4b: Reset restores every dialog setting to its factory default.
|
||||
|
||||
The dialog exposes seven settings: the original six plus the Group C
|
||||
"Editable dates" checkbox, which the Reset button must also restore (this
|
||||
was a follow-up fix after the first test round).
|
||||
"""
|
||||
cfg = FakeConfig()
|
||||
|
||||
# The settings exposed by the dialog, with their real defaults.
|
||||
hide_replaced = BalConfig(cfg, "bal_hide_replaced", True)
|
||||
hide_invalidated = BalConfig(cfg, "bal_hide_invalidated", True)
|
||||
auto_sign = BalConfig(cfg, "bal_auto_sign", True)
|
||||
editable_dates = BalConfig(cfg, "bal_editable_dates", False)
|
||||
calendar_app = BalConfig(cfg, "bal_open_app", "xdg-open")
|
||||
event_summary = BalConfig(
|
||||
cfg, "bal_event_summary", "BAL -Will execution of $wallet_name"
|
||||
)
|
||||
event_description = BalConfig(
|
||||
cfg,
|
||||
"bal_event_description",
|
||||
"BAL will execution of $wallet_name\r\n heirs list: \r\n$heirs_complete",
|
||||
)
|
||||
settings = [
|
||||
hide_replaced,
|
||||
hide_invalidated,
|
||||
auto_sign,
|
||||
editable_dates,
|
||||
calendar_app,
|
||||
event_summary,
|
||||
event_description,
|
||||
]
|
||||
|
||||
# Mutate every setting away from its default.
|
||||
hide_replaced.set(False)
|
||||
hide_invalidated.set(False)
|
||||
auto_sign.set(False)
|
||||
editable_dates.set(True)
|
||||
calendar_app.set("/custom/app")
|
||||
event_summary.set("custom summary")
|
||||
event_description.set("custom description")
|
||||
|
||||
# Sanity: the values really changed.
|
||||
assert hide_replaced.get() is False
|
||||
assert editable_dates.get() is True
|
||||
assert calendar_app.get() == "/custom/app"
|
||||
|
||||
# Reset and verify each one is back to its declared default.
|
||||
_reset_to_defaults(settings)
|
||||
for s in settings:
|
||||
assert s.get() == s.default
|
||||
# In particular the "Editable dates" flag is back OFF.
|
||||
assert editable_dates.get() is False
|
||||
|
||||
|
||||
def test_reset_does_not_touch_unrelated_settings():
|
||||
"""C4b: Reset only changes the listed settings, nothing else.
|
||||
|
||||
A will / will-executor style key that is NOT part of the dialog list must
|
||||
keep its value after a reset of the six dialog settings.
|
||||
"""
|
||||
cfg = FakeConfig()
|
||||
unrelated = BalConfig(cfg, "bal_will_settings", {"x": 1})
|
||||
unrelated.set({"custom": "value"})
|
||||
|
||||
dialog_settings = [
|
||||
BalConfig(cfg, "bal_hide_replaced", True),
|
||||
BalConfig(cfg, "bal_auto_sign", True),
|
||||
]
|
||||
for s in dialog_settings:
|
||||
s.set(False)
|
||||
|
||||
_reset_to_defaults(dialog_settings)
|
||||
|
||||
# The unrelated key is untouched by the dialog reset.
|
||||
assert unrelated.get() == {"custom": "value"}
|
||||
Reference in New Issue
Block a user