The Building Will report showed a fixed list of three "possible reasons"
whenever a build produced nothing, regardless of what actually happened; in a
case reproduced from the owner log all three were false and the real cause was
not even listed. The "Checking your will" row had the same flaw, showing one
sentence ("Found CHANGES to the DATE or the HEIRS") for five situations,
including one where it is plainly wrong (funds received).
core/heirs.py: record WHY buildTransactions gave up in a new last_build_error
attribute (8 reason codes), set at each path that previously returned empty
with no explanation, plus a processed_willexecutors counter to tell "every
will-executor was skipped" apart from "we tried and failed". Also fix a latent
crash in the prepare_transactions handler, which read a no-longer-existing
e.heirname attribute and re-raised the resulting AttributeError, masking the
real error.
gui/qt/dialogs.py: add msg_alert() (amber warning sign, body text in the theme
colour, readable in both themes), _build_failure_message() and
_check_failure_message() to turn those causes into one precise sentence each,
with an honest "cause could not be determined" fallback. Catch
BalanceTooLowException, which already carried the figures but fell through to
the generic red technical error.
No new exception classes were introduced (owner request): the plain
NotCompleteWillException cases are told apart structurally, not by text.
241 lines
6.1 KiB
Python
241 lines
6.1 KiB
Python
"""
|
|
bal.gui.qt.common
|
|
=================
|
|
|
|
Shared imports and tiny helper utilities for the Qt GUI layer.
|
|
|
|
Every other ``bal.gui.qt`` module does ``from .common import *`` so that the
|
|
long list of Electrum / PyQt6 imports lives in a single place. This file also
|
|
hosts a few GUI helpers that do not deserve a module of their own:
|
|
|
|
* :class:`shown_cv` - trivial mutable "is this tab shown?" holder.
|
|
* :func:`add_widget` - add a labelled widget (plus optional help) to a grid.
|
|
* :func:`log_error` - format an exception traceback for a dialog.
|
|
* :func:`export_meta_gui` - export plugin metadata to a JSON file.
|
|
(:class:`CheckAliveError` now lives in ``bal.core.checkalive``.)
|
|
"""
|
|
|
|
import copy
|
|
import enum
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import time
|
|
import traceback
|
|
from datetime import datetime, timedelta, timezone
|
|
from decimal import Decimal
|
|
from functools import partial
|
|
from typing import Any, Callable, Mapping, Optional, Union
|
|
|
|
from electrum.bitcoin import NLOCKTIME_BLOCKHEIGHT_MAX, NLOCKTIME_MAX, NLOCKTIME_MIN
|
|
from electrum.gui.qt.amountedit import BTCAmountEdit
|
|
from electrum.gui.qt.main_window import ElectrumWindow, StatusBarButton
|
|
from electrum.gui.qt.my_treeview import MyTreeView
|
|
from electrum.gui.qt.password_dialog import PasswordDialog
|
|
from electrum.gui.qt.transaction_dialog import TxDialog
|
|
from electrum.gui.qt.util import (
|
|
Buttons,
|
|
CancelButton,
|
|
ColorScheme,
|
|
EnterButton,
|
|
HelpButton,
|
|
MessageBoxMixin,
|
|
OkButton,
|
|
TaskThread,
|
|
WindowModalDialog,
|
|
char_width_in_lineedit,
|
|
getOpenFileName,
|
|
getSaveFileName,
|
|
import_meta_gui,
|
|
read_QIcon_from_bytes,
|
|
read_QPixmap_from_bytes,
|
|
webopen,
|
|
)
|
|
from electrum.i18n import _
|
|
from electrum.logging import get_logger
|
|
from electrum.network import BestEffortRequestFailed, Network, TxBroadcastError
|
|
from electrum.payment_identifier import PaymentIdentifier
|
|
from electrum.plugin import hook
|
|
from electrum.transaction import SerializationError, Transaction, tx_from_any
|
|
from electrum.util import (
|
|
DECIMAL_POINT,
|
|
FileExportFailed,
|
|
FileImportFailed,
|
|
UserCancelled,
|
|
decimal_point_to_base_unit_name,
|
|
read_json_file,
|
|
write_json_file,
|
|
)
|
|
from PyQt6.QtCore import (
|
|
QDateTime,
|
|
QModelIndex,
|
|
QPersistentModelIndex,
|
|
QSize,
|
|
Qt,
|
|
QTimer,
|
|
pyqtSignal,
|
|
)
|
|
from PyQt6.QtGui import QColor, QPainter, QPalette, QStandardItem, QStandardItemModel
|
|
from PyQt6.QtWidgets import (
|
|
QAbstractItemView,
|
|
QAbstractSpinBox,
|
|
QApplication,
|
|
QCheckBox,
|
|
QComboBox,
|
|
QDateTimeEdit,
|
|
QDialog,
|
|
QGridLayout,
|
|
QHBoxLayout,
|
|
QInputDialog,
|
|
QLabel,
|
|
QLineEdit,
|
|
QMenu,
|
|
QMenuBar,
|
|
QPushButton,
|
|
QScrollArea,
|
|
QSizePolicy,
|
|
QSpinBox,
|
|
QStackedWidget,
|
|
QStyle,
|
|
QStyleOptionFrame,
|
|
QTextEdit,
|
|
QToolButton,
|
|
QVBoxLayout,
|
|
QWidget,
|
|
)
|
|
|
|
from ...core.heirs import (
|
|
HEIR_DUST_AMOUNT,
|
|
HEIR_REAL_AMOUNT,
|
|
OP_RETURN_PREFIX,
|
|
BalanceTooLowException,
|
|
HeirAmountIsDustException,
|
|
Heirs,
|
|
WillExecutorFeeTooHighException,
|
|
get_op_return_hex,
|
|
is_op_return_address,
|
|
validate_op_return_hex,
|
|
)
|
|
|
|
# --- Core (GUI-free) logic layer ---
|
|
from ...core.plugin_base import BalPlugin, BalTimestamp
|
|
from ...core.util import Util
|
|
from ...core.will import (
|
|
AmountException,
|
|
HeirChangeException,
|
|
HeirNotFoundException,
|
|
NoHeirsException,
|
|
NotCompleteWillException,
|
|
NoWillExecutorNotPresent,
|
|
TxFeesChangedException,
|
|
Will,
|
|
WillexecutorChangeException,
|
|
WillExecutorNotPresent,
|
|
WillExpiredException,
|
|
WillItem,
|
|
WillPostponedException,
|
|
)
|
|
from ...core.willexecutors import ( # noqa: F401
|
|
Willexecutors,
|
|
is_onion_url,
|
|
is_tor_active,
|
|
)
|
|
|
|
# --- Presentation helpers ---
|
|
from .theme import (
|
|
server_status_text,
|
|
server_status_tooltip,
|
|
signature_suffix,
|
|
status_color,
|
|
)
|
|
from .window_utils import (
|
|
bring_to_front,
|
|
show_modal,
|
|
show_on_top,
|
|
stop_thread,
|
|
top_level_of,
|
|
)
|
|
|
|
_logger = get_logger(__name__)
|
|
|
|
|
|
class shown_cv: # noqa: N801 (intentionally lowercase: mirrors Qt signal naming)
|
|
_type = bool
|
|
|
|
def __init__(self, value):
|
|
self.value = value
|
|
|
|
def get(self):
|
|
return self.value
|
|
|
|
def set(self, value):
|
|
self.value = value
|
|
|
|
|
|
|
|
|
|
def add_widget(grid, label, widget, row, help_):
|
|
grid.addWidget(QLabel(_(label)), row, 0)
|
|
grid.addWidget(widget, row, 1)
|
|
grid.addWidget(HelpButton(help_), row, 2)
|
|
|
|
|
|
|
|
|
|
def log_error(exec_info, window=None):
|
|
"""Log an error and optionally show it.
|
|
|
|
``exec_info`` may be either a ``sys.exc_info()`` triple
|
|
``(type, value, traceback)`` or a single exception instance (callers use
|
|
both forms), so we handle both and always try to log a full traceback.
|
|
"""
|
|
_logger.error(f"LOG_ERROR: {exec_info}")
|
|
exc = None
|
|
if isinstance(exec_info, BaseException):
|
|
exc = exec_info
|
|
elif isinstance(exec_info, (tuple, list)) and len(exec_info) >= 2:
|
|
# sys.exc_info() form: the exception instance is the 2nd element.
|
|
exc = exec_info[1]
|
|
try:
|
|
if exc is not None:
|
|
_logger.error(
|
|
"".join(
|
|
traceback.format_exception(type(exc), exc, exc.__traceback__)
|
|
)
|
|
)
|
|
else:
|
|
_logger.error(traceback.format_exc())
|
|
except Exception:
|
|
_logger.error(traceback.format_exc())
|
|
|
|
if window is not None:
|
|
# show_error expects a human-readable message, not a triple.
|
|
window.show_error(str(exc) if exc is not None else str(exec_info))
|
|
|
|
|
|
|
|
|
|
def export_meta_gui(electrum_window, title, exporter):
|
|
filter_ = "All files (*)"
|
|
filename = getSaveFileName(
|
|
parent=electrum_window,
|
|
title=_("Select file to save your {}".format(title)),
|
|
filename="BALplugin_{}_{}_{}".format(
|
|
BalPlugin.chainname, str(electrum_window.wallet), title
|
|
),
|
|
filter=filter_,
|
|
config=electrum_window.config,
|
|
)
|
|
if not filename:
|
|
return
|
|
try:
|
|
exporter(filename)
|
|
except FileExportFailed as e:
|
|
electrum_window.show_critical(str(e))
|
|
else:
|
|
electrum_window.show_message(
|
|
_("Your {0} were exported to '{1}'".format(title, str(filename)))
|
|
)
|
|
|
|
|