UI polish and signed-tx colour fix (v0.3.3)
Fix and refine the BAL plugin GUI without changing business logic: core/will.py: restore the PUSHED requirement in needs_server_check so a signed-but-not-broadcast will is no longer server-queried and therefore stays blue (COMPLETE) instead of turning red (CHECK_FAIL). This matches the original Gitea check() condition. gui/qt/widgets.py: WillSettingsWidget vertical layout now caps every row to the widest date-row width and left-aligns them; the leading icons keep their original HelpButton width. gui/qt/lists.py + gui/qt/common.py: the wizard toolbar button now shows a 28x28 icon plus a bold 'Create your will' caption (QSize imported). gui/qt/dialogs.py (BalBuildWillDialog): - closing summary row labelled 'All done: Ok' with a blank separator above it; - 'checking variables' capitalised to 'Checking variables' (redundant trailing colon dropped); - final auto-closing countdown replaced by an explicit right-aligned 'Close' button; intermediate technical pauses kept; next-steps popup preserved. gui/qt/window.py + core/plugin_base.py: guide show_message on build, and sync_hide_filters() in update_all so hide flags refresh immediately. tests: test_needs_server_check updated; added offscreen preview helpers. Version bumped to 0.3.3. 186 tests pass; ruff clean (baseline only).
This commit is contained in:
committed by
steal
parent
365824767b
commit
30bab62247
@@ -609,3 +609,62 @@ Modifica centralizzata negli helper `msg_ok`, `msg_error`, `msg_warning`,
|
|||||||
Confermato dall'utente sui dati reali: dopo Sign -> Broadcast -> Check le
|
Confermato dall'utente sui dati reali: dopo Sign -> Broadcast -> Check le
|
||||||
transazioni gia inviate sono tornate verdi ("confirmed on server"); la lista
|
transazioni gia inviate sono tornate verdi ("confirmed on server"); la lista
|
||||||
torna pulita; il grassetto e l'aggiornamento delle hide-flag funzionano.
|
torna pulita; il grassetto e l'aggiornamento delle hide-flag funzionano.
|
||||||
|
|
||||||
|
## 17. UI polish and bug fix — signed-tx colour, wizard, Building Will dialog (v0.3.3)
|
||||||
|
|
||||||
|
This session groups several behaviour-invariant UI refinements plus one colour
|
||||||
|
bug fix. All comments and code remain in English; only the chat with the
|
||||||
|
author was in Italian.
|
||||||
|
|
||||||
|
### FIX — Signed-but-not-sent transaction shown RED instead of blue
|
||||||
|
`core/will.py` (`needs_server_check`): a previous-session change (section 16,
|
||||||
|
"FIX 2") had removed the `PUSHED` requirement from `needs_server_check`, so a
|
||||||
|
will that was *signed but never broadcast* was still server-queried. The query
|
||||||
|
returned CHECK_FAIL, and because `status_color()` checks CHECK_FAIL (red,
|
||||||
|
`#e83845`) before COMPLETE (blue, `#2bc8ed`), the row turned red. Restored the
|
||||||
|
original Gitea `check()` condition by adding back `and w.get_status("PUSHED")`,
|
||||||
|
so only already-broadcast wills are server-checked. A signed-but-not-sent will
|
||||||
|
now stays blue (COMPLETE) as in the original.
|
||||||
|
- `tests/test_core_will.py` (`test_needs_server_check`): a freshly-built item
|
||||||
|
(VALID, not PUSHED) now correctly expects `False`.
|
||||||
|
|
||||||
|
### Wizard "Will Settings" — equal-width, left-aligned rows
|
||||||
|
`gui/qt/widgets.py` (`WillSettingsWidget`, vertical layout): the calendar button
|
||||||
|
and the fee field used to stretch to the dialog's right edge, far wider than the
|
||||||
|
date rows. Now every row is capped to the widest date-row width (`row_w`) and
|
||||||
|
left-aligned, so they form a tidy column. The leading icons keep their original
|
||||||
|
`HelpButton` width (`icon_w` is used only as a spacer in front of the calendar,
|
||||||
|
never to widen the icons themselves).
|
||||||
|
|
||||||
|
### Wizard button — icon + text
|
||||||
|
`gui/qt/lists.py` (`create_toolbar`): the "build your will" toolbar button is now
|
||||||
|
more inviting: a 28×28 wizard icon plus a bold `"Create your will"` caption,
|
||||||
|
`setMinimumHeight(40)`. `gui/qt/common.py` gained `QSize` in the QtCore import.
|
||||||
|
|
||||||
|
### Building Will dialog — clearer final report + manual Close
|
||||||
|
`gui/qt/dialogs.py` (`BalBuildWillDialog`):
|
||||||
|
- The closing summary line is no longer a bare "Ok": it now has an explicit
|
||||||
|
left-side label, `"All done: Ok"`, like the other result rows.
|
||||||
|
- A blank separator row is inserted above "All done" so the overall outcome is
|
||||||
|
visually detached from the per-step rows.
|
||||||
|
- The four `"checking variables"` status strings are capitalised to
|
||||||
|
`"Checking variables"` to match the rows below; the redundant trailing colon
|
||||||
|
on the final one was dropped (`msg_set_status` already adds `":\t"`).
|
||||||
|
- The final auto-closing countdown (`self.wait(5)`) was replaced by an explicit
|
||||||
|
right-aligned **"Close"** button (`_add_close_button` / `_on_close_clicked`).
|
||||||
|
The dialog now stays open until the user dismisses it, so the full report can
|
||||||
|
be read at leisure. The intermediate technical pauses (`wait(10)`, `wait(5)`,
|
||||||
|
`wait(3)`) are kept. Closing still shows the persistent "next steps"
|
||||||
|
(Sign / Broadcast) popup when `self._next_steps_hint` is set.
|
||||||
|
|
||||||
|
### Preview helpers (dev-only, not shipped logic)
|
||||||
|
`tests/preview_wizard_settings_align.py`, `tests/preview_wizard_button.py`,
|
||||||
|
`tests/preview_building_will_close_btn.py`: small offscreen scripts used to
|
||||||
|
render before/after mock-ups for visual approval.
|
||||||
|
|
||||||
|
### Test
|
||||||
|
- 186 official tests pass; smoke test, external-zip test OK.
|
||||||
|
- `ruff` reports only the pre-existing baseline false positives (F401/F403/F405
|
||||||
|
star-import re-exports, one F841, one F541) — no new issues.
|
||||||
|
- Version bumped to **0.3.3** (`bal/VERSION`, `bal/__init__.py`,
|
||||||
|
`bal/manifest.json`).
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.3.2
|
0.3.3
|
||||||
@@ -34,4 +34,4 @@ The plugin targets Electrum 4.7.2 (the last stable release exposing
|
|||||||
``json_db.register_dict``) and PyQt6.
|
``json_db.register_dict``) and PyQt6.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.3.2"
|
__version__ = "0.3.3"
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class BalPlugin(BasePlugin):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
_version = None
|
_version = None
|
||||||
__version__ = "0.3.2" # AUTOMATICALLY GENERATED DO NOT EDIT
|
__version__ = "0.3.3" # AUTOMATICALLY GENERATED DO NOT EDIT
|
||||||
|
|
||||||
# Command used to open an .ics calendar file, per operating system.
|
# Command used to open an .ics calendar file, per operating system.
|
||||||
default_app = {
|
default_app = {
|
||||||
|
|||||||
@@ -88,17 +88,20 @@ class Will:
|
|||||||
"""Return True if ``w`` should be queried on its will-executor server
|
"""Return True if ``w`` should be queried on its will-executor server
|
||||||
when the user presses Check (or on Electrum close).
|
when the user presses Check (or on Electrum close).
|
||||||
|
|
||||||
A will needs a server check when it is VALID, has a will-executor
|
A will is queried only when it is VALID, has a will-executor assigned,
|
||||||
assigned, and is not yet CHECKED. This intentionally includes wills
|
was actually PUSHED (sent), and is not yet CHECKED. The ``PUSHED``
|
||||||
that are not (yet) marked PUSHED: a will that was actually sent in the
|
condition is essential: querying the server for a will that was *never*
|
||||||
past but whose saved status still reads "New" would otherwise be
|
sent would make the server (correctly) answer "I don't have this tx",
|
||||||
skipped, leaving the Server column stuck on "Not sent". The server
|
which ``WillItem.set_check_willexecutor`` then records as CHECK_FAIL.
|
||||||
response (see WillItem.set_check_willexecutor) then corrects the status
|
A freshly signed-but-not-sent will would therefore turn red, even though
|
||||||
to PUSHED/CHECKED if the transaction is present, or CHECK_FAIL if not.
|
it is merely "signed, not sent" (which must stay blue / #2bc8ed, as in
|
||||||
|
the original BAL behaviour). Restricting the check to PUSHED wills
|
||||||
|
matches the original ``check()`` logic and avoids that false failure.
|
||||||
"""
|
"""
|
||||||
return bool(
|
return bool(
|
||||||
w.get_status("VALID")
|
w.get_status("VALID")
|
||||||
and w.we
|
and w.we
|
||||||
|
and w.get_status("PUSHED")
|
||||||
and not w.get_status("CHECKED")
|
and not w.get_status("CHECKED")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ from electrum.transaction import SerializationError, Transaction, tx_from_any
|
|||||||
from electrum.util import (DECIMAL_POINT, FileExportFailed, UserCancelled,
|
from electrum.util import (DECIMAL_POINT, FileExportFailed, UserCancelled,
|
||||||
decimal_point_to_base_unit_name, read_json_file,
|
decimal_point_to_base_unit_name, read_json_file,
|
||||||
write_json_file)
|
write_json_file)
|
||||||
from PyQt6.QtCore import (QDateTime, QModelIndex, QPersistentModelIndex, Qt,
|
from PyQt6.QtCore import (QDateTime, QModelIndex, QPersistentModelIndex, QSize,
|
||||||
QTimer, pyqtSignal)
|
Qt, QTimer, pyqtSignal)
|
||||||
from PyQt6.QtGui import (QColor, QPainter, QPalette, QStandardItem,
|
from PyQt6.QtGui import (QColor, QPainter, QPalette, QStandardItem,
|
||||||
QStandardItemModel)
|
QStandardItemModel)
|
||||||
from PyQt6.QtWidgets import (QAbstractItemView, QAbstractSpinBox, QCheckBox,
|
from PyQt6.QtWidgets import (QAbstractItemView, QAbstractSpinBox, QCheckBox,
|
||||||
|
|||||||
@@ -516,6 +516,9 @@ class BalBuildWillDialog(BalDialog):
|
|||||||
self.build_row = None
|
self.build_row = None
|
||||||
self.sign_row = None
|
self.sign_row = None
|
||||||
self.push_row = None
|
self.push_row = None
|
||||||
|
# Manual next-steps hint (Sign / Broadcast) shown to the user after the
|
||||||
|
# dialog finishes; None when nothing is left to do.
|
||||||
|
self._next_steps_hint = None
|
||||||
self.network = Network.get_instance()
|
self.network = Network.get_instance()
|
||||||
self._stopping = False
|
self._stopping = False
|
||||||
self.thread = TaskThread(self)
|
self.thread = TaskThread(self)
|
||||||
@@ -541,7 +544,7 @@ class BalBuildWillDialog(BalDialog):
|
|||||||
return
|
return
|
||||||
txs = None
|
txs = None
|
||||||
_logger.debug("close plugin phase 1 started")
|
_logger.debug("close plugin phase 1 started")
|
||||||
varrow = self.msg_set_status("checking variables")
|
varrow = self.msg_set_status("Checking variables")
|
||||||
try:
|
try:
|
||||||
self.bal_window.init_class_variables()
|
self.bal_window.init_class_variables()
|
||||||
except CheckAliveError as cae:
|
except CheckAliveError as cae:
|
||||||
@@ -553,12 +556,12 @@ class BalBuildWillDialog(BalDialog):
|
|||||||
_logger.debug(
|
_logger.debug(
|
||||||
"during phase1 CAE: {}, Continue to invalidate".format(cae)
|
"during phase1 CAE: {}, Continue to invalidate".format(cae)
|
||||||
)
|
)
|
||||||
self.msg_set_status("checking variables",varrow, "Check Alive Threshold Passed: you have to Invalidate your old Will",self.COLOR_ERROR)
|
self.msg_set_status("Checking variables",varrow, "Check Alive Threshold Passed: you have to Invalidate your old Will",self.COLOR_ERROR)
|
||||||
else:
|
else:
|
||||||
raise cae
|
raise cae
|
||||||
return None, tx
|
return None, tx
|
||||||
except NoHeirsException:
|
except NoHeirsException:
|
||||||
self.msg_set_status("checking variables", varrow,"No Heirs",self.COLOR_ERROR)
|
self.msg_set_status("Checking variables", varrow,"No Heirs",self.COLOR_ERROR)
|
||||||
#self.msg_set_checking("No Heirs")
|
#self.msg_set_checking("No Heirs")
|
||||||
return False, None
|
return False, None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -573,7 +576,7 @@ class BalBuildWillDialog(BalDialog):
|
|||||||
self.bal_window.window.wallet.dust_threshold(),
|
self.bal_window.window.wallet.dust_threshold(),
|
||||||
)
|
)
|
||||||
_logger.debug("variables ok")
|
_logger.debug("variables ok")
|
||||||
self.msg_set_status("checking variables:", varrow, "Ok", self.COLOR_OK)
|
self.msg_set_status("Checking variables", varrow, "Ok", self.COLOR_OK)
|
||||||
except AmountException:
|
except AmountException:
|
||||||
self.msg_set_checking(
|
self.msg_set_checking(
|
||||||
self.msg_warning(
|
self.msg_warning(
|
||||||
@@ -937,7 +940,36 @@ class BalBuildWillDialog(BalDialog):
|
|||||||
self.thread.stop()
|
self.thread.stop()
|
||||||
self.bal_window.save_willitems()
|
self.bal_window.save_willitems()
|
||||||
self.msg_edit_row(_("Finished"))
|
self.msg_edit_row(_("Finished"))
|
||||||
|
# Instead of auto-closing after a countdown, let the user decide when to
|
||||||
|
# dismiss the dialog: they can read the full "Building Will" report at
|
||||||
|
# their own pace and then press "Close". This runs in the GUI thread
|
||||||
|
# (on_success callback) so building the button here is safe.
|
||||||
|
self._add_close_button()
|
||||||
|
|
||||||
|
def _add_close_button(self):
|
||||||
|
"""Add a right-aligned "Close" button to dismiss the dialog manually.
|
||||||
|
|
||||||
|
Replaces the old automatic countdown (self.wait(5) + self.close()).
|
||||||
|
Guarded so it is only built once even if called again.
|
||||||
|
"""
|
||||||
|
if getattr(self, "_close_button", None) is not None:
|
||||||
|
return
|
||||||
|
self._close_button = QPushButton(_("Close"))
|
||||||
|
self._close_button.clicked.connect(self._on_close_clicked)
|
||||||
|
button_row = QHBoxLayout()
|
||||||
|
button_row.addStretch(1)
|
||||||
|
button_row.addWidget(self._close_button)
|
||||||
|
self.vbox.addLayout(button_row)
|
||||||
|
self._close_button.setFocus()
|
||||||
|
|
||||||
|
def _on_close_clicked(self):
|
||||||
|
# Close the dialog first, then show the persistent popup guiding the
|
||||||
|
# user through any remaining MANUAL steps (Sign / Broadcast). Showing
|
||||||
|
# the (modal) hint after close() mirrors the previous behaviour where
|
||||||
|
# the hint appeared once the auto-closing dialog was gone.
|
||||||
self.close()
|
self.close()
|
||||||
|
if self._next_steps_hint:
|
||||||
|
self.bal_window.show_message(self._next_steps_hint)
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
self._stopping = True
|
self._stopping = True
|
||||||
@@ -975,8 +1007,71 @@ class BalBuildWillDialog(BalDialog):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
# td = traceback.format_exc()
|
# td = traceback.format_exc()
|
||||||
self.msg_set_pushing(self.msg_error(e))
|
self.msg_set_pushing(self.msg_error(e))
|
||||||
self.msg_edit_row(self.msg_ok())
|
# Blank separator row: visually detach the final "All done" summary
|
||||||
self.wait(5)
|
# from the per-step result rows above it, so the closing line stands
|
||||||
|
# out as the overall outcome rather than just another step.
|
||||||
|
self.msg_edit_row("")
|
||||||
|
# Final summary row: the whole "Building Will" sequence above (check /
|
||||||
|
# sign / broadcast) finished without errors. Give it an explicit
|
||||||
|
# left-side label ("All done") so this closing Ok is not an orphan
|
||||||
|
# result like the other rows have.
|
||||||
|
self.msg_edit_row("{}:\t{}".format(_("All done"), self.msg_ok()))
|
||||||
|
|
||||||
|
# Guide the user through any remaining MANUAL steps. After the will is
|
||||||
|
# (re)built -- e.g. because an heir was removed/added from the Wizard --
|
||||||
|
# the new transactions may still need to be SIGNED and/or BROADCAST by
|
||||||
|
# the user. This dialog only signs/pushes automatically when it already
|
||||||
|
# has the password and the will is in the right state; in every other
|
||||||
|
# case the user is otherwise left without any indication of what to do
|
||||||
|
# next. We inspect the real status of the valid wills and tell the user
|
||||||
|
# exactly which buttons to press.
|
||||||
|
self._show_next_steps_hint()
|
||||||
|
|
||||||
|
def _show_next_steps_hint(self):
|
||||||
|
"""Append a clear "what to do next" line to the Building Will dialog.
|
||||||
|
|
||||||
|
Pure UX guidance (no logic change): looks at the valid wills and, if any
|
||||||
|
still needs signing or broadcasting, tells the user to press 'Sign'
|
||||||
|
and/or 'Broadcast' manually. The computed hint is also stored in
|
||||||
|
``self._next_steps_hint`` so a persistent popup can be shown after the
|
||||||
|
dialog closes (this dialog auto-closes after a few seconds, which is too
|
||||||
|
short to be sure the user noticed the in-dialog line).
|
||||||
|
"""
|
||||||
|
self._next_steps_hint = None
|
||||||
|
try:
|
||||||
|
need_sign = False
|
||||||
|
need_push = False
|
||||||
|
for wid in Will.only_valid(self.bal_window.willitems):
|
||||||
|
w = self.bal_window.willitems[wid]
|
||||||
|
if not w.get_status("COMPLETE"):
|
||||||
|
# Not signed yet.
|
||||||
|
need_sign = True
|
||||||
|
elif w.we and not w.get_status("PUSHED"):
|
||||||
|
# Signed but not yet sent to its will-executor.
|
||||||
|
need_push = True
|
||||||
|
|
||||||
|
if need_sign and need_push:
|
||||||
|
hint = _(
|
||||||
|
"Next steps (manual): press 'Sign' to sign your will, "
|
||||||
|
"then 'Broadcast' to send it to the will-executors."
|
||||||
|
)
|
||||||
|
elif need_sign:
|
||||||
|
hint = _(
|
||||||
|
"Next step (manual): press 'Sign' to sign your will."
|
||||||
|
)
|
||||||
|
elif need_push:
|
||||||
|
hint = _(
|
||||||
|
"Next step (manual): press 'Broadcast' to send your will "
|
||||||
|
"to the will-executors."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Nothing left to do (already signed and, if needed, sent).
|
||||||
|
return
|
||||||
|
|
||||||
|
self._next_steps_hint = hint
|
||||||
|
self.msg_edit_row("<b>{}</b>".format(hint))
|
||||||
|
except Exception as hint_err:
|
||||||
|
_logger.debug(f"next-steps hint error: {hint_err}")
|
||||||
|
|
||||||
def on_error(self, error):
|
def on_error(self, error):
|
||||||
_logger.error(error)
|
_logger.error(error)
|
||||||
|
|||||||
@@ -457,13 +457,19 @@ class PreviewList(MyTreeView, MessageBoxMixin):
|
|||||||
menu.addAction(_("Check"), self.check)
|
menu.addAction(_("Check"), self.check)
|
||||||
menu.addAction(_("Invalidate"), self.invalidate_will)
|
menu.addAction(_("Invalidate"), self.invalidate_will)
|
||||||
|
|
||||||
wizard = QPushButton()
|
# The Wizard is the main entry point to create an inheritance, so make
|
||||||
|
# it stand out: show a bold label next to a slightly larger icon (the
|
||||||
|
# plain icon-only button was too easy to overlook).
|
||||||
|
wizard = QPushButton(" " + _("Create your will"))
|
||||||
wizard.setIcon(
|
wizard.setIcon(
|
||||||
read_QIcon_from_bytes(
|
read_QIcon_from_bytes(
|
||||||
self.bal_window.bal_plugin.read_file("icons/wizard.png")
|
self.bal_window.bal_plugin.read_file("icons/wizard.png")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Tooltip so the icon is self-explanatory when hovered.
|
wizard.setIconSize(QSize(28, 28))
|
||||||
|
wizard.setMinimumHeight(40)
|
||||||
|
wizard.setStyleSheet("QPushButton{font-weight:bold;}")
|
||||||
|
# Tooltip so the button is self-explanatory when hovered.
|
||||||
wizard.setToolTip(_("Wizard - Build your will"))
|
wizard.setToolTip(_("Wizard - Build your will"))
|
||||||
wizard.clicked.connect(self.bal_window.init_wizard)
|
wizard.clicked.connect(self.bal_window.init_wizard)
|
||||||
# display = QPushButton(_("Display"))
|
# display = QPushButton(_("Display"))
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ class BalTxFeesWidget(QWidget):
|
|||||||
button.setStyleSheet("font-size: 16px;")
|
button.setStyleSheet("font-size: 16px;")
|
||||||
layout.addWidget(button)
|
layout.addWidget(button)
|
||||||
layout.addWidget(self.txfee_widget)
|
layout.addWidget(self.txfee_widget)
|
||||||
|
# Expose the leading icon (prefix) and the editable field so the parent
|
||||||
|
# WillSettingsWidget can align them on a grid (see its vertical layout).
|
||||||
|
self.prefix_widget = button
|
||||||
|
self.field_widget = self.txfee_widget
|
||||||
|
|
||||||
def doubleclick(self, event=None):
|
def doubleclick(self, event=None):
|
||||||
pass
|
pass
|
||||||
@@ -206,6 +210,9 @@ class BalTimeEditWidget(QWidget, _LockTimeEditor):
|
|||||||
help_button.setToolTip(_(self.tooltip_text))
|
help_button.setToolTip(_(self.tooltip_text))
|
||||||
#help_button.setStyleSheet("font-size: 155555);
|
#help_button.setStyleSheet("font-size: 155555);
|
||||||
hbox.addWidget(help_button)
|
hbox.addWidget(help_button)
|
||||||
|
# Expose the leading icon (prefix) so the parent WillSettingsWidget can
|
||||||
|
# align all rows on a common left edge (see its vertical layout).
|
||||||
|
self.prefix_widget = help_button
|
||||||
self.combo.currentIndexChanged.connect(self.on_current_index_changed)
|
self.combo.currentIndexChanged.connect(self.on_current_index_changed)
|
||||||
|
|
||||||
for w in self.editors:
|
for w in self.editors:
|
||||||
@@ -558,10 +565,58 @@ class WillSettingsWidget(QWidget):
|
|||||||
w = self.widgets["baltx_fees"]
|
w = self.widgets["baltx_fees"]
|
||||||
if w not in bal_window.txfee_widgets:
|
if w not in bal_window.txfee_widgets:
|
||||||
bal_window.txfee_widgets.append(w)
|
bal_window.txfee_widgets.append(w)
|
||||||
box.addWidget(self.widgets["locktime"])
|
if layout_type == "h":
|
||||||
box.addWidget(self.widgets["threshold"])
|
box.addWidget(self.widgets["locktime"])
|
||||||
box.addWidget(self.calendar_button)
|
box.addWidget(self.widgets["threshold"])
|
||||||
box.addWidget(self.widgets["baltx_fees"])
|
box.addWidget(self.calendar_button)
|
||||||
|
box.addWidget(self.widgets["baltx_fees"])
|
||||||
|
else:
|
||||||
|
# Vertical layout (the "Build your will" wizard): make every row the
|
||||||
|
# same width and left aligned so they all fit in one tidy block,
|
||||||
|
# instead of letting the calendar button and the fee field stretch to
|
||||||
|
# the dialog's right edge (which made them far wider than the date
|
||||||
|
# rows above).
|
||||||
|
#
|
||||||
|
# IMPORTANT: the leading icons keep their ORIGINAL size. The icons
|
||||||
|
# are HelpButtons, which already pin themselves to a fixed width
|
||||||
|
# (2.2 * char_width_in_lineedit()); we must NOT widen them, otherwise
|
||||||
|
# they look oversized compared with the original toolbar layout. We
|
||||||
|
# only need to (1) align the calendar row's left edge with the icons'
|
||||||
|
# original width and (2) cap every row to the date-row width.
|
||||||
|
locktime_w = self.widgets["locktime"]
|
||||||
|
threshold_w = self.widgets["threshold"]
|
||||||
|
fees_w = self.widgets["baltx_fees"]
|
||||||
|
|
||||||
|
# Original icon width (HelpButton's own fixed width); used only to
|
||||||
|
# offset the calendar button so its field starts under the others.
|
||||||
|
icon_w = locktime_w.prefix_widget.sizeHint().width()
|
||||||
|
|
||||||
|
# Common row width = natural width of the date rows (the reference).
|
||||||
|
row_w = max(
|
||||||
|
locktime_w.sizeHint().width(),
|
||||||
|
threshold_w.sizeHint().width(),
|
||||||
|
)
|
||||||
|
for w in (locktime_w, threshold_w, fees_w):
|
||||||
|
w.setFixedWidth(row_w)
|
||||||
|
|
||||||
|
# The calendar row has no prefix icon: wrap it so it starts with an
|
||||||
|
# empty spacer of the icon width (calendar field aligned with the
|
||||||
|
# date/fee fields) and cap it to the same total width as the rows
|
||||||
|
# above, so it no longer stretches to the dialog's right edge.
|
||||||
|
calendar_row = QWidget(self)
|
||||||
|
calendar_box = QHBoxLayout(calendar_row)
|
||||||
|
calendar_box.setContentsMargins(0, 0, 0, 0)
|
||||||
|
calendar_box.setSpacing(0)
|
||||||
|
calendar_spacer = QWidget()
|
||||||
|
calendar_spacer.setFixedWidth(icon_w)
|
||||||
|
calendar_box.addWidget(calendar_spacer)
|
||||||
|
calendar_box.addWidget(self.calendar_button)
|
||||||
|
calendar_row.setFixedWidth(row_w)
|
||||||
|
|
||||||
|
box.addWidget(locktime_w, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||||
|
box.addWidget(threshold_w, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||||
|
box.addWidget(calendar_row, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||||
|
box.addWidget(fees_w, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||||
|
|
||||||
if self.read_only:
|
if self.read_only:
|
||||||
self.widgets["locktime"].set_read_only(True)
|
self.widgets["locktime"].set_read_only(True)
|
||||||
|
|||||||
@@ -574,10 +574,15 @@ class BalWindow:
|
|||||||
_logger.info("build will")
|
_logger.info("build will")
|
||||||
self.build_will(ignore_duplicate, keep_original)
|
self.build_will(ignore_duplicate, keep_original)
|
||||||
|
|
||||||
|
# Track whether the rebuild produced a coherent, ready-to-sign
|
||||||
|
# will, so we can guide the user through the remaining manual
|
||||||
|
# steps (Sign + Broadcast) afterwards.
|
||||||
|
rebuilt_ok = False
|
||||||
try:
|
try:
|
||||||
self.check_will()
|
self.check_will()
|
||||||
for wid, _w in self.willitems.items():
|
for wid, _w in self.willitems.items():
|
||||||
self.wallet.set_label(wid, "BAL Transaction")
|
self.wallet.set_label(wid, "BAL Transaction")
|
||||||
|
rebuilt_ok = True
|
||||||
except WillExpiredException as e:
|
except WillExpiredException as e:
|
||||||
self.invalidate_will()
|
self.invalidate_will()
|
||||||
except NotCompleteWillException as e:
|
except NotCompleteWillException as e:
|
||||||
@@ -590,6 +595,31 @@ class BalWindow:
|
|||||||
|
|
||||||
self.window.history_list.update()
|
self.window.history_list.update()
|
||||||
self.window.utxo_list.update()
|
self.window.utxo_list.update()
|
||||||
|
|
||||||
|
# Guide the user: the inheritance was just (re)built and is now
|
||||||
|
# in the "New" state, so it must be SIGNED and then BROADCAST
|
||||||
|
# again -- two manual steps the user has to perform. Without
|
||||||
|
# this hint the user is left with a freshly rebuilt will and no
|
||||||
|
# indication that it still needs to be signed and re-sent to the
|
||||||
|
# will-executors.
|
||||||
|
if rebuilt_ok:
|
||||||
|
if self.no_willexecutor:
|
||||||
|
next_steps = _(
|
||||||
|
"Your inheritance has been rebuilt and now needs "
|
||||||
|
"to be signed again.\n\n"
|
||||||
|
"Next step (manual):\n"
|
||||||
|
" 1. Press 'Sign' to sign the new transaction."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
next_steps = _(
|
||||||
|
"Your inheritance has been rebuilt and now needs "
|
||||||
|
"to be signed and re-sent to the will-executors.\n\n"
|
||||||
|
"Next steps (manual):\n"
|
||||||
|
" 1. Press 'Sign' to sign the new transaction.\n"
|
||||||
|
" 2. Press 'Broadcast' to send it to the "
|
||||||
|
"will-executors."
|
||||||
|
)
|
||||||
|
self.show_message(next_steps)
|
||||||
self.update_all()
|
self.update_all()
|
||||||
return self.willitems
|
return self.willitems
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "bal",
|
"name": "bal",
|
||||||
"fullname": "Bitcoin After Life",
|
"fullname": "Bitcoin After Life",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"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.",
|
"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",
|
"author": "Svatantrya",
|
||||||
"licence": "MIT",
|
"licence": "MIT",
|
||||||
|
|||||||
105
tests/preview_building_will_close_btn.py
Normal file
105
tests/preview_building_will_close_btn.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Visual PREVIEW: replace the final countdown with a "Close" button.
|
||||||
|
|
||||||
|
Mocks the "Building Will" dialog content (the result rows are built exactly as
|
||||||
|
the real dialog builds them) and shows the proposed bottom "Close" button that
|
||||||
|
replaces the "Please wait 5secs" auto-closing countdown.
|
||||||
|
|
||||||
|
BEFORE: last line is the countdown "Please wait 5secs" (dialog auto-closes).
|
||||||
|
AFTER : a real "Close" button at the bottom; the user closes when ready.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
QT_QPA_PLATFORM=offscreen python3 tests/preview_building_will_close_btn.py
|
||||||
|
Writes preview_buildwill_before.png / preview_buildwill_after.png.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||||
|
|
||||||
|
from PyQt6.QtWidgets import ( # noqa: E402
|
||||||
|
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
||||||
|
)
|
||||||
|
from PyQt6.QtCore import Qt # noqa: E402
|
||||||
|
|
||||||
|
COLOR_OK = "#05ad05"
|
||||||
|
|
||||||
|
|
||||||
|
def _ok(text="Ok"):
|
||||||
|
return "<font color='{}'><b>{}</b></font>".format(COLOR_OK, text)
|
||||||
|
|
||||||
|
|
||||||
|
def _rows(after: bool):
|
||||||
|
"""The exact result rows the real dialog shows on a clean run.
|
||||||
|
|
||||||
|
BEFORE keeps the old lowercase "checking variables" and the "All done"
|
||||||
|
row directly under the others. AFTER applies the two text fixes:
|
||||||
|
capitalised "Checking variables" + a blank separator row above "All done".
|
||||||
|
"""
|
||||||
|
if not after:
|
||||||
|
return [
|
||||||
|
"checking variables:\t" + _ok("Ok"),
|
||||||
|
"Checking your will:\t" + _ok("Ok"),
|
||||||
|
"Signing your will:\t<b>Nothing to do</b>",
|
||||||
|
"Broadcasting your will to executors:\t<b>Nothing to do</b>",
|
||||||
|
"All done:\t" + _ok("Ok"),
|
||||||
|
]
|
||||||
|
return [
|
||||||
|
"Checking variables:\t" + _ok("Ok"),
|
||||||
|
"Checking your will:\t" + _ok("Ok"),
|
||||||
|
"Signing your will:\t<b>Nothing to do</b>",
|
||||||
|
"Broadcasting your will to executors:\t<b>Nothing to do</b>",
|
||||||
|
"", # blank separator row
|
||||||
|
"All done:\t" + _ok("Ok"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _build(after: bool) -> QWidget:
|
||||||
|
panel = QWidget()
|
||||||
|
panel.setMinimumWidth(600)
|
||||||
|
v = QVBoxLayout(panel)
|
||||||
|
v.addWidget(QLabel("<b>Building Will:</b>"))
|
||||||
|
|
||||||
|
rows = QWidget()
|
||||||
|
rv = QVBoxLayout(rows)
|
||||||
|
rv.setContentsMargins(8, 4, 8, 4)
|
||||||
|
for line in _rows(after):
|
||||||
|
lbl = QLabel(line)
|
||||||
|
lbl.setTextFormat(Qt.TextFormat.RichText)
|
||||||
|
rv.addWidget(lbl)
|
||||||
|
v.addWidget(rows)
|
||||||
|
|
||||||
|
if not after:
|
||||||
|
# BEFORE: the countdown row (auto-close).
|
||||||
|
wait = QLabel("Please wait 5secs")
|
||||||
|
v.addWidget(wait)
|
||||||
|
else:
|
||||||
|
# AFTER: a Close button row, right aligned (standard dialog layout).
|
||||||
|
v.addSpacing(8)
|
||||||
|
btn_row = QWidget()
|
||||||
|
h = QHBoxLayout(btn_row)
|
||||||
|
h.setContentsMargins(0, 0, 0, 0)
|
||||||
|
h.addStretch(1)
|
||||||
|
close = QPushButton("Close")
|
||||||
|
close.setDefault(True)
|
||||||
|
close.setMinimumWidth(90)
|
||||||
|
h.addWidget(close)
|
||||||
|
v.addWidget(btn_row)
|
||||||
|
|
||||||
|
panel.resize(620, 230)
|
||||||
|
return panel
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = QApplication.instance() or QApplication([])
|
||||||
|
for after, name in ((False, "preview_buildwill_before.png"),
|
||||||
|
(True, "preview_buildwill_after.png")):
|
||||||
|
panel = _build(after)
|
||||||
|
panel.show()
|
||||||
|
app.processEvents()
|
||||||
|
panel.grab().save(name)
|
||||||
|
print("wrote", name)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
139
tests/preview_wizard_button.py
Normal file
139
tests/preview_wizard_button.py
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Visual PREVIEW: proposals to make the Wizard button more visible.
|
||||||
|
|
||||||
|
Renders the toolbar Wizard button (using the real icons/wizard.png) in several
|
||||||
|
styles so the user can pick one. Nothing is changed in the plugin yet.
|
||||||
|
|
||||||
|
Variants:
|
||||||
|
0. CURRENT : icon only, default size (what ships today).
|
||||||
|
A. BIGGER : same icon, larger button + larger iconSize.
|
||||||
|
B. ICON+TEXT: bigger icon plus a "Create your will" label.
|
||||||
|
C. ACCENT : icon + text on a colored (Bitcoin-orange) rounded button.
|
||||||
|
D. ACCENT-BLUE: icon + text on a BAL-blue (#2bc8ed) rounded button.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
QT_QPA_PLATFORM=offscreen python3 tests/preview_wizard_button.py
|
||||||
|
Writes preview_wizardbtn_<id>.png in the repo root.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||||
|
|
||||||
|
from PyQt6.QtWidgets import ( # noqa: E402
|
||||||
|
QApplication, QWidget, QHBoxLayout, QPushButton, QComboBox, QLineEdit,
|
||||||
|
QLabel,
|
||||||
|
)
|
||||||
|
from PyQt6.QtGui import QIcon, QPixmap # noqa: E402
|
||||||
|
from PyQt6.QtCore import QSize, Qt # noqa: E402
|
||||||
|
|
||||||
|
ICON_PATH = os.path.join(os.path.dirname(__file__), "..", "bal", "icons",
|
||||||
|
"wizard.png")
|
||||||
|
|
||||||
|
|
||||||
|
def _icon():
|
||||||
|
pm = QPixmap(ICON_PATH)
|
||||||
|
return QIcon(pm)
|
||||||
|
|
||||||
|
|
||||||
|
def _toolbar_tail(parent):
|
||||||
|
"""The widgets that sit to the right of the wizard button, for context."""
|
||||||
|
out = []
|
||||||
|
icon = QPushButton(parent)
|
||||||
|
icon.setText("📅")
|
||||||
|
combo = QComboBox(parent)
|
||||||
|
combo.addItems(["Raw", "Date"])
|
||||||
|
combo.setCurrentIndex(1)
|
||||||
|
field = QLineEdit("04/06/2028 00:00", parent)
|
||||||
|
field.setFixedWidth(140)
|
||||||
|
out += [icon, combo, field]
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _frame(make_wizard, label):
|
||||||
|
panel = QWidget()
|
||||||
|
v = QHBoxLayout(panel)
|
||||||
|
v.setContentsMargins(10, 10, 10, 10)
|
||||||
|
tag = QLabel(label, panel)
|
||||||
|
tag.setFixedWidth(120)
|
||||||
|
v.addWidget(tag)
|
||||||
|
wiz = make_wizard(panel)
|
||||||
|
v.addWidget(wiz)
|
||||||
|
for w in _toolbar_tail(panel):
|
||||||
|
v.addWidget(w)
|
||||||
|
v.addStretch(1)
|
||||||
|
panel.resize(720, 70)
|
||||||
|
return panel
|
||||||
|
|
||||||
|
|
||||||
|
# ---- variants -------------------------------------------------------------
|
||||||
|
def v_current(parent):
|
||||||
|
b = QPushButton(parent)
|
||||||
|
b.setIcon(_icon())
|
||||||
|
b.setToolTip("Wizard - Build your will")
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
|
def v_bigger(parent):
|
||||||
|
b = QPushButton(parent)
|
||||||
|
b.setIcon(_icon())
|
||||||
|
b.setIconSize(QSize(36, 36))
|
||||||
|
b.setFixedSize(48, 44)
|
||||||
|
b.setToolTip("Wizard - Build your will")
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
|
def v_icon_text(parent):
|
||||||
|
b = QPushButton(" Create your will", parent)
|
||||||
|
b.setIcon(_icon())
|
||||||
|
b.setIconSize(QSize(28, 28))
|
||||||
|
b.setMinimumHeight(40)
|
||||||
|
b.setStyleSheet("QPushButton{font-weight:bold;}")
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
|
def v_accent_orange(parent):
|
||||||
|
b = QPushButton(" Create your will", parent)
|
||||||
|
b.setIcon(_icon())
|
||||||
|
b.setIconSize(QSize(28, 28))
|
||||||
|
b.setMinimumHeight(40)
|
||||||
|
b.setStyleSheet(
|
||||||
|
"QPushButton{background-color:#f7931a;color:white;font-weight:bold;"
|
||||||
|
"border:none;border-radius:8px;padding:6px 14px;}"
|
||||||
|
"QPushButton:hover{background-color:#ffa733;}"
|
||||||
|
)
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
|
def v_accent_blue(parent):
|
||||||
|
b = QPushButton(" Create your will", parent)
|
||||||
|
b.setIcon(_icon())
|
||||||
|
b.setIconSize(QSize(28, 28))
|
||||||
|
b.setMinimumHeight(40)
|
||||||
|
b.setStyleSheet(
|
||||||
|
"QPushButton{background-color:#2bc8ed;color:white;font-weight:bold;"
|
||||||
|
"border:none;border-radius:8px;padding:6px 14px;}"
|
||||||
|
"QPushButton:hover{background-color:#4fd6f3;}"
|
||||||
|
)
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = QApplication.instance() or QApplication([])
|
||||||
|
variants = [
|
||||||
|
(v_current, "0-CURRENT", "preview_wizardbtn_0_current.png"),
|
||||||
|
(v_bigger, "A-BIGGER", "preview_wizardbtn_A_bigger.png"),
|
||||||
|
(v_icon_text, "B-ICON+TEXT", "preview_wizardbtn_B_icontext.png"),
|
||||||
|
(v_accent_orange, "C-ORANGE", "preview_wizardbtn_C_orange.png"),
|
||||||
|
(v_accent_blue, "D-BLUE", "preview_wizardbtn_D_blue.png"),
|
||||||
|
]
|
||||||
|
for make, label, name in variants:
|
||||||
|
panel = _frame(make, label)
|
||||||
|
panel.show()
|
||||||
|
app.processEvents()
|
||||||
|
panel.grab().save(name)
|
||||||
|
print("wrote", name)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
151
tests/preview_wizard_settings_align.py
Normal file
151
tests/preview_wizard_settings_align.py
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Visual PREVIEW for the wizard "Bitcoin After Life Will Settings" rows.
|
||||||
|
|
||||||
|
Reproduces the structure of WillSettingsWidget in its VERTICAL layout (the one
|
||||||
|
used by the "Build your will" wizard):
|
||||||
|
|
||||||
|
row 1: [icon][combo "Date"][date field] (delivery time / locktime)
|
||||||
|
row 2: [icon][combo "Date"][date field] (check alive / threshold)
|
||||||
|
row 3: [calendar button] (calendar export)
|
||||||
|
row 4: [icon "丰"][spin "5"] (tx fees)
|
||||||
|
|
||||||
|
The icons are HelpButtons, which pin themselves to a fixed width
|
||||||
|
(2.2 * char_width_in_lineedit()). This preview reproduces that original icon
|
||||||
|
size and shows:
|
||||||
|
|
||||||
|
* BEFORE: calendar/fee stretch to the right edge -> rows wider than dates.
|
||||||
|
* AFTER : icons keep their ORIGINAL fixed width; every row is capped to the
|
||||||
|
date-row width and left aligned, so all rows fit in the same block
|
||||||
|
and line up on both edges.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
QT_QPA_PLATFORM=offscreen python3 tests/preview_wizard_settings_align.py
|
||||||
|
Writes preview_wizard_before.png / preview_wizard_after.png in the repo root.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||||
|
|
||||||
|
from PyQt6.QtWidgets import ( # noqa: E402
|
||||||
|
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QToolButton, QComboBox,
|
||||||
|
QLineEdit, QSpinBox, QLabel,
|
||||||
|
)
|
||||||
|
from PyQt6.QtGui import QFontMetrics # noqa: E402
|
||||||
|
from PyQt6.QtCore import Qt # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def _char_w():
|
||||||
|
fm = QFontMetrics(QApplication.instance().font())
|
||||||
|
return fm.horizontalAdvance("0")
|
||||||
|
|
||||||
|
|
||||||
|
def _icon(text):
|
||||||
|
"""Mimic HelpButton: a QToolButton pinned to 2.2 * char width."""
|
||||||
|
b = QToolButton()
|
||||||
|
b.setText(text)
|
||||||
|
b.setFixedWidth(round(2.2 * _char_w()))
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
|
def _date_row():
|
||||||
|
w = QWidget()
|
||||||
|
h = QHBoxLayout(w)
|
||||||
|
h.setContentsMargins(0, 0, 0, 0)
|
||||||
|
h.setSpacing(0)
|
||||||
|
icon = _icon("📅")
|
||||||
|
combo = QComboBox()
|
||||||
|
combo.addItems(["Raw", "Date"])
|
||||||
|
combo.setCurrentIndex(1)
|
||||||
|
field = QLineEdit("04/06/2028 00:00")
|
||||||
|
h.addWidget(icon)
|
||||||
|
h.addWidget(combo)
|
||||||
|
h.addWidget(field)
|
||||||
|
h.addStretch(1)
|
||||||
|
w._prefix = icon
|
||||||
|
return w
|
||||||
|
|
||||||
|
|
||||||
|
def _fee_row():
|
||||||
|
w = QWidget()
|
||||||
|
h = QHBoxLayout(w)
|
||||||
|
h.setContentsMargins(0, 0, 0, 0)
|
||||||
|
h.setSpacing(0)
|
||||||
|
icon = _icon("丰")
|
||||||
|
spin = QSpinBox()
|
||||||
|
spin.setValue(5)
|
||||||
|
spin.setMaximum(10000)
|
||||||
|
h.addWidget(icon)
|
||||||
|
h.addWidget(spin)
|
||||||
|
w._prefix = icon
|
||||||
|
return w
|
||||||
|
|
||||||
|
|
||||||
|
def _calendar_button():
|
||||||
|
return QToolButton()
|
||||||
|
|
||||||
|
|
||||||
|
def _panel():
|
||||||
|
panel = QWidget()
|
||||||
|
box = QVBoxLayout(panel)
|
||||||
|
box.addWidget(QLabel("Bitcoin After Life Will Settings"))
|
||||||
|
return panel, box
|
||||||
|
|
||||||
|
|
||||||
|
def build_before():
|
||||||
|
panel, box = _panel()
|
||||||
|
box.addWidget(_date_row())
|
||||||
|
box.addWidget(_date_row())
|
||||||
|
box.addWidget(_calendar_button())
|
||||||
|
box.addWidget(_fee_row())
|
||||||
|
panel.resize(760, 220)
|
||||||
|
return panel
|
||||||
|
|
||||||
|
|
||||||
|
def build_after():
|
||||||
|
panel, box = _panel()
|
||||||
|
r1 = _date_row()
|
||||||
|
r2 = _date_row()
|
||||||
|
cal = _calendar_button()
|
||||||
|
r4 = _fee_row()
|
||||||
|
|
||||||
|
# icons keep their original fixed width (no resizing)
|
||||||
|
icon_w = r1._prefix.sizeHint().width()
|
||||||
|
|
||||||
|
row_w = max(r1.sizeHint().width(), r2.sizeHint().width())
|
||||||
|
for r in (r1, r2, r4):
|
||||||
|
r.setFixedWidth(row_w)
|
||||||
|
|
||||||
|
cal_row = QWidget()
|
||||||
|
cb = QHBoxLayout(cal_row)
|
||||||
|
cb.setContentsMargins(0, 0, 0, 0)
|
||||||
|
cb.setSpacing(0)
|
||||||
|
sp = QWidget()
|
||||||
|
sp.setFixedWidth(icon_w)
|
||||||
|
cb.addWidget(sp)
|
||||||
|
cb.addWidget(cal)
|
||||||
|
cal_row.setFixedWidth(row_w)
|
||||||
|
|
||||||
|
box.addWidget(r1, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||||
|
box.addWidget(r2, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||||
|
box.addWidget(cal_row, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||||
|
box.addWidget(r4, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||||
|
panel.resize(760, 220)
|
||||||
|
return panel
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = QApplication.instance() or QApplication([])
|
||||||
|
for builder, name in (
|
||||||
|
(build_before, "preview_wizard_before.png"),
|
||||||
|
(build_after, "preview_wizard_after.png"),
|
||||||
|
):
|
||||||
|
panel = builder()
|
||||||
|
panel.show()
|
||||||
|
app.processEvents()
|
||||||
|
panel.grab().save(name)
|
||||||
|
print("wrote", name)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -227,17 +227,22 @@ def test_check_heir_added_triggers_rebuild():
|
|||||||
|
|
||||||
|
|
||||||
def test_needs_server_check():
|
def test_needs_server_check():
|
||||||
"""Check button selection logic: a VALID will with a will-executor that is
|
"""Check button selection logic: only a VALID, PUSHED will with a
|
||||||
not yet CHECKED must be queried on the server, even if it is not PUSHED
|
will-executor that is not yet CHECKED must be queried on the server.
|
||||||
(regression for the 'New / Not sent' wills that Check ignored)."""
|
|
||||||
|
A will that was never sent (not PUSHED) must NOT be queried: the server
|
||||||
|
would correctly answer "I don't have it", which would be recorded as
|
||||||
|
CHECK_FAIL and turn a merely signed-but-not-sent will red instead of leaving
|
||||||
|
it blue (#2bc8ed). This matches the original BAL ``check()`` behaviour."""
|
||||||
we = {"url": "https://we.example.com"}
|
we = {"url": "https://we.example.com"}
|
||||||
|
|
||||||
# New (not PUSHED) but has a will-executor -> must be checked.
|
# New (not PUSHED) but has a will-executor -> must NOT be checked, otherwise
|
||||||
|
# a signed-but-not-sent will would falsely turn CHECK_FAIL (red).
|
||||||
item_new = _make_willitem_blank()
|
item_new = _make_willitem_blank()
|
||||||
item_new.we = we
|
item_new.we = we
|
||||||
assert Will.needs_server_check(item_new) is True
|
assert Will.needs_server_check(item_new) is False
|
||||||
|
|
||||||
# PUSHED but not CHECKED -> must be checked (previous behaviour).
|
# PUSHED but not CHECKED -> must be checked.
|
||||||
item_pushed = _make_willitem_blank()
|
item_pushed = _make_willitem_blank()
|
||||||
item_pushed.we = we
|
item_pushed.we = we
|
||||||
item_pushed.set_status("PUSHED", True)
|
item_pushed.set_status("PUSHED", True)
|
||||||
|
|||||||
Reference in New Issue
Block a user