A cyclic part could only ever be rendered at its natural width, which is
fine for one of @scorpio810's two real layouts and wrong for the other:
April 5000/2000, 32-point cards %IX0.0 .. %IX0.31, then %IX1.0
Schneider M340, 64-point cards I1.00 .. I1.63, then I2.00
The first wants no padding, the second wants two digits. Since the two
conflict, the width cannot be derived from the modulus or from the part
type -- it has to be the user's to set.
Add a format field holding a run of zeros, the same convention a
spreadsheet uses for integer padding: "00" renders 7 as 07, "000" as 007.
The field's length is the minimum number of digits. It applies to every
numeric part type, not only cyclic ones, so "Chiffre 01" can be widened
past two digits without inventing another type for it.
An empty mask means the part type's own natural width, so it reproduces
exactly what every existing context does today -- Chiffre 1 stays 7,
Chiffre 01 stays 07, Chiffre 001 stays 007. That is what makes this safe
for existing projects: absent is the default, and absent changes nothing.
Stored as a sixth field on the context part and as an XML attribute
written only when set, following how modulus was added: readers guard on
size() and treat a short item as "no format". All seven places that
rebuild a part while incrementing it now carry the format through --
missing one would have silently dropped the padding on the second element
numbered.
The editor field is restricted to zeros by a validator, and is enabled
only for types that render as a number.
Measured:
April, mask empty %IX0.29 %IX0.30 %IX0.31 %IX1.0 %IX1.1
M340, mask "00" I1.00 I1.01 ... I1.62 I1.63 I2.00 I2.01
no mask unit 7,8,9 ten 07,08,09 hundred 007,008,009
ten with mask "0000" 0007 0008 0009
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.
Adds a configurable wrap-at-N counter type to the autonumbering engine
(NumerotationContext + NumerotationContextCommands), covering PLC/rack-style
addressing conventions like "e0.0...e0.7, e1.0...e1.7" (8 channels per
card) generally, rather than hardcoding octal specifically.
- New "wrap" part type (WrapNum, alongside the existing UnitNum/TenNum/
HundredNum strategies) stores a modulus in addition to the existing
value/increase/initialvalue fields. Its own next()/previous() only wraps
its own value modulo the configured modulus -- carrying into (or
borrowing from) the adjacent part requires visibility across parts,
which only the composition loop has.
- NumerotationContextCommands::next()/previous() gained carry()/borrow()
helpers: when a wrap part's own next() would reach/exceed its modulus
(or go below 0 on previous()), the nearest preceding numeric part is
bumped by exactly one unit, skipping non-numeric parts (e.g. a "."
string separator). Wrap parts chain correctly if adjacent (e.g. seconds
wrapping into minutes wrapping into hours).
- For the leading part of a wrap-and-carry pair to stay fixed except when
carried into (i.e. actually produce "e0.0...e0.7, e1.0..." rather than
advancing on every step under its own strategy), its own increase must
be 0. The increase spinbox's minimum was 1, which made this
configuration impossible through the UI -- lowered to 0 and documented
with a tooltip, since this wasn't obvious from the UI alone.
- NumerotationContext gained a 5th pipe-separated field (modulus) in its
serialized string form, defaulting to 0 (non-wrapping) for every
existing part type; toXml()/fromXml() persist it as a "modulus" XML
attribute the same way "initialvalue" is already persisted for
unitfolio/tenfolio/hundredfolio.
- New "Cyclique (modulo)" entry in the part-type dropdown (numparteditorw),
available for element, conductor, and folio autonumbering alike, since
all three already go through NumerotationContextCommands.
Verified in the running app via the numbering config dialog's own
Suivant/Précédent buttons (which call the production
NumerotationContextCommands::next()/previous() directly): a two-part
context (unit, increase=0 + wrap mod 8) produced exactly
e0.0→...→e0.7→e1.0→...→e1.7 on repeated "next", and the exact reverse
(with correct borrowing) on repeated "previous".