mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-08-13 10:04:13 +02:00
TitleBlockTemplate::minimumWidth() divided by (100.0 - sum(RelativeToTotalLength)) without guarding against a zero or negative denominator. When a template's relative-to-total-length
columns summed to exactly 100% (e.g. the shipped A4_1.titleblock), this produced qRound(NaN), which fatally aborted under Qt6's stricter qCheckedFPConversionToInteger assertion -- reached via double-clicking a title block template to edit it. Introduce TitleBlockTemplate::classifyWidthConstraint(), shared by minimumWidth() and maximumWidth(), returning std::optional WidthConstraintCase> to distinguish three non-finite outcomes: Unconstrained (RTT columns == 100%, no absolute columns -- an ordinary, valid template), RelativeWidthExceeds100Percent (RTT alone exceeds 100%), and AbsoluteColumnsExceedRemainingWidth (RTT == 100% with at least one absolute column also present) -- the latter two meaning the template's columns cannot be laid out at any width. maximumWidth() previously only checked "are all columns absolute", which incorrectly reported "no upper bound" for the two unsatisfiable cases above; it now shares the same classification, so both functions agree. Update TitleBlockTemplateView::updateDisplayedMinMaxWidth() to show distinct, accurate tooltip text for all four cases instead of printing the old std::numeric_limits<int>::max() sentinel or a misleading "no constraint" message for an unsatisfiable template. Manually verified all four cases: a normal template (finite width), A4_1.titleblock (Unconstrained), an over-100% RTT template (RelativeWidthExceeds100Percent), and RTT==100% with an absolute column present (AbsoluteColumnsExceedRemainingWidth). Translations still partially missing.
This commit is contained in:
@@ -999,34 +999,47 @@ void TitleBlockTemplateView::updateDisplayedMinMaxWidth()
|
||||
int max_width = tbtemplate_ -> maximumWidth();
|
||||
|
||||
QString min_max_width_sentence;
|
||||
if (min_width != -1 && max_width != -1) {
|
||||
min_max_width_sentence = QString(
|
||||
tr(
|
||||
"Longueur minimale : %1px\nLongueur maximale : %2px\n",
|
||||
"tooltip showing the minimum and/or maximum width of the edited template"
|
||||
)
|
||||
).arg(min_width).arg(max_width);
|
||||
} else if (min_width != -1) {
|
||||
min_max_width_sentence = QString(
|
||||
tr(
|
||||
"Longueur minimale : %1px\n",
|
||||
"tooltip showing the minimum width of the edited template"
|
||||
)
|
||||
).arg(min_width);
|
||||
} else if (max_width != -1) {
|
||||
min_max_width_sentence = QString(
|
||||
tr(
|
||||
"Longueur maximale : %1px\n",
|
||||
"tooltip showing the maximum width of the edited template"
|
||||
)
|
||||
).arg(max_width);
|
||||
} else {
|
||||
min_max_width_sentence = QString(
|
||||
tr(
|
||||
"Longueur non contrainte.\n",
|
||||
"tooltip shown when the edited template has neither a minimum nor a maximum width constraint"
|
||||
)
|
||||
);
|
||||
|
||||
switch (static_cast<TitleBlockTemplate::WidthConstraintCase>(min_width)) {
|
||||
case TitleBlockTemplate::WidthConstraintCase::RelativeWidthExceeds100Percent:
|
||||
// minimumWidth() and maximumWidth() both check infeasibility
|
||||
// first, identically, so max_width reports the same case
|
||||
// here -- no need to check it separately.
|
||||
min_max_width_sentence = tr(
|
||||
"Attention : la somme des largeurs relatives dépasse 100%% de la largeur totale, ce modèle de cartouche ne peut être satisfait par aucune largeur.\n",
|
||||
"tooltip warning shown when a template's relative-to-total-length columns alone already exceed 100%% of the total width"
|
||||
);
|
||||
break;
|
||||
case TitleBlockTemplate::WidthConstraintCase::AbsoluteColumnsExceedRemainingWidth:
|
||||
min_max_width_sentence = tr(
|
||||
"Attention : les colonnes de largeur fixe ne peuvent pas tenir dans la largeur restante, ce modèle de cartouche ne peut être satisfait par aucune largeur.\n",
|
||||
"tooltip warning shown when a template's relative-to-total-length columns already consume all available width, leaving no room for its fixed-width columns"
|
||||
);
|
||||
break;
|
||||
default:
|
||||
if (min_width != static_cast<int>(TitleBlockTemplate::WidthConstraintCase::Unconstrained)) {
|
||||
min_max_width_sentence += QString(
|
||||
tr(
|
||||
"Longueur minimale : %1px\n",
|
||||
"tooltip showing the minimum width of the edited template"
|
||||
)
|
||||
).arg(min_width);
|
||||
}
|
||||
if (max_width != static_cast<int>(TitleBlockTemplate::WidthConstraintCase::Unconstrained)) {
|
||||
min_max_width_sentence += QString(
|
||||
tr(
|
||||
"Longueur maximale : %1px\n",
|
||||
"tooltip showing the maximum width of the edited template"
|
||||
)
|
||||
).arg(max_width);
|
||||
}
|
||||
if (min_max_width_sentence.isEmpty()) {
|
||||
min_max_width_sentence = tr(
|
||||
"Longueur non contrainte.\n",
|
||||
"tooltip shown when the edited template has neither a minimum nor a maximum width constraint"
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// the tooltip may also display the split label for readability purpose
|
||||
|
||||
+109
-26
@@ -960,47 +960,130 @@ int TitleBlockTemplate::columnTypeTotal(QET::TitleBlockColumnLength type) {
|
||||
}
|
||||
|
||||
/**
|
||||
@return the minimum width for this template
|
||||
@brief TitleBlockTemplate::classifyWidthConstraint
|
||||
Classifies this template's absolute-width (ABS) and
|
||||
relative-to-total-length (RTT) columns, independently of whether a
|
||||
minimum or a maximum width is being computed.
|
||||
|
||||
The classification is derived from the same inequality
|
||||
minimumWidth() solves for a minimum width:
|
||||
@code
|
||||
TOT >= ((sum(RTT)/100)*TOT) + sum(ABS)
|
||||
@endcode
|
||||
Regardless of TOT, the RTT term above scales linearly with TOT. If
|
||||
sum(RTT) alone already accounts for 100% or more of TOT, no choice
|
||||
of TOT -- however large -- can make room for it (and for any ABS
|
||||
columns on top of it): making the template wider grows the RTT
|
||||
columns' pixel footprint by the same proportion, so the shortfall
|
||||
never resolves. This function exists so both minimumWidth() and
|
||||
maximumWidth() report that consistently, instead of maximumWidth()
|
||||
incorrectly treating a misconfigured template as "no upper bound".
|
||||
|
||||
@param[out] abs_total set to columnTypeTotal(QET::Absolute).
|
||||
@param[out] remaining_width_fraction set to the fraction of the
|
||||
total template width left over once the RTT columns have taken
|
||||
their share: (100.0 - sum(RTT)) / 100.0. Zero means the RTT
|
||||
columns claim the entire width, leaving nothing for ABS columns;
|
||||
negative means they claim more than the entire width, which is
|
||||
unsatisfiable regardless of any ABS columns. Only meaningful when
|
||||
this function returns std::nullopt.
|
||||
@return WidthConstraintCase::RelativeWidthExceeds100Percent if
|
||||
sum(RTT) exceeds 100% (unsatisfiable at any width, with or without
|
||||
ABS columns), WidthConstraintCase::AbsoluteColumnsExceedRemainingWidth
|
||||
if sum(RTT) equals exactly 100% and at least one ABS column is also
|
||||
present (unsatisfiable at any width, since the RTT columns leave no
|
||||
room for it), WidthConstraintCase::Unconstrained if sum(RTT) equals
|
||||
exactly 100% with no ABS columns (every term vanishes, any width
|
||||
holds -- an ordinary, valid template), or std::nullopt if this
|
||||
template's columns admit a genuine finite width.
|
||||
*/
|
||||
std::optional<TitleBlockTemplate::WidthConstraintCase> TitleBlockTemplate::classifyWidthConstraint(int &abs_total, qreal &remaining_width_fraction)
|
||||
{
|
||||
abs_total = columnTypeTotal(QET::Absolute);
|
||||
remaining_width_fraction = (100.0 - columnTypeTotal(QET::RelativeToTotalLength)) / 100.0;
|
||||
|
||||
if (remaining_width_fraction < 0.0) {
|
||||
return WidthConstraintCase::RelativeWidthExceeds100Percent;
|
||||
}
|
||||
if (remaining_width_fraction == 0.0) {
|
||||
return abs_total == 0
|
||||
? WidthConstraintCase::Unconstrained
|
||||
: WidthConstraintCase::AbsoluteColumnsExceedRemainingWidth;
|
||||
}
|
||||
return std::nullopt; // a finite width exists: remaining_width_fraction > 0
|
||||
}
|
||||
|
||||
/**
|
||||
@brief TitleBlockTemplate::minimumWidth
|
||||
@return the minimum width, in pixels, required for this template's
|
||||
absolute-width (ABS) and relative-to-total-length (RTT) columns to
|
||||
all fit.
|
||||
|
||||
Derivation: writing TOT for the (variable) total template width,
|
||||
the minimum size enforced by the ABS and RTT columns is:
|
||||
@code
|
||||
TOT >= ((sum(RTT)/100)*TOT) + sum(ABS)
|
||||
=> (1 - (sum(RTT)/100))*TOT >= sum(ABS)
|
||||
=> TOT >= sum(ABS) / (1 - (sum(RTT)/100))
|
||||
=> TOT >= sum(ABS) / ((100 - sum(RTT))/100)
|
||||
@endcode
|
||||
relative-to-remaining-length (RTR) columns do not constrain the
|
||||
minimum width, since by definition they only ever claim a share of
|
||||
whatever space is left over after the ABS and RTT columns are laid
|
||||
out.
|
||||
|
||||
If no finite minimum width applies, returns one of
|
||||
WidthConstraintCase::Unconstrained,
|
||||
WidthConstraintCase::RelativeWidthExceeds100Percent, or
|
||||
WidthConstraintCase::AbsoluteColumnsExceedRemainingWidth (cast to
|
||||
int) instead -- see classifyWidthConstraint() and that enum's
|
||||
documentation for when each case applies.
|
||||
*/
|
||||
int TitleBlockTemplate::minimumWidth()
|
||||
{
|
||||
// Abbreviations: ABS: absolute, RTT: relative to total, RTR:
|
||||
// relative to remaining,
|
||||
// TOT: total diagram/TBT width (variable).
|
||||
|
||||
// Minimum size may be enforced by ABS and RTT widths:
|
||||
// TOT >= ((sum(REL)/100)*TOT)+sum(ABS)
|
||||
// => (1 - (sum(REL)/100))TOT >= sum(ABS)
|
||||
// => TOT >= sum(ABS) / (1 - (sum(REL)/100))
|
||||
// => TOT >= sum(ABS) / ((100 - sum(REL))/100))
|
||||
int abs_total = columnTypeTotal(QET::Absolute);
|
||||
qreal denominator = (100.0 - columnTypeTotal(QET::RelativeToTotalLength)) / 100.0;
|
||||
|
||||
if (denominator <= 0.0) {
|
||||
// The relative-to-total-length columns alone already consume
|
||||
// 100% (or more) of the available width, so the formula above
|
||||
// would divide by zero (or go negative). There is no finite
|
||||
// minimum width this formula can determine. Report "no
|
||||
// constraint", the same convention maximumWidth() uses.
|
||||
return -1;
|
||||
int abs_total;
|
||||
qreal remaining_width_fraction;
|
||||
if (auto width_case = classifyWidthConstraint(abs_total, remaining_width_fraction)) {
|
||||
return static_cast<int>(*width_case);
|
||||
}
|
||||
|
||||
return(qRound(abs_total / denominator));
|
||||
return qRound(abs_total / remaining_width_fraction);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief TitleBlockTemplate::maximumWidth
|
||||
@return the maximum width for this template,
|
||||
or -1 if it does not have any.
|
||||
@return the maximum width, in pixels, this template may be
|
||||
rendered at.
|
||||
|
||||
If this template is composed entirely of absolute-width (ABS)
|
||||
columns, the maximum is fixed: the template cannot extend beyond
|
||||
their sum, since nothing in it scales with the template's total
|
||||
width. Otherwise, at least one column scales with the total width
|
||||
(relative-to-total-length or relative-to-remaining-length), so
|
||||
there is ordinarily no upper bound: returns
|
||||
WidthConstraintCase::Unconstrained (cast to int) -- the common case
|
||||
for most templates.
|
||||
|
||||
The exception is when this template's columns cannot be satisfied
|
||||
by any width at all (see classifyWidthConstraint()) -- in that case
|
||||
there is no width, however large, that works, and this returns
|
||||
WidthConstraintCase::RelativeWidthExceeds100Percent or
|
||||
WidthConstraintCase::AbsoluteColumnsExceedRemainingWidth (cast to
|
||||
int) instead of Unconstrained, matching minimumWidth()'s handling
|
||||
of the same cases.
|
||||
*/
|
||||
int TitleBlockTemplate::maximumWidth()
|
||||
{
|
||||
int abs_total;
|
||||
qreal remaining_width_fraction;
|
||||
if (auto width_case = classifyWidthConstraint(abs_total, remaining_width_fraction)) {
|
||||
return static_cast<int>(*width_case);
|
||||
}
|
||||
if (columnTypeCount(QET::Absolute) == columns_width_.count()) {
|
||||
// The template is composed of absolute widths only,
|
||||
// therefore it may not extend beyond their sum.
|
||||
return(columnTypeTotal(QET::Absolute));
|
||||
return abs_total; // already computed by classifyWidthConstraint()
|
||||
}
|
||||
return(-1);
|
||||
return static_cast<int>(WidthConstraintCase::Unconstrained);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <QtSvg>
|
||||
#include <QtXml>
|
||||
#include <optional>
|
||||
|
||||
/**
|
||||
@brief The TitleBlockTemplate class
|
||||
@@ -36,9 +37,73 @@
|
||||
*/
|
||||
class TitleBlockTemplate : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
@brief The TitleBlockTemplate::WidthConstraintCase enum Distinguishes
|
||||
the possible outcomes of minimumWidth() or maximumWidth() for a
|
||||
template's column layout: either a genuine finite pixel width, or one
|
||||
of the cases below where no single finite width applies -- some of which are
|
||||
perfectly ordinary (Unconstrained), and some of which indicate the
|
||||
template's columns cannot be laid out at any width.
|
||||
|
||||
Both functions return one of the non-Bounded* cases below,
|
||||
cast to int, in place of a genuine width whenever no finite
|
||||
width applies. (*minimumWidth()/maximumWidth() never return
|
||||
a literal "Bounded" value -- when a finite width exists,
|
||||
they return that width directly. Bounded only appears as a
|
||||
concept in classifyWidthConstraint()'s std::nullopt return.)
|
||||
The int-based encoding exists because this project
|
||||
currently targets C++17.
|
||||
|
||||
@todo Once this project's minimum supported C++ standard
|
||||
reaches C++23, migrate minimumWidth() and maximumWidth() to
|
||||
return std::expected<int, WidthConstraintCase> instead of
|
||||
encoding these cases as negative int sentinels. That removes
|
||||
the possibility of a caller silently misinterpreting a
|
||||
sentinel as a real pixel width -- something the current
|
||||
int-based encoding cannot prevent at compile time.
|
||||
*/
|
||||
enum class WidthConstraintCase : int {
|
||||
/**
|
||||
There is no finite width constraint in this direction:
|
||||
any width is valid. This is a perfectly ordinary case,
|
||||
not a problem with the template -- for minimumWidth(),
|
||||
it happens when the relative-to-total-length (RTT)
|
||||
columns account for exactly 100% of the total width and
|
||||
there are no absolute-width (ABS) columns competing for
|
||||
space -- every term in the minimum-width inequality
|
||||
vanishes (0 >= 0), which holds for any width. For
|
||||
maximumWidth(), it happens whenever at least one column
|
||||
scales with the total width, so nothing caps how wide
|
||||
the template may grow -- the common case for most
|
||||
templates.
|
||||
*/
|
||||
Unconstrained = -1,
|
||||
|
||||
/**
|
||||
The RTT columns alone already exceed 100% of the total
|
||||
width (sum(RTT) > 100), independently of whether any ABS
|
||||
columns exist. The minimum-width inequality then only
|
||||
holds for a non-positive width, which cannot represent a
|
||||
real template: this indicates a genuinely misconfigured
|
||||
template that cannot be laid out at any width.
|
||||
*/
|
||||
RelativeWidthExceeds100Percent = -2,
|
||||
|
||||
/**
|
||||
The RTT columns account for exactly 100% of the total
|
||||
width, but at least one ABS column also needs a nonzero,
|
||||
fixed amount of space on top of that. The minimum-width
|
||||
inequality reduces to "0 >= (a positive number)", which
|
||||
never holds: this also indicates a genuinely
|
||||
misconfigured template that cannot be laid out at any
|
||||
width.
|
||||
*/
|
||||
AbsoluteColumnsExceedRemainingWidth = -3
|
||||
};
|
||||
|
||||
// constructors, destructor
|
||||
public:
|
||||
TitleBlockTemplate(QObject * = nullptr);
|
||||
~TitleBlockTemplate() override;
|
||||
private:
|
||||
@@ -146,6 +211,20 @@ class TitleBlockTemplate : public QObject {
|
||||
bool checkCell(const QDomElement &, TitleBlockCell ** = nullptr);
|
||||
void flushCells();
|
||||
void initCells();
|
||||
/**
|
||||
@brief TitleBlockTemplate::classifyWidthConstraint Classifies this template's absolute-width (ABS) and
|
||||
relative-to-total-length (RTT) columns, independently of whether a minimum or a maximum width is being computed -- see
|
||||
minimumWidth() for the derivation this is based on.
|
||||
@param[out] abs_total set to columnTypeTotal(QET::Absolute).
|
||||
@param[out] remaining_width_fraction set to the fraction of the total template width left over once the RTT columns have taken
|
||||
their share: (100.0 - sum(RTT)) / 100.0. Zero means the RTT columns claim the entire width, leaving nothing for ABS
|
||||
columns; negative means they claim more than the entire width, which is unsatisfiable regardless of any ABS columns. Only
|
||||
meaningful when this function returns std::nullopt.
|
||||
@return WidthConstraintCase::RelativeWidthExceeds100Percent, WidthConstraintCase::AbsoluteColumnsExceedRemainingWidth, or
|
||||
WidthConstraintCase::Unconstrained if no finite width exists for this template's columns, or std::nullopt if a finite width
|
||||
does exist (computable as qRound(abs_total / remaining_width_fraction)).
|
||||
*/
|
||||
std::optional<WidthConstraintCase> classifyWidthConstraint(int &abs_total, qreal &remaining_width_fraction);
|
||||
int lengthRange(int, int, const QList<int> &) const;
|
||||
QString finalTextForCell(
|
||||
const TitleBlockCell &,
|
||||
|
||||
Reference in New Issue
Block a user