Add alphabetical auto-numbering (a, b, ... z, aa, ab, ...) (#579)

Adds a real base-26 incrementing part type to the autonumbering engine,
alongside the 14 existing NumStrategy leaves. Unlike StringNum (a fixed,
non-incrementing text segment), AlphaNum::next()/previous() carry/borrow
entirely within the part's own value -- the composition loop in
NumerotationContextCommands doesn't need to change, since (unlike #578's
wrap-and-carry) nothing here needs to signal an adjacent part.

- incrementAlpha()/decrementAlpha() implement the spreadsheet-column-name
  algorithm: increment carries right-to-left on 'z'/'Z' overflow,
  prepending a new leading letter if the whole value overflows (z -> aa,
  az -> ba). decrement is the exact inverse, including the symmetric
  shrink case (aa -> z) once every position has borrowed. A single letter
  already at "a"/"A" has no representable predecessor and is clamped
  rather than turned into "z" -- caught via manual testing, since the
  initial implementation mutated the string in the borrow loop before
  checking whether to clamp, silently discarding the original value.
- Registered in NumerotationContext::validRegExpNum() but deliberately
  not in validRegExpNumber(), so addValue() doesn't force alphabetic
  values through int conversion.
- New "Cyclique"-adjacent "Alphabétique" entry in numparteditorw's type
  dropdown, with its own letters-only QRegularExpressionValidator; the
  increase spinbox is disabled since the step is always exactly one
  letter, not a configurable amount.

Also wires the new part type through to actual element/conductor labels,
which turned out to be required for the feature to do anything visible
beyond folio numbering (which applies a NumerotationContext's
represented string directly). Element and conductor numbering instead
go through a separate formula-substitution layer
(autonum::sequentialNumbers + %sequ_/%seqt_/%seqh_-style placeholders in
AssignVariables::assignSequence()) that numerotationContextToFormula()
auto-populates. Without a matching placeholder, an "alpha" part would
silently vanish from the generated formula and never reach the label,
even though the underlying counter was advancing correctly:
- sequentialNumbers gained an `alpha` QStringList member (copy ctor,
  operator=, operator==, toXml/fromXml, clear()).
- numerotationContextToFormula() emits a new %seqa_N placeholder for
  alpha parts, the same way %sequ_N is emitted for unit parts.
- setSequential()/setSequentialToList() populate seqStruct.alpha,
  passing the raw string through as-is rather than the .toInt()-based
  formatting used for the numeric part types.
- AssignVariables::assignSequence() substitutes %seqa_N from
  seqStruct.alpha, mirroring the existing %sequ_N/%seqt_N/%seqh_N
  substitutions.
No "alphafolio" variant was added, matching the discussion's scope (only
unit/ten/hundred have folio-anchored variants).

Verified against production code via the numbering config dialog's own
Suivant/Précédent buttons: from "a", 25 clicks reached "z"; one more
produced "aa"; 25 more reached "az"; one more produced "ba" (carry).
Reversed: "ba"->"az"->(25 clicks)->"aa"->"z" (shrink)->(25 clicks)->"a".
One more "previous" at "a" correctly stayed at "a" after the clamp fix.
Also confirmed the Formule field auto-updates to "%seqa_1" the instant
the type is switched to "Alphabétique", confirming the formula-generation
wiring works live in the UI, not just at the engine level.
This commit is contained in:
ispyisail
2026-08-01 21:34:20 +12:00
parent 25effbd137
commit f7a79e75af
7 changed files with 196 additions and 4 deletions
+32 -3
View File
@@ -44,6 +44,7 @@ namespace autonum
ten_folio = other.ten_folio;
hundred = other.hundred;
hundred_folio = other.hundred_folio;
alpha = other.alpha;
}
sequentialNumbers::~sequentialNumbers()
@@ -61,6 +62,7 @@ namespace autonum
ten_folio = other.ten_folio;
hundred = other.hundred;
hundred_folio = other.hundred_folio;
alpha = other.alpha;
return (*this);
}
@@ -72,7 +74,8 @@ namespace autonum
ten == other.ten && \
ten_folio == other.ten_folio && \
hundred == other.hundred && \
hundred_folio == other.hundred_folio)
hundred_folio == other.hundred_folio && \
alpha == other.alpha)
return true;
else
return false;
@@ -129,6 +132,11 @@ namespace autonum
document,
"hundredFolio",
hundred_folio.join(";")));
if(!alpha.isEmpty())
element.appendChild(QETXML::textToDomElement(
document,
"alpha",
alpha.join(";")));
return element;
}
@@ -162,6 +170,9 @@ namespace autonum
from = element.firstChildElement("hundredFolio");
hundred_folio = from.text().split(";");
from = element.firstChildElement("alpha");
alpha = from.text().split(";");
}
//Clear this sequence
@@ -173,6 +184,7 @@ namespace autonum
ten_folio.clear();
hundred.clear();
hundred_folio.clear();
alpha.clear();
}
/**
@@ -414,8 +426,10 @@ namespace autonum
m_seq_struct.ten_folio.size()),
qMax(m_seq_struct.hundred_folio.size(),
m_seq_struct.unit.size())),
qMax(m_seq_struct.hundred.size(),
m_seq_struct.ten.size())
qMax(
qMax(m_seq_struct.hundred.size(),
m_seq_struct.ten.size()),
m_seq_struct.alpha.size())
);
for (int i=1; i<=max ; i++)
@@ -438,6 +452,9 @@ namespace autonum
if (m_assigned_label.contains("%seqhf_" + QString::number(i)) && m_seq_struct.hundred_folio.size() >= i) {
m_assigned_label.replace("%seqhf_" + QString::number(i),m_seq_struct.hundred_folio.at(i-1));
}
if (m_assigned_label.contains("%seqa_" + QString::number(i)) && m_seq_struct.alpha.size() >= i) {
m_assigned_label.replace("%seqa_" + QString::number(i),m_seq_struct.alpha.at(i-1));
}
}
}
@@ -462,6 +479,9 @@ namespace autonum
number = QString("%1").arg(context.itemAt(i).at(1).toInt(), 2, 10, QChar('0'));
else if (type == "hundred" || type == "hundredfolio")
number = QString("%1").arg(context.itemAt(i).at(1).toInt(), 3, 10, QChar('0'));
else if (type == "alpha")
//Alphabetic value, not an integer -- used as-is.
number = context.itemAt(i).at(1);
else number = QString::number(context.itemAt(i).at(1).toInt());
list.append(number);
}
@@ -551,6 +571,10 @@ namespace autonum
autonum::setSequentialToList(seqStruct.hundred_folio, context,"hundredfolio");
autonum::setFolioSequentialToHash(seqStruct.hundred_folio, diagram->m_elmt_hundredfolio_max, hashKey);
}
if (label.contains("%seqa_"))
{
autonum::setSequentialToList(seqStruct.alpha, context,"alpha");
}
}
}
@@ -570,6 +594,7 @@ namespace autonum
int count_tenf = 0;
int count_hundred = 0;
int count_hundredf = 0;
int count_alpha = 0;
for(int i=0 ; i<nc.size() ; i++)
{
@@ -626,6 +651,10 @@ namespace autonum
count_hundredf++;
formula.append("%seqhf_" + QString::number(count_hundredf));
}
else if (type == "alpha") {
count_alpha++;
formula.append("%seqa_" + QString::number(count_alpha));
}
}
return formula;