diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d9a87b..0b06e76 100644
--- a/CHANGELOG.md
+++ b/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`.
diff --git a/bal/core/plugin_base.py b/bal/core/plugin_base.py
index 9f98afb..794980e 100644
--- a/bal/core/plugin_base.py
+++ b/bal/core/plugin_base.py
@@ -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)
diff --git a/bal/gui/qt/common.py b/bal/gui/qt/common.py
index 38a4710..e3ede1c 100644
--- a/bal/gui/qt/common.py
+++ b/bal/gui/qt/common.py
@@ -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
diff --git a/bal/gui/qt/lists.py b/bal/gui/qt/lists.py
index 08fdc71..0116637 100644
--- a/bal/gui/qt/lists.py
+++ b/bal/gui/qt/lists.py
@@ -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:
diff --git a/bal/gui/qt/plugin.py b/bal/gui/qt/plugin.py
index bef91ae..564975c 100644
--- a/bal/gui/qt/plugin.py
+++ b/bal/gui/qt/plugin.py
@@ -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(
+ 'bitcoin-after.life'
+ )
+ 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("" + _("Support:") + ""))
+ 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()
diff --git a/bal/gui/qt/widgets.py b/bal/gui/qt/widgets.py
index 31c8d00..2c12af5 100644
--- a/bal/gui/qt/widgets.py
+++ b/bal/gui/qt/widgets.py
@@ -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
diff --git a/bal/gui/qt/window.py b/bal/gui/qt/window.py
index e088177..6b4593a 100644
--- a/bal/gui/qt/window.py
+++ b/bal/gui/qt/window.py
@@ -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}")
diff --git a/tests/test_group_c_settings.py b/tests/test_group_c_settings.py
new file mode 100644
index 0000000..4f200eb
--- /dev/null
+++ b/tests/test_group_c_settings.py
@@ -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"}