forked from bitcoinafterlife/bal-electrum-plugin
v0.5.10: Check Alive/BASIC fixes, UX improvements, Raw-Date handling.
This commit is contained in:
@@ -47,13 +47,24 @@ class BalCalendarButton(QToolButton):
|
|||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
def _ensure_ics(self):
|
def _ensure_ics(self):
|
||||||
"""Generate the .ics content and cache the temp file path."""
|
"""Generate the .ics content and cache the temp file path.
|
||||||
|
|
||||||
|
If the provider returns no content because there are no reminder events
|
||||||
|
to save (the delivery date is too close or already passed), warn the
|
||||||
|
user and do NOT create a file (ToDo #2).
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
content = self._ics_provider()
|
content = self._ics_provider()
|
||||||
if content:
|
if content:
|
||||||
self._calendar_temp_path = BalCalendar.write_temp_ics(content)
|
self._calendar_temp_path = BalCalendar.write_temp_ics(content)
|
||||||
else:
|
else:
|
||||||
self._calendar_temp_path = None
|
self._calendar_temp_path = None
|
||||||
|
self._bal_window.show_warning(
|
||||||
|
_(
|
||||||
|
"No reminders were saved: the delivery date is too "
|
||||||
|
"close (or already passed)"
|
||||||
|
)
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.error(f"failed to generate .ics: {e}")
|
_logger.error(f"failed to generate .ics: {e}")
|
||||||
self._calendar_temp_path = None
|
self._calendar_temp_path = None
|
||||||
|
|||||||
@@ -723,7 +723,7 @@ class BalBuildWillDialog(BalDialog):
|
|||||||
self.msg_set_status(
|
self.msg_set_status(
|
||||||
_("Checking your will"),
|
_("Checking your will"),
|
||||||
None,
|
None,
|
||||||
_("Inheritance already executed (on blockchain)"),
|
_("An inheritance of this wallet is already executed (on blockchain)"),
|
||||||
self.COLOR_OK,
|
self.COLOR_OK,
|
||||||
)
|
)
|
||||||
elif executed_status == "MEMPOOL":
|
elif executed_status == "MEMPOOL":
|
||||||
@@ -776,8 +776,14 @@ class BalBuildWillDialog(BalDialog):
|
|||||||
if not txs:
|
if not txs:
|
||||||
self.msg_set_building(
|
self.msg_set_building(
|
||||||
_(
|
_(
|
||||||
"Balance is too low, or CheckAlive is in the "
|
"Could not build the will ! Possible reasons:\n"
|
||||||
"past. Skipped"
|
"1- the Balance of wallet is too low to cover the "
|
||||||
|
"fees for miners and will executors,\n"
|
||||||
|
"2- the Heirs' shares are below the minimum (Dust "
|
||||||
|
"UTXO, less than 546 Satoshi),\n"
|
||||||
|
"3- the Check Alive Date/Time is later than the "
|
||||||
|
"delivery time (it must be earlier),\n"
|
||||||
|
"Skipped"
|
||||||
),
|
),
|
||||||
# Orange (warning) instead of red (error): an empty
|
# Orange (warning) instead of red (error): an empty
|
||||||
# wallet after the inheritance was executed is a NORMAL
|
# wallet after the inheritance was executed is a NORMAL
|
||||||
@@ -1531,6 +1537,12 @@ class BalBuildWillDialog(BalDialog):
|
|||||||
]
|
]
|
||||||
|
|
||||||
total = len(offsets)
|
total = len(offsets)
|
||||||
|
# ToDo #2: if no reminder falls in the future (the delivery date is
|
||||||
|
# too close or already passed), there are no events to write.
|
||||||
|
# Return None so the caller shows a clear warning instead of
|
||||||
|
# producing an empty, seemingly-broken .ics file.
|
||||||
|
if total == 0:
|
||||||
|
return None
|
||||||
for idx, offset in enumerate(offsets, start=1):
|
for idx, offset in enumerate(offsets, start=1):
|
||||||
event_dt = BalCalendar.format_time(locktime - timedelta(days=offset))
|
event_dt = BalCalendar.format_time(locktime - timedelta(days=offset))
|
||||||
summary = BalCalendar.ical_escape(
|
summary = BalCalendar.ical_escape(
|
||||||
|
|||||||
@@ -486,6 +486,7 @@ class Plugin(BalPlugin):
|
|||||||
user_type_combo.setCurrentIndex(0)
|
user_type_combo.setCurrentIndex(0)
|
||||||
user_type_combo.blockSignals(False)
|
user_type_combo.blockSignals(False)
|
||||||
self.USER_TYPE.set("basic")
|
self.USER_TYPE.set("basic")
|
||||||
|
self._apply_editor_default_on_toolbars()
|
||||||
self.update_all()
|
self.update_all()
|
||||||
return
|
return
|
||||||
self.USER_TYPE.set("advanced" if idx == 1 else "basic")
|
self.USER_TYPE.set("advanced" if idx == 1 else "basic")
|
||||||
@@ -500,6 +501,11 @@ class Plugin(BalPlugin):
|
|||||||
reset_btn_6, reset_btn_7, reset_btn_8, reset_btn_9, reset_btn_10,
|
reset_btn_6, reset_btn_7, reset_btn_8, reset_btn_9, reset_btn_10,
|
||||||
reset_btn_auto_sign):
|
reset_btn_auto_sign):
|
||||||
w.setVisible(not basic)
|
w.setVisible(not basic)
|
||||||
|
# Opzione 2: apply the per-mode Raw/Date editor default ONLY here, on
|
||||||
|
# a real USER TYPE change (not inside update_all/CHECK), so pressing
|
||||||
|
# CHECK never resets a manual Date/RAW choice. update_all() below
|
||||||
|
# still refreshes the transaction list and the combo visibility.
|
||||||
|
self._apply_editor_default_on_toolbars()
|
||||||
self.update_all()
|
self.update_all()
|
||||||
|
|
||||||
user_type_combo.currentIndexChanged.connect(on_user_type_change)
|
user_type_combo.currentIndexChanged.connect(on_user_type_change)
|
||||||
@@ -833,6 +839,28 @@ class Plugin(BalPlugin):
|
|||||||
for _k, w in self.bal_windows.items():
|
for _k, w in self.bal_windows.items():
|
||||||
w.update_all()
|
w.update_all()
|
||||||
|
|
||||||
|
def _apply_editor_default_on_toolbars(self):
|
||||||
|
"""Apply the per-mode Raw/Date editor default to the WILL/HEIR toolbars.
|
||||||
|
|
||||||
|
Called ONLY on a real USER TYPE (BASIC<->ADVANCED) change - never from
|
||||||
|
update_all()/CHECK - so pressing CHECK does not reset a manual Date/RAW
|
||||||
|
choice (Opzione 2). Guarded so a missing widget never breaks the switch.
|
||||||
|
"""
|
||||||
|
for _k, w in self.bal_windows.items():
|
||||||
|
for _list in (getattr(w, "heir_list_widget", None),
|
||||||
|
getattr(w, "will_list_widget", None)):
|
||||||
|
settings_widget = getattr(_list, "will_settings_widget", None)
|
||||||
|
apply_default = getattr(
|
||||||
|
settings_widget, "apply_user_type_editor_default", None
|
||||||
|
)
|
||||||
|
if callable(apply_default):
|
||||||
|
try:
|
||||||
|
apply_default()
|
||||||
|
except Exception as _e:
|
||||||
|
_logger.debug(
|
||||||
|
f"apply_user_type_editor_default error: {_e}"
|
||||||
|
)
|
||||||
|
|
||||||
def get_window_title(self, title):
|
def get_window_title(self, title):
|
||||||
return _("BAL - ") + _(title)
|
return _("BAL - ") + _(title)
|
||||||
|
|
||||||
|
|||||||
@@ -291,18 +291,39 @@ class BalTimeEditWidget(QWidget, _LockTimeEditor):
|
|||||||
# combo is hidden. We keep a flag so the rest of __init__ can force the
|
# combo is hidden. We keep a flag so the rest of __init__ can force the
|
||||||
# Date editor regardless of the stored value's format.
|
# Date editor regardless of the stored value's format.
|
||||||
self._basic_mode = self.bal_window.bal_plugin.is_basic_mode()
|
self._basic_mode = self.bal_window.bal_plugin.is_basic_mode()
|
||||||
|
# Tracks whether the user PICKED an editor by hand from the Raw/Date
|
||||||
|
# selector (set only on the combo's `activated` signal, i.e. real user
|
||||||
|
# interaction). It lets the runtime BASIC<->ADVANCED switch respect a
|
||||||
|
# manual "Date" choice in ADVANCED instead of forcing RAW back. It stays
|
||||||
|
# False for programmatic changes (defaults, switches).
|
||||||
|
self._user_picked_editor = False
|
||||||
default_index = 0
|
default_index = 0
|
||||||
if not default_locktime:
|
if not default_locktime:
|
||||||
default_locktime = self.bal_window.bal_plugin.WILL_SETTINGS.get()[self.base_field]
|
default_locktime = self.bal_window.bal_plugin.WILL_SETTINGS.get()[self.base_field]
|
||||||
try:
|
# Default editor per mode (owner request):
|
||||||
int(default_locktime)
|
# * BASIC -> Date editor (index 1); the Raw/Date selector is hidden,
|
||||||
default_index = 1
|
# so the user always picks a date and never sees RAW.
|
||||||
except Exception:
|
# * ADVANCED -> RAW editor (index 0) by default; the selector stays
|
||||||
default_index = 0
|
# visible so the user can switch to Date manually.
|
||||||
# Force the calendar ("Date") editor in BASIC mode so the user always
|
|
||||||
# picks a date and never sees the RAW input ("30d"/"1y" style).
|
|
||||||
if self._basic_mode:
|
if self._basic_mode:
|
||||||
default_index = 1
|
default_index = 1
|
||||||
|
else:
|
||||||
|
default_index = 0
|
||||||
|
# In ADVANCED we default to RAW. If the stored value is an absolute
|
||||||
|
# timestamp (a bare number), showing it in RAW would display that
|
||||||
|
# raw number; substitute the relative default ("1y"/"30d") so the
|
||||||
|
# RAW editor opens with a human-readable duration instead.
|
||||||
|
try:
|
||||||
|
int(default_locktime)
|
||||||
|
is_absolute = True
|
||||||
|
except Exception:
|
||||||
|
is_absolute = False
|
||||||
|
if is_absolute:
|
||||||
|
default_locktime = (
|
||||||
|
self.bal_window.bal_plugin.default_will_settings_relative()[
|
||||||
|
self.base_field
|
||||||
|
]
|
||||||
|
)
|
||||||
#hbox.addWidget(QLabel(self.label_text))
|
#hbox.addWidget(QLabel(self.label_text))
|
||||||
help_button=HelpButton(self.help_text)
|
help_button=HelpButton(self.help_text)
|
||||||
help_button.setText(self.label_text)
|
help_button.setText(self.label_text)
|
||||||
@@ -316,6 +337,11 @@ class BalTimeEditWidget(QWidget, _LockTimeEditor):
|
|||||||
# align all rows on a common left edge (see its vertical layout).
|
# align all rows on a common left edge (see its vertical layout).
|
||||||
self.prefix_widget = help_button
|
self.prefix_widget = help_button
|
||||||
self.combo.currentIndexChanged.connect(self.on_current_index_changed)
|
self.combo.currentIndexChanged.connect(self.on_current_index_changed)
|
||||||
|
# `activated` fires ONLY on real user interaction with the selector (not
|
||||||
|
# on programmatic setCurrentIndex), so we use it to remember that the
|
||||||
|
# user picked the editor by hand. This is what lets the runtime
|
||||||
|
# BASIC<->ADVANCED switch respect a manual "Date" choice in ADVANCED.
|
||||||
|
self.combo.activated.connect(self._on_user_picked_editor)
|
||||||
|
|
||||||
for w in self.editors:
|
for w in self.editors:
|
||||||
w.setVisible(False)
|
w.setVisible(False)
|
||||||
@@ -360,6 +386,16 @@ class BalTimeEditWidget(QWidget, _LockTimeEditor):
|
|||||||
update_heirs_dialog,
|
update_heirs_dialog,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _on_user_picked_editor(self, i):
|
||||||
|
"""Record that the user chose the Raw/Date editor by hand.
|
||||||
|
|
||||||
|
Connected to the combo's ``activated`` signal, which only fires on real
|
||||||
|
user interaction (not on programmatic ``setCurrentIndex``). Used by the
|
||||||
|
runtime BASIC<->ADVANCED switch to respect a manual "Date" choice in
|
||||||
|
ADVANCED instead of forcing RAW back.
|
||||||
|
"""
|
||||||
|
self._user_picked_editor = True
|
||||||
|
|
||||||
def on_current_index_changed(self, i):
|
def on_current_index_changed(self, i):
|
||||||
self.current_index = i
|
self.current_index = i
|
||||||
for w in self.editors:
|
for w in self.editors:
|
||||||
@@ -408,9 +444,11 @@ class BalTimeEditWidget(QWidget, _LockTimeEditor):
|
|||||||
(owner request: keep the current value to avoid confusing the delivery
|
(owner request: keep the current value to avoid confusing the delivery
|
||||||
date with the inheritance). It is called from
|
date with the inheritance). It is called from
|
||||||
``WillSettingsWidget.apply_user_type_visibility()`` (triggered by
|
``WillSettingsWidget.apply_user_type_visibility()`` (triggered by
|
||||||
``BalWindow.update_all()`` on a BASIC<->ADVANCED switch), so toggling the
|
``BalWindow.update_all()``, which also runs on every CHECK/refresh), so
|
||||||
mode takes effect immediately on the already-existing WILL/HEIR toolbars
|
it must be side-effect free on the editor - otherwise pressing CHECK
|
||||||
without restarting Electrum.
|
would reset a manual Date/RAW choice back to the default. The actual
|
||||||
|
per-mode editor default lives in ``apply_user_type_editor_default()``,
|
||||||
|
which is called ONLY on a real USER TYPE change.
|
||||||
|
|
||||||
It is safe to call repeatedly and on either layout (horizontal toolbar or
|
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.
|
vertical wizard): it only flips the visibility of the Raw/Date combo.
|
||||||
@@ -421,10 +459,37 @@ class BalTimeEditWidget(QWidget, _LockTimeEditor):
|
|||||||
# If the mode cannot be read, show the combo (the safe, most
|
# If the mode cannot be read, show the combo (the safe, most
|
||||||
# capable default for an existing widget).
|
# capable default for an existing widget).
|
||||||
basic = False
|
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)
|
self.combo.setVisible(not basic)
|
||||||
|
|
||||||
|
def apply_user_type_editor_default(self):
|
||||||
|
"""Set the ACTIVE Raw/Date editor to the per-mode default.
|
||||||
|
|
||||||
|
Split out from ``apply_user_type_visibility`` (Opzione 2) so it runs
|
||||||
|
ONLY on a real BASIC<->ADVANCED USER TYPE change, not on every
|
||||||
|
``update_all()``/CHECK refresh. That is what stopped pressing CHECK from
|
||||||
|
resetting a manual Date choice back to RAW.
|
||||||
|
|
||||||
|
Behaviour:
|
||||||
|
* BASIC -> always the Date editor (index 1); the selector is hidden,
|
||||||
|
so the field must not stay on RAW.
|
||||||
|
* ADVANCED -> RAW by default (index 0), UNLESS the user previously
|
||||||
|
picked an editor by hand (``_user_picked_editor``), in
|
||||||
|
which case their choice is left untouched.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
basic = self.bal_window.bal_plugin.is_basic_mode()
|
||||||
|
except Exception:
|
||||||
|
basic = False
|
||||||
|
try:
|
||||||
|
if basic:
|
||||||
|
self.set_index(1)
|
||||||
|
else:
|
||||||
|
if not self._user_picked_editor:
|
||||||
|
self.set_index(0)
|
||||||
|
except Exception:
|
||||||
|
# Never let a cosmetic editor switch break mode toggling.
|
||||||
|
pass
|
||||||
|
|
||||||
def set_value(
|
def set_value(
|
||||||
self,
|
self,
|
||||||
x: Any,
|
x: Any,
|
||||||
@@ -653,6 +718,8 @@ class ThresholdTimeWidget(BalTimeEditWidget):
|
|||||||
"if you choose Raw, you can insert various options based on suffix:<br>"
|
"if you choose Raw, you can insert various options based on suffix:<br>"
|
||||||
" - d: number of days after current day(ex: 1d means tomorrow)<br>"
|
" - d: number of days after current day(ex: 1d means tomorrow)<br>"
|
||||||
" - y: number of years after current day(ex: 1y means one year from today)<br>"
|
" - y: number of years after current day(ex: 1y means one year from today)<br>"
|
||||||
|
"<br><b>Note:</b> the date/time is expressed in your computer's "
|
||||||
|
"<b>Local time</b> (not UTC time).<br>"
|
||||||
)
|
)
|
||||||
label_text = "🚨"
|
label_text = "🚨"
|
||||||
#label_text = "Check Alive"
|
#label_text = "Check Alive"
|
||||||
@@ -682,6 +749,8 @@ class LockTimeWidget(BalTimeEditWidget):
|
|||||||
"if you choose Raw, you can insert various options based on suffix:<br>"
|
"if you choose Raw, you can insert various options based on suffix:<br>"
|
||||||
" - d: number of days after current day(ex: 1d means tomorrow)<br>"
|
" - d: number of days after current day(ex: 1d means tomorrow)<br>"
|
||||||
" - y: number of years after currrent day(ex: 1y means one year from today)<br>"
|
" - y: number of years after currrent day(ex: 1y means one year from today)<br>"
|
||||||
|
"<br><b>Note:</b> the date/time is expressed in your computer's "
|
||||||
|
"<b>Local time</b> (not UTC time).<br>"
|
||||||
)
|
)
|
||||||
label_text = "🚛"
|
label_text = "🚛"
|
||||||
#label_text = "Locktime"
|
#label_text = "Locktime"
|
||||||
@@ -711,6 +780,12 @@ class WillSettingsWidget(QWidget):
|
|||||||
# alive and fee fields are display-only; they can only be edited from
|
# alive and fee fields are display-only; they can only be edited from
|
||||||
# the "Build your will" wizard, which passes read_only=False.
|
# the "Build your will" wizard, which passes read_only=False.
|
||||||
self.read_only = read_only
|
self.read_only = read_only
|
||||||
|
# Remember which layout this widget was built with, so the per-update
|
||||||
|
# hooks (apply_editable_dates / apply_user_type_visibility) can tell the
|
||||||
|
# main-window toolbar ("h") apart from the wizard (vertical) - needed
|
||||||
|
# for the BASIC-mode Check-Alive "visible but read-only in the main
|
||||||
|
# window only" behaviour.
|
||||||
|
self.layout_type = layout_type
|
||||||
box = QHBoxLayout(self) if layout_type == "h" else QVBoxLayout(self)
|
box = QHBoxLayout(self) if layout_type == "h" else QVBoxLayout(self)
|
||||||
|
|
||||||
self.calendar_button = BalCalendarButton(self.bal_window, self._ics_provider)
|
self.calendar_button = BalCalendarButton(self.bal_window, self._ics_provider)
|
||||||
@@ -726,13 +801,13 @@ class WillSettingsWidget(QWidget):
|
|||||||
self.widgets["threshold"] = ThresholdTimeWidget(bal_window, self)
|
self.widgets["threshold"] = ThresholdTimeWidget(bal_window, self)
|
||||||
self.widgets["locktime"].valueEdited.connect(self.on_locktime_change)
|
self.widgets["locktime"].valueEdited.connect(self.on_locktime_change)
|
||||||
self.widgets["threshold"].valueEdited.connect(self.on_locktime_change)
|
self.widgets["threshold"].valueEdited.connect(self.on_locktime_change)
|
||||||
# SIMPLE / ADVANCED: in BASIC mode hide the whole "Check Alive"
|
# SIMPLE / ADVANCED: in BASIC mode the "Check Alive" (threshold) row is
|
||||||
# (threshold) row, including its leading icon. The widget is still
|
# hidden EVERYWHERE (both the main-window toolbar and the wizard). The
|
||||||
# created and kept in self.widgets so the rest of the code (and the
|
# user must never see or touch the Check Alive in BASIC. The widget is
|
||||||
# saved settings) keep working; it is only hidden from view. The
|
# still created and kept in self.widgets so the rest of the code and the
|
||||||
# Delivery time (locktime) row stays visible. We hide it after creation
|
# saved settings keep working; it is only hidden from view. (This
|
||||||
# so both the horizontal (toolbar/Heirs) and vertical (wizard) layouts
|
# reverts the v0.5.2 experiment that had shown it read-only in the main
|
||||||
# below add an already-hidden widget.
|
# window - owner asked to hide it again.)
|
||||||
if bal_window.bal_plugin.is_basic_mode():
|
if bal_window.bal_plugin.is_basic_mode():
|
||||||
self.widgets["threshold"].setVisible(False)
|
self.widgets["threshold"].setVisible(False)
|
||||||
# self.widgets['baltx_fees'].valueChange.connect(self.bal_window.update_setting_widgets)
|
# self.widgets['baltx_fees'].valueChange.connect(self.bal_window.update_setting_widgets)
|
||||||
@@ -954,7 +1029,9 @@ class WillSettingsWidget(QWidget):
|
|||||||
basic = False
|
basic = False
|
||||||
threshold = self.widgets.get("threshold")
|
threshold = self.widgets.get("threshold")
|
||||||
if threshold is not None:
|
if threshold is not None:
|
||||||
# Hidden in BASIC, visible in ADVANCED.
|
# Hidden in BASIC, visible in ADVANCED - in BOTH layouts (main
|
||||||
|
# window toolbar and wizard). Reverts the v0.5.2 experiment that
|
||||||
|
# kept it visible in the main window in BASIC.
|
||||||
threshold.setVisible(not basic)
|
threshold.setVisible(not basic)
|
||||||
|
|
||||||
# Task #04: also re-apply the Raw/Date selector visibility on BOTH the
|
# Task #04: also re-apply the Raw/Date selector visibility on BOTH the
|
||||||
@@ -969,6 +1046,19 @@ class WillSettingsWidget(QWidget):
|
|||||||
if callable(apply_combo):
|
if callable(apply_combo):
|
||||||
apply_combo()
|
apply_combo()
|
||||||
|
|
||||||
|
def apply_user_type_editor_default(self):
|
||||||
|
"""Apply the per-mode Raw/Date editor default to both time fields.
|
||||||
|
|
||||||
|
Called ONLY on a real BASIC<->ADVANCED USER TYPE change (not on every
|
||||||
|
refresh/CHECK), so a manual Date/RAW choice is preserved when the user
|
||||||
|
just presses CHECK. See BalTimeEditWidget.apply_user_type_editor_default.
|
||||||
|
"""
|
||||||
|
for field in ("locktime", "threshold"):
|
||||||
|
widget = self.widgets.get(field)
|
||||||
|
apply_default = getattr(widget, "apply_user_type_editor_default", None)
|
||||||
|
if callable(apply_default):
|
||||||
|
apply_default()
|
||||||
|
|
||||||
def open_or_save_calendar(self):
|
def open_or_save_calendar(self):
|
||||||
"""Build and save an .ics calendar file with SEPARATE reminder events.
|
"""Build and save an .ics calendar file with SEPARATE reminder events.
|
||||||
|
|
||||||
@@ -1248,6 +1338,43 @@ class WillSettingsWidget(QWidget):
|
|||||||
except Exception as _e:
|
except Exception as _e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# VISUAL WARNING (ToDo #5): the Check Alive must always be EARLIER than
|
||||||
|
# the delivery time. If the user sets a Check Alive that is later than
|
||||||
|
# (or equal to) the delivery time, tint its box with a soft red so the
|
||||||
|
# problem is obvious immediately, instead of only surfacing later as a
|
||||||
|
# build error. Applied only in ADVANCED mode (in BASIC the Check Alive
|
||||||
|
# is hidden and not editable, so there is nothing to warn about). The
|
||||||
|
# tint is cleared again as soon as the relationship becomes valid.
|
||||||
|
try:
|
||||||
|
SOFT_RED = "#FCE4E4"
|
||||||
|
threshold_box = self.widgets["threshold"]
|
||||||
|
if not self.bal_window.bal_plugin.is_basic_mode():
|
||||||
|
if threshold.to_timestamp() >= locktime.to_timestamp():
|
||||||
|
# Only set a background colour, and DO NOT touch the border:
|
||||||
|
# styling the border of a QDateTimeEdit/QAbstractSpinBox via
|
||||||
|
# stylesheet makes Qt stop drawing its native up/down spin
|
||||||
|
# arrows. Scoping the rule to the inner editor widget types
|
||||||
|
# (rather than the whole container) keeps the tint on the
|
||||||
|
# value box while leaving the arrows intact.
|
||||||
|
threshold_box.setStyleSheet(
|
||||||
|
"QDateTimeEdit, QLineEdit, QAbstractSpinBox "
|
||||||
|
"{ background-color: %s; }" % SOFT_RED
|
||||||
|
)
|
||||||
|
threshold_box.setToolTip(
|
||||||
|
_(
|
||||||
|
"The Check Alive date must be earlier than the "
|
||||||
|
"delivery time."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
threshold_box.setStyleSheet("")
|
||||||
|
threshold_box.setToolTip("")
|
||||||
|
else:
|
||||||
|
# BASIC: never show the warning styling (field is hidden).
|
||||||
|
threshold_box.setStyleSheet("")
|
||||||
|
except Exception as _e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PercAmountEdit(BTCAmountEdit):
|
class PercAmountEdit(BTCAmountEdit):
|
||||||
|
|||||||
@@ -325,6 +325,10 @@ class BalWindow:
|
|||||||
raise NoWillExecutorNotPresent(
|
raise NoWillExecutorNotPresent(
|
||||||
"No Will-Executor or backup transaction selected"
|
"No Will-Executor or backup transaction selected"
|
||||||
)
|
)
|
||||||
|
# date_to_check already carries the correct reference timestamp for
|
||||||
|
# the current mode (the Check Alive in ADVANCED, or "now" in BASIC -
|
||||||
|
# see init_class_variables). So build the will directly against it;
|
||||||
|
# no per-mode branch is needed here anymore.
|
||||||
txs = self.heirs.get_transactions(
|
txs = self.heirs.get_transactions(
|
||||||
self.bal_plugin,
|
self.bal_plugin,
|
||||||
self.window.wallet,
|
self.window.wallet,
|
||||||
@@ -451,74 +455,33 @@ class BalWindow:
|
|||||||
for heir, value in updates.items():
|
for heir, value in updates.items():
|
||||||
self.heirs[heir] = value
|
self.heirs[heir] = value
|
||||||
|
|
||||||
# How long BEFORE the delivery time (locktime) the auto-computed Check
|
|
||||||
# Alive threshold is placed in BASIC mode (see compute_date_to_check).
|
|
||||||
# Owner-approved value: 2 hours. Kept as a named constant so the offset
|
|
||||||
# is documented in one place and easy to tune later if needed.
|
|
||||||
BASIC_MODE_CHECK_ALIVE_OFFSET_SECONDS = 2 * 60 * 60
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def compute_date_to_check(is_basic_mode, locktime_setting, threshold_setting):
|
|
||||||
"""Resolve the "Check Alive" reference timestamp (``date_to_check``).
|
|
||||||
|
|
||||||
SIMPLE / ADVANCED: the "Check Alive" (threshold) field is hidden in
|
|
||||||
BASIC mode, so the user can never keep it in sync with the delivery
|
|
||||||
time (locktime) when they anticipate/postpone it from the wizard.
|
|
||||||
Left untouched, the threshold stays at its old/default value (e.g.
|
|
||||||
"today + 11 months") and, if the user later sets a delivery date
|
|
||||||
EARLIER than that, two checks elsewhere (the "locktime is lower than
|
|
||||||
threshold" guard in ``build_inheritance_transaction``, and
|
|
||||||
``Will.check_will_expired`` via ``date_to_check``) misfire and
|
|
||||||
block/expire the will - even though the postpone/expiry check itself
|
|
||||||
is meant to be inert in BASIC mode (see the ``is_basic_mode()`` guard
|
|
||||||
in ``init_class_variables``).
|
|
||||||
|
|
||||||
FIX: in BASIC mode only, ignore the stored threshold and instead
|
|
||||||
derive ``date_to_check`` LIVE from the current delivery time
|
|
||||||
(``locktime_setting``), placed a small, fixed offset BEFORE it
|
|
||||||
(``BASIC_MODE_CHECK_ALIVE_OFFSET_SECONDS`` = 2 hours). This keeps
|
|
||||||
``date_to_check`` always < locktime by construction, so anticipating
|
|
||||||
or postponing the delivery date in BASIC mode never triggers a
|
|
||||||
spurious "expired" / "threshold" error. In ADVANCED mode nothing
|
|
||||||
changes: the stored threshold is used as-is, exactly like before.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
is_basic_mode: ``BalPlugin.is_basic_mode()`` result.
|
|
||||||
locktime_setting: the current ``will_settings["locktime"]`` value
|
|
||||||
(relative string like ``"1y"`` or an absolute timestamp).
|
|
||||||
threshold_setting: the current ``will_settings["threshold"]``
|
|
||||||
value, used as-is in ADVANCED mode and as a fallback if the
|
|
||||||
locktime cannot be parsed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
float: the resolved ``date_to_check`` UNIX timestamp.
|
|
||||||
"""
|
|
||||||
if is_basic_mode:
|
|
||||||
try:
|
|
||||||
locktime_ts = Util.parse_locktime_string(locktime_setting)
|
|
||||||
# Util.parse_locktime_string never raises: on anything it
|
|
||||||
# cannot parse it silently returns 0 (see bal/core/util.py).
|
|
||||||
# Treat that sentinel the same as a parse error, otherwise we
|
|
||||||
# would compute a nonsensical date_to_check near the UNIX
|
|
||||||
# epoch instead of falling back to the stored threshold.
|
|
||||||
if locktime_ts:
|
|
||||||
return (
|
|
||||||
locktime_ts
|
|
||||||
- BalWindow.BASIC_MODE_CHECK_ALIVE_OFFSET_SECONDS
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return BalTimestamp(threshold_setting).to_timestamp()
|
|
||||||
|
|
||||||
def init_class_variables(self):
|
def init_class_variables(self):
|
||||||
if not self.heirs:
|
if not self.heirs:
|
||||||
raise NoHeirsException(_("Heirs are not defined"))
|
raise NoHeirsException(_("Heirs are not defined"))
|
||||||
try:
|
try:
|
||||||
self.date_to_check = BalWindow.compute_date_to_check(
|
# SIMPLE / ADVANCED root behaviour of the "Check Alive" (threshold).
|
||||||
self.bal_plugin.is_basic_mode(),
|
#
|
||||||
self.will_settings["locktime"],
|
# self.date_to_check is the single reference timestamp that EVERY
|
||||||
self.will_settings["threshold"],
|
# downstream validity check reads: the build filter
|
||||||
)
|
# (get_locktimes/get_transactions), the heir-count in
|
||||||
|
# check_willexecutors_and_heirs ("No Heirs"), check_will_expired,
|
||||||
|
# check_amounts, and the "locktime is lower than threshold" guard.
|
||||||
|
#
|
||||||
|
# In BASIC mode the Check Alive is HIDDEN and NOT editable by the
|
||||||
|
# user, so it must never govern any of those checks. Setting
|
||||||
|
# date_to_check to the Check Alive there caused the will to be
|
||||||
|
# wrongly blocked whenever the (fixed) Check Alive ended up later
|
||||||
|
# than the delivery time (e.g. "No Heirs" even with heirs present,
|
||||||
|
# or a will that refused to (re)build). We therefore set
|
||||||
|
# date_to_check to "now" in BASIC: every check is then evaluated
|
||||||
|
# against the current moment, i.e. the Check Alive effectively does
|
||||||
|
# not exist, while the delivery time (locktime) is still fully
|
||||||
|
# enforced. ADVANCED mode keeps the user-controlled Check Alive
|
||||||
|
# exactly as before.
|
||||||
|
if self.bal_plugin.is_basic_mode():
|
||||||
|
self.date_to_check = datetime.now().timestamp()
|
||||||
|
else:
|
||||||
|
self.date_to_check = BalTimestamp(self.will_settings['threshold']).to_timestamp()
|
||||||
# found = False
|
# found = False
|
||||||
# NOTE: block-height tracking removed (A1) - locktimes are always
|
# NOTE: block-height tracking removed (A1) - locktimes are always
|
||||||
# UNIX timestamps now, so we no longer read the current block height
|
# UNIX timestamps now, so we no longer read the current block height
|
||||||
|
|||||||
Reference in New Issue
Block a user