Add a 'Numérotation auto' tab to the global settings page (Settings >
Nouveau projet) where users can define default auto-numbering rules
for Conducteurs, Eléments, and Folios. These rules are automatically
transferred to every new project created.
Changes:
- Add NumerotationContext::saveToSettings()/loadFromSettings() static
helpers for persisting named numerotation contexts via QSettings
- Add 'Numérotation auto' tab to NewDiagramPage with three sub-tabs
using SelectAutonumW widgets (same UI as project properties)
- Add save/remove/persist slots for conductor, element, and folio
contexts with immediate QSettings persistence on every change
- NewDiagramPage::applyConf() saves autonum settings when editing
global defaults (no project)
- QETProject constructor loads global autonum settings from QSettings
for new empty projects
QET had no icon theme: the 446 entries of the icon table and the 116
iconsets in .ui files each named a resource path, so an icon could only
ever be one file, and a variant for another palette or a vector source
had nowhere to go (GitHub #466, #690, #870). This adds the theme layout
without changing a single pixel; a dark variant comes in a follow-up.
The theme "qet" follows the freedesktop layout Qt's icon loader
understands. misc/make_icon_themes.py generates ico/icon-themes.qrc,
which aliases the existing ico/<size>/<name>.png files into
themes/qet/<size>/<name>.png, and ico/themes/qet/index.theme. No file
moves. The four table entries that paired a 16 pixel file with a 22
pixel file of another name (ConductorSettings, DiagramAdd,
DiagramDelete, DialogInformation) get the 22 pixel file aliased under
the 16 pixel name.
QETApp::initIconTheme() registers the theme before initIcons() and makes
it current on every platform, so a desktop icon theme cannot replace
QET's icons. Icons are then looked up by name: QIcon::fromTheme() in
qeticons.cpp and in the few places that built a QIcon from a resource
path directly, and theme="..." on the iconsets in .ui files, with the
resource path kept as fallback. Flags, color swatches, application and
MIME icons stay on their paths.
One entry does not go through the theme. The elements panel draws the
project root with ProjectFileGP in the 50 pixel slot it reserves for
element previews, and the name "project" also carries the 128 pixel
file the configuration dialog uses. On a 2x display Qt's loader picks
that file for a 50 pixel request and fills the slot. ProjectFileGP
loads the 16 and 22 pixel files directly, as before.
tests/qttest/tst_qeticons: every name in the theme resolves, the four
aliases resolve at 22 pixels, a Fusion tool button shows its icon at
3:1 with disabled weaker than enabled, and the project root icon stays
at 22 pixels or less when asked for 50 at 2x while the configuration
dialog still gets its 128 pixel file. The rendering helpers shared
with tst_qetpalette moved to tests/qttest/inkcontrast.h.
Forum #3186 / issue #850: a user who has built up conductor and element
numbering rules in one project has no way to reuse them in the next one.
The only answer today is to open both .qet files in a text editor and copy
the XML across by hand.
Adds an "Import from another project..." button to the auto-numbering page
of the project properties dialog. It offers every numbering found in the
chosen file, per category, with names that already exist here unticked by
default and a "replace same-named numberings" option for when that is what
the user wants.
The source file is parsed as plain XML rather than opened as a QETProject.
Opening it would run the whole load path, including the modal dialog raised
for a file written by a different version of QElectroTech -- a dialog the
user has no reason to see, since nothing but the <newdiagrams> block is
being read.
Two supporting changes:
- readValuesFromProject() clears the three combo boxes before filling
them. It only ran once before; it now runs again after an import, and
without the clear every name appeared twice.
- FolioAutonumberingW::setContext() likewise replaces its list instead
of appending to it. It has a single caller, the line above.
This deliberately does not attempt the project-template feature also raised
on the forum thread. That needs decisions about where templates live and
what else they carry, and is better settled in a discussion first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The remaining two defects from bugtracker #671's original analysis,
which #686 knowingly didn't cover (see that PR's review thread and the
comment on the now-closed #672).
## #671 item 5: the XML matching ignored nesting
prefixFromLabelFile() was a flat token scan: it matched any <category
name="..."> whose name equalled the next path segment, with no check
that the match was actually a *child* of the previous match. It gave
correct results on the shipped 10_electric/qet_labels.xml only because
that file's document order happens to line up with its hierarchy --
any file with a same-named category at the wrong nesting depth would
silently return the wrong prefix.
Reproduced with a synthetic file where a top-level sibling category
happens to share a name with what should be an unmatched grandchild:
the old (already re-verified-fixed-for-whitespace) lookup returns a
prefix from a completely unrelated branch of the document; this
rewrite correctly reports "not found".
Fixed by replacing the QXmlStreamReader token walk with a QDomDocument
walk that only ever considers a matched node's direct <category>
children (firstChildElement()/nextSiblingElement(), scoped to that
node), which cannot cross into a same-named sibling subtree. This also
makes the whitespace-dependence fixed in #686 moot for the same
reason: DOM parsing doesn't distinguish pretty-printed from minified
input to begin with.
The inheritance rule ("if a directory has no prefix, use its parent's,
and so on") and the empty-<prefix/>-overrides-inheritance behaviour
#686 added both carry over unchanged: a category's own <prefix> child,
even an empty one, always overrides whatever a shallower ancestor
already provided; a category with no <prefix> child at all leaves the
inherited value untouched.
## #671 item 2: common-collection trees other than 10_electric
The lookup only ever consulted commonElementsDir()/10_electric --
literally: `if (current_location.fileName() == "10_electric")`. The
common collection ships four other top-level trees (20_logic,
30_hydraulic, 50_pneumatic, 60_energy); none of them could carry a
qet_labels.xml at all, because nothing ever looked for one.
Generalised to commonElementsDir()/<tree>/qet_labels.xml for whichever
top-level tree the element's path actually walks up to, tried first,
then custom, then company -- each of the latter two tried against both
a from-root layout (matching a custom/company file organised as a
mirror of the common collection, tree name included) and a
tree-relative one (matching a file scoped to just one tree), so
existing custom files keep working either way. This is the same
multi-candidate structure #686 already established for custom-then-
company; it now also covers which common-collection tree to check.
## Testing
Same constraint as #686: no working full build in this sandbox
(missing generated headers/deps), so the exact functions as committed
were extracted into a standalone Qt6 harness and run against the real
shipped 10_electric/qet_labels.xml (pretty-printed and minified),
a synthetic empty-prefix-override file, and the nesting-trap file
above -- 9/9, including the three cases #686 already fixed (direct
prefix, inherited prefix, not-found) staying correct, confirming this
rewrite doesn't regress that work.
Not exercised here (needs a real running QETApp / ElementsLocation,
which the standalone harness can't stand up): the elementPrefixForLocation()
candidate-list wiring itself -- collection_root computation, the
from-root/tree-relative dual lookup, and the common-then-custom-then-
company ordering. That code is mechanical and was reviewed carefully
by hand, but it has not been run.
Requested by @scorpio810 in review: an empty <prefix/> in the custom
collection should cancel a company-collection prefix, not fall through
to it. QXmlStreamReader::readElementText() returns a null QString for an
empty element, and the caller's isNull() check treats that the same as
"not found" -- distinguish the two so an explicit override actually
overrides. Verified in a standalone harness against a synthetic
override file, pretty-printed and minified.
Two more while in the same function, both from the original bugtracker
#671 analysis that this PR only partially addressed:
- QString path[10] with an unbounded index becomes a QStringList. The
deepest category in the shipped collection already needs 9 of the 10
slots; a custom collection can nest deeper, and overflow was writing
QString objects past the end of a stack array (#671 item 3).
- The common-collection lookup still concatenated
commonElementsDir() + "10_electric/qet_labels.xml" directly.
commonElementsDir() returns the configured path verbatim with no
guaranteed trailing separator, so relocating the collection to a path
without one silently mangles this into one word and the file is never
found -- the single most-reported cause of "prefixes don't work"
(#671 item 1, forum #2178/#2651). QDir::filePath() joins correctly
either way; applied to all three lookups (common, custom, company).
Also fixes a defect not in that original analysis: the token-matching
loop in prefixFromLabelFile() advanced twice per matched element --
once explicitly after a match, once more unconditionally at the bottom
of the loop -- which only produced the right result because a
pretty-printed file inserts a whitespace Characters token between
adjacent elements for the second advance to land on. A minified
qet_labels.xml has no such token, so the second advance skips clean
over the very element being searched for and the lookup silently finds
nothing -- reproduced against the real shipped 10_electric/qet_labels.xml
(returns "" instead of "K" for a plain coil, on every case tested, not
just the inheritance one). A single `continue` after a handled match
removes the double advance.
Testing: extracted the exact functions as committed into a standalone
Qt6 harness (outside the full QET build, which needs a dependency
fetch this sandbox doesn't have) and ran them against the real shipped
qet_labels.xml, pretty-printed and minified, covering a direct prefix,
inherited-from-ancestor prefix, not-found, and the explicit-empty-
override case -- 8/8, matching between formats, no regressions in the
pretty-printed results. The QDir::filePath() fix was verified
separately against both a trailing-slash and no-trailing-slash base
path. Not yet built inside the actual application (pugixml and other
generated headers aren't available standalone); the algorithm itself,
which is where all four defects lived, is what was under test.
Qt6's QFont::setWeight() now takes a QFont::Weight enum instead of
a raw int, so uic-generated code from the old <weight>75/50</weight>
XML properties fails to compile (worked on GCC via -fpermissive,
but hard error on Apple Clang for the macOS build).
<bold> is already set on all affected widgets, so <weight> was
redundant and can be dropped without any visual change.
Default arguments aren't part of a function's pointer-to-member type, so &Class::slot has a type requiring the argument regardless of its
default value -- incompatible with a signal providing none, and &Class::slot alone won't compile against these signals at all.
When migrating to the modern member pointer connect, replaced with a lambda that calls the slot with no arguments, letting
the default apply exactly as before.
- SelectAutonumW::applyEnable(bool = true), connected to each NumPartEditorW's changed() signal in both setContext() and
on_add_button_clicked(). The corresponding disconnect() in on_remove_button_clicked() is removed rather than reimplemented: a
lambda-based connection can't be matched and removed by a separately-written disconnect() call, and the explicit disconnect
was already redundant -- the very next line deletes the part object, which Qt automatically disconnects on destruction (the same
guarantee setContext()'s own qDeleteAll() cleanup already relies on).
- PartText::adjustItemPosition(int = 0), connected to QTextDocument::contentsChanged().
- ExportDialog::slot_changeFilesExtension(bool = false), connected to ExportPropertiesWidget::formatChanged().
Bug #331: "Il serait intéressant de pouvoir directement dans la fenêtre
'Sélection numérotation auto' modifier la valeur d'incrément et visualiser
la prochaine numérotation qui sera appliquée. Ceci sans être obligé
d'ouvrir la page de configuration."
The dock (AutoNumberingDockWidget) already let you see and edit a rule's
*current* value inline (added in 52c8ef6b4/031710b5f/ee4ba82d2). The
increment itself, and any preview of where the numbering is headed, was
reachable only through Configurer -> the full project-properties dialog.
Two new widgets per row (conductor/element/folio):
- An increment spin box, read from and written to the same NumerotationContext
field NumPartEditorW's increase_spinBox already edits in the full dialog --
same data, second place to reach it.
- A read-only next-value field, computed via
NumerotationContextCommands::next() -- the identical engine the "Suivant"
button in the full dialog already uses to step a whole context. Reusing it
rather than reimplementing the arithmetic means wrap-and-carry between parts
comes out identical to what actually happens when the number is next
consumed, and zero-padding matches real rendering
(NumerotationContext::formatValue(), mirroring
autonum::setSequentialToList()'s padding rule by hand since that function is
local to assignvariables.cpp).
NumerotationContext gains replaceIncrease(index, increase), a sibling to the
existing replaceValue() that touches only the increment field.
Every refresh call site in the file (13 of them) previously refreshed just the
value field; they now go through a new refreshRow(category), which refreshes
value + increment + next-value-preview together via a small per-row widget
bundle (rowFor()). This also let resetAutoNum()'s three-way switch collapse to
one line, and refreshValueFields()'s three near-identical blocks collapse to a
loop -- both existing before this change, not new here.
Verified live under Xvfb: created an element numbering rule "K" (Chiffre 1,
value 1, increment 1) via the full dialog, confirmed the dock showed
Valeur=1/Incrément=1/Suivant=2. Changed the dock's own Incrément to 3 --
Suivant updated live to 4, no dialog needed. Changed Valeur to 10 -- Suivant
became 13. Reopened the full configuration dialog and confirmed it read back
the same value_field=10/increase_spinBox=3, i.e. the round trip through
replaceIncrease()/storeContext() does not disturb type, initial value, modulus
or format.
Builds clean, CMake/Ninja Release, Qt 5.15, 820/820, no new warnings.
Fixes: https://qelectrotech.org/bugtracker/view.php?id=331
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BorderTitleBlock::slot_setAutoPageNum was removed in 471f876 ("Remove unused signal", 2023-10-17) without noticing autonumberingdockwidget.cpp still referenced it via old-style
SIGNAL()/SLOT() macros, which fail silently at runtime instead of producing a compile error. This has remained broken on master ever since; a fix (57572a2, "Fix two broken signal
connections") exists on the unmerged qt6-cmake-elevatormind-merged branch but that one only commented the lines out without solving the underlying issue.
Removed the dead code entirely and call BorderTitleBlock::importTitleBlock() directly on the active diagram in on_m_folio_cb_activated() instead. The same mechanism is already
used elsewhere in the codebase (undo command, new-diagram creation, XML loading) to push TitleBlockProperties into a diagram and trigger a folio numbering recompute via needFolioData().
Reported by @scorpio810 on #626: "The field does not update automatically;
you need to list the other rules for it to update."
Two reasons, both mine:
The refresh was wired to the combo boxes' activated() signal, which Qt
emits only for user interaction. Nothing that changed a context
programmatically -- which is to say, numbering an element -- ever reached
it. Re-picking a rule from the combo was not a workaround so much as the
only code path that refreshed at all.
And there was no signal to hang it on for two of the three categories:
addElementAutoNum() emitted elementAutoNumAdded(), but addConductorAutoNum()
and addFolioAutoNum() emitted nothing, so even a listener would not have
heard a conductor counter advance.
Add QETProject::autoNumContextUpdated(), emitted by all three setters, and
have the dock re-read its three fields on it. Kept deliberately separate
from the existing *AutoNumAdded/*Removed signals: those make listeners
rebuild their rule lists, which is both heavier than needed here and would
disturb the user's current selection every time an element is numbered.
This one only says "re-read me".
The automatic refresh skips a field that has keyboard focus, so numbering
an element cannot overwrite a value half-typed under the cursor. Explicit
refreshes after a reset or an edit still write unconditionally, so the
field always ends up showing the canonical stored value.
Measured, advancing a counter the way numbering advances it and without
touching the combo box:
field before advance "5"
context after advance 6
field after advance "6" (was still "5")
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
Reported by @scorpio810 on #632 with a screenshot: a "Chiffre 1" followed by
a "Cyclique (modulo) 8" numbers elements 0..7 and then jumps straight to 9,
never showing 8, and never producing the 0-7 / 10-17 / 20-27 pattern the
feature exists for.
The cause is that the wrap-and-carry feature shipped without its rendering
half. Commit 68c2603 added the arithmetic and the editor UI across seven
files, none of them assignvariables.*, so there is no %seqw_ variable, no
wrap list in sequentialNumbers, no branch in setSequential(), and no branch
in numerotationContextToFormula(). A cyclic part therefore contributes
nothing to the generated formula and cannot be referenced from one -- it is
invisible.
Invisible but not inert: it still advances and still carries. So the digit
in front of it receives +1 from the carry on top of its own increment, and
the only digit the label does show jumps by two. That is the missing 8.
Add the missing half:
- sequentialNumbers gains a wrap list, handled in the copy constructor,
assignment, comparison, clear(), toXml() and fromXml();
- setSequential() collects wrap parts when the label uses %seqw_;
- assignSequence() substitutes %seqw_N and counts wrap in its bound, so a
context whose only counter is cyclic still resolves;
- numerotationContextToFormula() emits %seqw_N, so adding a Cyclique part
in the editor now puts a token in the formula instead of nothing.
Old projects are unaffected: <wrap> is simply absent from files written
before this, which fromXml() reads as an empty list, and such files have no
cyclic parts to reference in the first place. An older QET reading a newer
file ignores the unknown child.
Measured on the exact configuration from the report, unit + wrap(mod 8):
formula generated %sequ_1%seqw_1 (was %sequ_1 -- wrap contributed none)
carry digit increment 1 00 11 22 33 44 55 66 77 90 101 112 ...
carry digit increment 0 00 01 02 03 04 05 06 07 10 11 ... 17 20 21
The second line is the requested pattern. The first shows what is left once
the rendering is fixed but the carry digit still increments itself as well
as receiving the carry -- worth a UI decision, noted on the PR.
Follows @scorpio810's review on PR #626 and three defects found by finally
running the thing rather than only building it.
Replace the "?" button with an editable value field, as asked for. It shows
the current value of the context's counter -- the last part that actually
progresses, i.e. the least significant digit -- and typing a new value and
committing it writes that value back. This is strictly more useful than the
button it replaces: "?" is still reachable by typing it, and any other value
is now reachable too, which was the point of the request.
It also removes a destructive edge the button had: "reset to ?" rewrote
*every* part, so a scheme built as "K" + counter became "?????" and the
configured prefix was gone for good. There is no undo command for
numbering contexts.
Two bugs fixed in the reset path itself:
- The project was never marked modified. addConductorAutoNum() and friends
are a plain insert into a QMap; they emit nothing and set no dirty flag,
and the properties dialog that this code was modelled on calls
setModified(true) separately afterwards. Without it the user resets a
counter, closes the project, is not asked to save, and the reset is lost.
Verified before the fix: projectWasModified() stayed false across a click.
- A wrap part was reset to "1". A modulo part cycles over [0, modulus) --
the PLC addressing that motivated the feature runs %IX0.0..%IX0.31 -- so
its starting value is 0, not 1.
An empty value field is treated as "no change" rather than as an empty
value, so clearing the box by accident cannot wipe a counter, and the field
is refreshed from the context after every write and whenever the selected
context changes.
Turning the default "Chiffre 1" part into a "Cyclique (modulo)" one left
the modulus spin box at 0, and a modulus of 0 means "no cycle" -- so the
part counted upward forever instead of wrapping, which is the whole point
of the type. Reported on #593 against a modulus-7 test and, more usefully,
against a real April 5000 PLC layout addressed %IX0.0..%IX0.31 per card.
setType() defaulted the modulus to 8 inside the block that installs numeric
behaviour, and that block runs only when the *previous* type was
non-numeric. Switching from one numeric type to another skips it. Since a
fresh part starts out as "Chiffre 1", the ordinary way to reach this
feature -- change the type of the part in front of you -- was exactly the
path that skipped the default. Going the long way round, via "Texte", set
the modulus to 8 and worked, which is why the feature tests fine when you
build the context some other way.
Moved the default out of that block so it applies whatever the part was
before, and made it fire only when the current modulus is unusable, so a
value the user picked on purpose survives switching type away and back.
The wrap/carry arithmetic itself was already correct: with a carry target
in front of it, a modulus-32 part yields %IX0.0..%IX0.31, %IX1.0 as asked.
Saved configurations are untouched -- a stored modulus, including a 0 left
behind by this bug, still loads and round-trips exactly as it was.
Resetting an active numbering counter back to a starting value, or
marking it as needing manual numbering, currently requires the full
round trip through the project properties dialog: open it from the
dock's Configure button, locate the right numbering context, select
the specific part row, clear and retype the value, confirm.
Add two small buttons next to each of the three combo boxes
(Conductor/Element/Folio) on AutoNumberingDockWidget itself:
- Reset to start: calls NumerotationContext::replaceValue() on every
part that represents a progressing counter, using a sensible
per-type value -- the part's own stored initialvalue for
folio-anchored types (unitfolio/tenfolio/hundredfolio), "1" for
plain numeric types and wrap, "a" for alpha. Non-incrementing types
(string, plant, locmach, idfolio, folio, elementline,
elementcolumn, elementprefix) are left untouched, since there's no
meaningful "start" distinct from whatever the user configured for a
fixed/contextual value.
- Reset to "?": sets every part's value to the literal placeholder
"?" unconditionally, for marking a context as needing manual
numbering.
Both write the updated context back via the same
addConductorAutoNum/addElementAutoNum/addFolioAutoNum calls the
project properties dialog itself already uses, so the dock's existing
refresh signals fire exactly as they do today.
Verified with a full build (Qt6) after the change -- clean compile
and link, including the .ui-generated Ui class correctly picking up
the six new button object names. Wasn't able to get a reliable live
GUI run in this environment to click-test the buttons themselves (ran
into unrelated session/display instability before any interaction
with the new buttons occurred), so this is verified by code review
and successful build rather than a runtime screenshot.
Implements https://github.com/qelectrotech/qelectrotech-source-mirror/discussions/597
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".
Implements the first pillar of #574: a "Shortcuts" preferences page letting
users rebind, search and reset every keyboard shortcut in the app.
What it does
- New ShortcutManager singleton: every one of the ~95 setShortcut()/
setShortcuts() call sites across qet.cpp, qetmainwindow.cpp,
elementspanelwidget.cpp, autonumberingdockwidget.cpp, richtexteditor.cpp,
qetdiagrameditor.cpp, qettemplateeditor.cpp and qetelementeditor.cpp now
calls registerAction(target, id, category, default_sequence) instead,
which applies the user's saved override (or the default) and remembers
the target for later editing.
- New ShortcutsConfigPage, added to the existing "Configurer QElectroTech"
dialog: a filterable table of every registered shortcut, grouped by
category, each with a QKeySequenceEdit and a per-row reset button, plus a
"reset all" button. Bindings are only persisted (via
ShortcutManager::setSequence()) when the dialog is accepted.
- Conflict detection: rows whose currently-edited sequence collides with
another row are highlighted with a tooltip naming the conflicting action.
- Overrides are stored under a "shortcuts/" QSettings group, one key per
id, keyed to match the id (not persisted at all when equal to the
hardcoded default), so a future QET version can safely raise a default
for anyone who never customized it.
Design notes
- Targets are handled generically via QObject rather than QAction, since one
call site (autonumberingdockwidget's "Configurer" button) is a
QPushButton, not a QAction. Both declare an identical "shortcut"
QKeySequence Q_PROPERTY, so registerAction() reads/writes it through the
property system instead of needing a separate code path.
- Several live targets can share one id at once -- QET allows multiple
windows of the same kind (diagram editor, element editor...) open
simultaneously, each constructing its own QAction with the same id.
setSequence() updates every live target for that id in one call, so a
rebind takes effect in all open windows immediately, without restart.
- A shortcut's description is captured from its target's text() the first
time that id is registered, then cached -- so the config page stays
correct even after the owning window is closed. One consequence: a
shortcut belonging to an on-demand window (element editor, title block
editor, rich text editor) only appears in the list once that window has
been opened at least once in the current session, since nothing has
registered its id yet otherwise.
Testing
Full CMake build (qmake CONFIG+=no_kf5, Qt 5.15) compiles clean with zero
errors and zero new warnings. Verified end-to-end in a real running session
(Xvfb + xdotool):
- The Shortcuts page appears in Configure QElectroTech with the right icon,
lists every always-registered shortcut with correct category/action name/
current binding.
- The filter box correctly narrows the list, and correctly returns nothing
for an action whose owning window hasn't been constructed yet this
session (confirming the on-demand-registration behavior above is working
as designed, not silently broken).
- Conflict detection correctly flagged a real pre-existing same-key overlap
between "Supprimer" (delete selection, Del) and "Supprimer ce folio"
(delete diagram from panel, Del) -- both highlighted with explanatory
tooltips.
- Rebound "Manuel en ligne" to Ctrl+Shift+M, clicked OK: persisted under
[shortcuts] in QElectroTech.conf, and the Aide menu's entry showed the new
binding immediately, no restart needed.
- Reopened the dialog: the rebind was still shown. Clicked its per-row
reset button, then OK: the settings key was removed entirely (not stored
as "F1"), correctly falling back to the hardcoded default.
Retrofitting the Tab/Shift+Tab, select-all (#585) and Ctrl+G jump-to-element
(#586) shortcuts through this registry is left for a follow-up once those
PRs land, to avoid re-merging still-open branches into this one.
Developed with assistance from Claude (Anthropic).
QVariant::canConvert(int) is deprecated in Qt6. Use the non-deprecated
canConvert<T>() template (canConvert<QString>() / canConvert<int>()),
which is available on Qt5 too. Clears the last 2 -Wdeprecated-
declarations warnings.
When using composite text in report elements, the name of the variable
was displayed when inserting the reportElement into the drawing (e.g.
%{function}). This is corrected here.
Add missing variables to assignvariables.cpp