forked from bitcoinafterlife/bal-electrum-plugin
v0.4.8: seven UX fixes for the BAL inheritance plugin
- #04: Raw/Date selector now reappears on the WILL/HEIR tabs when switching to ADVANCED (and when a raw value is pushed from the wizard), keeping the current value/editor. New BalTimeEditWidget.apply_user_type_visibility() wired into WillSettingsWidget for both the locktime and check-alive boxes. - #3: Building Will report window minimum height 500 -> 450 px. - #5: 'User Type' setting moved to the bottom, above 'Rebroadcast transactions'. - #6: enabling ADVANCED now requires typing 'at My Risk' (case-insensitive); wrong phrase or cancel reverts to BASIC. QInputDialog added to common import. - #7a: on CHECK with an emptied wallet, an extra reassuring line is shown on 'Checking your will': 'Inheritance already executed (on blockchain)' in GREEN (CONFIRMED) or 'Inheritance in mempool (waiting confirmation)' in ORANGE (MEMPOOL). New _executed_inheritance_status() helper. - #7b: 'Balance is too low, or CheckAlive is in the past. Skipped' (space added) recoloured to ORANGE instead of red. - Reset button renamed 'Reset setting' -> 'Reset to Default Setting'. - Added tests/test_group_h_v048.py (8 tests). Version 0.4.7 -> 0.4.8. Full suite: 266 passed; ruff clean; CHANGELOG #24; memory updated (#04 done).
This commit is contained in:
@@ -1 +1 @@
|
||||
0.4.7
|
||||
0.4.8
|
||||
@@ -34,4 +34,4 @@ The plugin targets Electrum 4.7.2 (the last stable release exposing
|
||||
``json_db.register_dict``) and PyQt6.
|
||||
"""
|
||||
|
||||
__version__ = "0.4.7"
|
||||
__version__ = "0.4.8"
|
||||
|
||||
@@ -91,7 +91,7 @@ class BalPlugin(BasePlugin):
|
||||
"""
|
||||
|
||||
_version = None
|
||||
__version__ = "0.4.7" # AUTOMATICALLY GENERATED DO NOT EDIT
|
||||
__version__ = "0.4.8" # AUTOMATICALLY GENERATED DO NOT EDIT
|
||||
|
||||
# Command used to open an .ics calendar file, per operating system.
|
||||
default_app = {
|
||||
|
||||
@@ -55,9 +55,9 @@ from PyQt6.QtGui import (QColor, QPainter, QPalette, QStandardItem,
|
||||
QStandardItemModel)
|
||||
from PyQt6.QtWidgets import (QAbstractItemView, QAbstractSpinBox, QCheckBox,
|
||||
QComboBox, QDateTimeEdit, QGridLayout, QHBoxLayout,
|
||||
QLabel, QLineEdit, QTextEdit, QMenu, QMenuBar,
|
||||
QPushButton, QScrollArea, QSizePolicy, QSpinBox,
|
||||
QStackedWidget, QStyle, QStyleOptionFrame,
|
||||
QInputDialog, QLabel, QLineEdit, QTextEdit, QMenu,
|
||||
QMenuBar, QPushButton, QScrollArea, QSizePolicy,
|
||||
QSpinBox, QStackedWidget, QStyle, QStyleOptionFrame,
|
||||
QVBoxLayout, QWidget, QDialog)
|
||||
|
||||
# --- Core (GUI-free) logic layer ---
|
||||
|
||||
@@ -555,11 +555,12 @@ class BalBuildWillDialog(BalDialog):
|
||||
self.scroll_area = QScrollArea(self)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setWidget(self.message_label)
|
||||
# Open the report area already ~500px tall (owner request, allegato1:
|
||||
# the previous ~140px area was far too short). The dialog may still grow
|
||||
# up to 700px to fit a few more lines; beyond that the vertical
|
||||
# scrollbar takes over and the bottom buttons stay reachable.
|
||||
self.scroll_area.setMinimumHeight(500)
|
||||
# Open the report area ~450px tall (owner request: 500px left too much
|
||||
# empty space below the short report; 450px lines up with the desired
|
||||
# window height). The dialog may still grow up to 700px to fit a few more
|
||||
# lines; beyond that the vertical scrollbar takes over and the bottom
|
||||
# buttons stay reachable.
|
||||
self.scroll_area.setMinimumHeight(450)
|
||||
self.scroll_area.setMaximumHeight(700)
|
||||
self.vbox.addWidget(self.scroll_area, 1)
|
||||
|
||||
@@ -701,6 +702,38 @@ class BalBuildWillDialog(BalDialog):
|
||||
_logger.debug(f"not complete {e} true")
|
||||
message = False
|
||||
have_to_build = True
|
||||
# Task #7a: if the will was already executed, the wallet is empty and
|
||||
# this exception is expected. Before showing the alarming "Found
|
||||
# CHANGES ... a NEW WILL must be prepared" message, add a clear,
|
||||
# reassuring note on the "Checking your will" row telling the user
|
||||
# the inheritance is already on its way (mempool) or done (on-chain).
|
||||
# The original message is still shown afterwards (owner request: the
|
||||
# red/"changes" message stays, this is only an extra, more precise
|
||||
# informative line).
|
||||
# NOTE: we add the informative note as its OWN extra row (not via
|
||||
# msg_set_checking, which reuses self.check_row and would be
|
||||
# overwritten by the "Found CHANGES" line set below). Passing row=None
|
||||
# to msg_set_status appends a new line, so the executed/mempool note
|
||||
# and the original message are BOTH visible.
|
||||
executed_status = self._executed_inheritance_status()
|
||||
if executed_status == "CONFIRMED":
|
||||
# Green: the inheritance transaction is confirmed on the
|
||||
# blockchain, so it has been executed correctly.
|
||||
self.msg_set_status(
|
||||
_("Checking your will"),
|
||||
None,
|
||||
_("Inheritance already executed (on blockchain)"),
|
||||
self.COLOR_OK,
|
||||
)
|
||||
elif executed_status == "MEMPOOL":
|
||||
# Orange (warning colour): the transaction is in the mempool,
|
||||
# waiting to be confirmed. Not an error, just "in progress".
|
||||
self.msg_set_status(
|
||||
_("Checking your will"),
|
||||
None,
|
||||
_("Inheritance in mempool (waiting confirmation)"),
|
||||
self.COLOR_WARNING,
|
||||
)
|
||||
if isinstance(e, HeirChangeException):
|
||||
message = _("Heirs changed:")
|
||||
elif isinstance(e, WillExecutorNotPresent):
|
||||
@@ -741,8 +774,15 @@ class BalBuildWillDialog(BalDialog):
|
||||
txs = self.bal_window.build_will()
|
||||
if not txs:
|
||||
self.msg_set_building(
|
||||
_("Balance is too low, or CheckAlive is in the past.Skipped"),
|
||||
color = self.COLOR_ERROR,
|
||||
_(
|
||||
"Balance is too low, or CheckAlive is in the "
|
||||
"past. Skipped"
|
||||
),
|
||||
# Orange (warning) instead of red (error): an empty
|
||||
# wallet after the inheritance was executed is a NORMAL
|
||||
# situation, not a failure, so the colour should not
|
||||
# alarm the user (owner request).
|
||||
color=self.COLOR_WARNING,
|
||||
)
|
||||
return False, None
|
||||
|
||||
@@ -1546,6 +1586,42 @@ class BalBuildWillDialog(BalDialog):
|
||||
self.msg_edit_row(self.msg_error(f"Error: {b}"))
|
||||
_logger.error(f"error phase2: {b}")
|
||||
|
||||
def _executed_inheritance_status(self):
|
||||
"""Return the on-chain status of an already-executed inheritance.
|
||||
|
||||
Task #7a (owner request). After an inheritance is executed the wallet is
|
||||
fully emptied (the plugin always empties the wallet). Pressing CHECK on
|
||||
such an empty wallet makes ``check_will`` raise NotCompleteWillException
|
||||
(the heirs no longer match the empty wallet), which previously showed the
|
||||
alarming "Found CHANGES ... a NEW WILL must be prepared" message even
|
||||
though the inheritance was, in fact, correctly executed.
|
||||
|
||||
To reassure the user we look at the will items, whose status is already
|
||||
set to CONFIRMED / MEMPOOL by ``Will.check_will`` (see core/will.py), and
|
||||
return a short code describing the real situation:
|
||||
|
||||
Returns:
|
||||
"CONFIRMED" if at least one will transaction is confirmed on-chain
|
||||
(the inheritance is already executed);
|
||||
"MEMPOOL" if none is confirmed but at least one is in the mempool
|
||||
(waiting for confirmation);
|
||||
None if neither (the change really has to be rebuilt).
|
||||
|
||||
CONFIRMED takes precedence over MEMPOOL, since a confirmed transaction is
|
||||
the strongest evidence that the inheritance has gone through.
|
||||
"""
|
||||
has_mempool = False
|
||||
try:
|
||||
for _wid, witem in self.bal_window.willitems.items():
|
||||
if witem.get_status("CONFIRMED"):
|
||||
return "CONFIRMED"
|
||||
if witem.get_status("MEMPOOL"):
|
||||
has_mempool = True
|
||||
except Exception as _err:
|
||||
# Never let this purely informational check break the flow.
|
||||
_logger.debug(f"_executed_inheritance_status error: {_err}")
|
||||
return "MEMPOOL" if has_mempool else None
|
||||
|
||||
def msg_set_checking(self, status="Waiting", row=None):
|
||||
row = self.check_row if row is None else row
|
||||
self.check_row = self.msg_set_status(_("Checking your will"), row, status)
|
||||
|
||||
@@ -438,6 +438,29 @@ class Plugin(BalPlugin):
|
||||
def on_user_type_change(idx):
|
||||
# Persist "basic"/"advanced" and refresh the open windows so the
|
||||
# advanced controls appear/disappear right away.
|
||||
#
|
||||
# SAFETY GATE (owner request): enabling ADVANCED exposes powerful,
|
||||
# easy-to-misuse controls. Before switching to ADVANCED the user must
|
||||
# type a confirmation phrase ("at My Risk", case-insensitive). If the
|
||||
# phrase is wrong or the dialog is cancelled, we revert the combo to
|
||||
# BASIC and do NOT enable ADVANCED. Switching back to BASIC needs no
|
||||
# confirmation.
|
||||
if idx == 1:
|
||||
text, ok = QInputDialog.getText(
|
||||
d,
|
||||
_("Enable ADVANCED mode"),
|
||||
_("Type 'at My Risk' to enable ADVANCED mode"),
|
||||
)
|
||||
# Accept any capitalisation (e.g. "at my risk", "AT MY RISK").
|
||||
if not ok or text.strip().lower() != "at my risk":
|
||||
# Revert to BASIC. Block the signal while we reset the combo
|
||||
# index so this handler is not called again recursively.
|
||||
user_type_combo.blockSignals(True)
|
||||
user_type_combo.setCurrentIndex(0)
|
||||
user_type_combo.blockSignals(False)
|
||||
self.USER_TYPE.set("basic")
|
||||
self.update_all()
|
||||
return
|
||||
self.USER_TYPE.set("advanced" if idx == 1 else "basic")
|
||||
self.update_all()
|
||||
|
||||
@@ -473,40 +496,29 @@ class Plugin(BalPlugin):
|
||||
# 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,
|
||||
"User Type",
|
||||
user_type_combo,
|
||||
0,
|
||||
(
|
||||
"Choose how much detail the plugin shows.\n\n"
|
||||
"BASIC (default): a simpler interface. It hides the advanced "
|
||||
"controls (the Raw/Date selector and the 'Check Alive' field) "
|
||||
"and turns off the 'check alive' postpone behaviour, so you "
|
||||
"only set the delivery date.\n\n"
|
||||
"ADVANCED: shows every control, including the 'Check Alive' "
|
||||
"field and the Raw/Date selector."
|
||||
),
|
||||
)
|
||||
# NOTE: the "User Type" row used to be the first row (row 0). It was moved
|
||||
# to the BOTTOM of the grid (just above "Rebroadcast transactions") at the
|
||||
# owner's request; see the add_widget call further below. The remaining
|
||||
# rows were renumbered up by one accordingly.
|
||||
add_widget(
|
||||
grid,
|
||||
"Hide Replaced",
|
||||
heir_hide_replaced,
|
||||
1,
|
||||
0,
|
||||
"Hide replaced transactions from will detail and list",
|
||||
)
|
||||
add_widget(
|
||||
grid,
|
||||
"Hide Invalidated",
|
||||
heir_hide_invalidated,
|
||||
2,
|
||||
1,
|
||||
"Hide invalidated transactions from will detail and list",
|
||||
)
|
||||
add_widget(
|
||||
grid,
|
||||
"Auto-sign on Check",
|
||||
heir_auto_sign,
|
||||
3,
|
||||
2,
|
||||
(
|
||||
"When checking, automatically sign and broadcast the will "
|
||||
"transactions to their will-executors.\n"
|
||||
@@ -518,7 +530,7 @@ class Plugin(BalPlugin):
|
||||
grid,
|
||||
"Panel editable Date and Fee",
|
||||
heir_editable_dates,
|
||||
4,
|
||||
3,
|
||||
(
|
||||
"When enabled, the delivery-time and check-alive date fields "
|
||||
"can be edited everywhere (toolbar / Heirs tab), not only in "
|
||||
@@ -540,7 +552,7 @@ class Plugin(BalPlugin):
|
||||
grid,
|
||||
"Add transaction without willexecutor",
|
||||
heir_no_willexecutor,
|
||||
5,
|
||||
4,
|
||||
(
|
||||
"Create a will that does not require a Will-executor; it can be "
|
||||
"saved, for example, on a USB stick, and a copy can be given to "
|
||||
@@ -551,7 +563,7 @@ class Plugin(BalPlugin):
|
||||
grid,
|
||||
"Number of reminders",
|
||||
heir_num_reminders,
|
||||
6,
|
||||
5,
|
||||
(
|
||||
"How many reminder alarms the exported calendar (.ics) event "
|
||||
"contains.\n\n"
|
||||
@@ -568,7 +580,7 @@ class Plugin(BalPlugin):
|
||||
grid,
|
||||
"Event summary",
|
||||
edit_event_summary,
|
||||
7,
|
||||
6,
|
||||
(
|
||||
"Default message to be used in event summary\n"
|
||||
"Variables:\n"
|
||||
@@ -581,7 +593,7 @@ class Plugin(BalPlugin):
|
||||
grid,
|
||||
"Event description",
|
||||
edit_event_description,
|
||||
8,
|
||||
7,
|
||||
(
|
||||
"Default message to be used in event description\n"
|
||||
"Variables:\n"
|
||||
@@ -607,6 +619,24 @@ class Plugin(BalPlugin):
|
||||
# "Ask before to ping willexecutor",
|
||||
# )
|
||||
# add_widget(grid,"Enable Multiverse(EXPERIMENTAL/BROKEN)",heir_enable_multiverse,6,"enable multiple locktimes, will import.... ")
|
||||
# "User Type" placed here (row 8), at the BOTTOM of the settings just
|
||||
# above "Rebroadcast transactions" (owner request). It used to be the very
|
||||
# first row; the other rows were renumbered up by one when it was moved.
|
||||
add_widget(
|
||||
grid,
|
||||
"User Type",
|
||||
user_type_combo,
|
||||
8,
|
||||
(
|
||||
"Choose how much detail the plugin shows.\n\n"
|
||||
"BASIC (default): a simpler interface. It hides the advanced "
|
||||
"controls (the Raw/Date selector and the 'Check Alive' field) "
|
||||
"and turns off the 'check alive' postpone behaviour, so you "
|
||||
"only set the delivery date.\n\n"
|
||||
"ADVANCED: shows every control, including the 'Check Alive' "
|
||||
"field and the Raw/Date selector."
|
||||
),
|
||||
)
|
||||
grid.addWidget(heir_repush, 9, 0)
|
||||
grid.addWidget(
|
||||
HelpButton(
|
||||
@@ -668,7 +698,7 @@ class Plugin(BalPlugin):
|
||||
# reflects the reset values.
|
||||
self.update_all()
|
||||
|
||||
btn_reset = QPushButton(_("Reset setting"))
|
||||
btn_reset = QPushButton(_("Reset to Default Setting"))
|
||||
btn_reset.setToolTip(_("Reset these settings to their default values"))
|
||||
btn_reset.clicked.connect(on_reset_defaults)
|
||||
|
||||
|
||||
@@ -388,6 +388,43 @@ class BalTimeEditWidget(QWidget, _LockTimeEditor):
|
||||
self.combo.setCurrentIndex(index)
|
||||
#self.on_current_index_changed(index, force)
|
||||
|
||||
def apply_user_type_visibility(self):
|
||||
"""Re-apply the BASIC/ADVANCED visibility of the Raw/Date selector.
|
||||
|
||||
WHY this is needed (bug reported by the owner, task #04): the Raw/Date
|
||||
combo is hidden in BASIC mode and shown in ADVANCED mode. That visibility
|
||||
used to be decided ONLY in ``__init__`` (see the ``self._basic_mode``
|
||||
block there). The WILL and HEIR tab toolbars are created once and then
|
||||
REUSED for the whole session (they are not rebuilt when the user switches
|
||||
USER TYPE), so after switching from BASIC to ADVANCED the combo stayed
|
||||
hidden there and the user could only use the "Date" editor. The same
|
||||
happened when a RAW value was pushed from the wizard: the raw number
|
||||
showed (the active editor was updated) but the selector box stayed hidden
|
||||
because nothing ever re-showed it. The wizard worked only because it is
|
||||
recreated every time it is opened.
|
||||
|
||||
This method re-reads ``is_basic_mode()`` and shows/hides ONLY the combo.
|
||||
It deliberately does NOT change the current value or the active editor
|
||||
(owner request: keep the current value to avoid confusing the delivery
|
||||
date with the inheritance). It is called from
|
||||
``WillSettingsWidget.apply_user_type_visibility()`` (triggered by
|
||||
``BalWindow.update_all()`` on a BASIC<->ADVANCED switch), so toggling the
|
||||
mode takes effect immediately on the already-existing WILL/HEIR toolbars
|
||||
without restarting Electrum.
|
||||
|
||||
It is safe to call repeatedly and on either layout (horizontal toolbar or
|
||||
vertical wizard): it only flips the visibility of the Raw/Date combo.
|
||||
"""
|
||||
try:
|
||||
basic = self.bal_window.bal_plugin.is_basic_mode()
|
||||
except Exception:
|
||||
# If the mode cannot be read, show the combo (the safe, most
|
||||
# capable default for an existing widget).
|
||||
basic = False
|
||||
# Hidden in BASIC, visible in ADVANCED. The current value/editor is left
|
||||
# untouched on purpose (see the docstring).
|
||||
self.combo.setVisible(not basic)
|
||||
|
||||
def set_value(
|
||||
self,
|
||||
x: Any,
|
||||
@@ -918,6 +955,18 @@ class WillSettingsWidget(QWidget):
|
||||
# Hidden in BASIC, visible in ADVANCED.
|
||||
threshold.setVisible(not basic)
|
||||
|
||||
# Task #04: also re-apply the Raw/Date selector visibility on BOTH the
|
||||
# delivery-time (locktime) and the check-alive (threshold) boxes. The
|
||||
# combo was hidden once at construction (BASIC) and the reused WILL/HEIR
|
||||
# toolbars never re-showed it after switching to ADVANCED. Each composite
|
||||
# decides its own combo visibility from is_basic_mode() WITHOUT touching
|
||||
# its current value or editor.
|
||||
for field in ("locktime", "threshold"):
|
||||
widget = self.widgets.get(field)
|
||||
apply_combo = getattr(widget, "apply_user_type_visibility", None)
|
||||
if callable(apply_combo):
|
||||
apply_combo()
|
||||
|
||||
def open_or_save_calendar(self):
|
||||
"""Build and save an .ics calendar file with SEPARATE reminder events.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "bal",
|
||||
"fullname": "Bitcoin After Life",
|
||||
"version": "0.4.7",
|
||||
"version": "0.4.8",
|
||||
"description": "Provides free and decentralized Bitcoin inheritance support. Build time-locked 'will' transactions that transfer funds to your heirs if you stop refreshing them (dead-man's switch), optionally relayed by will-executor servers.",
|
||||
"author": "Svatantrya",
|
||||
"licence": "MIT",
|
||||
|
||||
Reference in New Issue
Block a user