Following up on the earlier division-by-zero fix: return -1 from the bad denominator branch of minimumWidth(), matching the "no
constraint" convention maximumWidth() already uses, instead of std::numeric_limits<int>::max() or 0. Update
TitleBlockTemplateView::updateDisplayedMinMaxWidth() to skip the "Longueur minimale" line when minimumWidth() reports -1, mirroring
its existing handling of maximumWidth() == -1.
QetLogger (discussion #644, steps 1-3) captures whatever an explicit
qDebug()/qInfo()/qWarning() call already decided to report. Most of a
session -- painting, dragging, a slow synchronous operation -- produces
no log output at all, so a silent multi-second gap in the log is
indistinguishable from the user simply not doing anything. That gap
came up directly: investigating a user-reported "the program lagged"
required inferring stalls from timestamp gaps between unrelated log
lines, which can't tell a real freeze apart from normal idle time.
EventLoopWatchdog closes that gap directly instead of inferring it. A
QTimer::PreciseTimer repeating tick (every 50ms) measures the *actual*
elapsed time since the previous tick via QElapsedTimer (monotonic,
unaffected by system clock/NTP adjustments). Qt does not queue up
missed fires for a normal repeating timer, so if the main thread is
blocked for 600ms, the timer fires once as soon as the loop frees up,
with ~600ms measured since the last tick -- that gap is the stall,
measured at its source. Only logs (via the existing qWarning() path,
so it reuses QetLogger's file/ring/rotation with no new plumbing) when
a tick is late by more than 200ms, so a healthy session produces zero
output from this class, in keeping with QetLogger's bounded-log design.
Same QET_WATCHDOG_DISABLE=1 escape-hatch convention as QetLogger's own
QET_LOG_DISABLE=1.
Deliberately not included: attributing a stall to what caused it. This
tells you a stall happened and how long -- pairing that timestamp with
gdb attached to a running session (as used for the CLI hang, PR #661)
is still how you get from "it stalled" to a root cause.
Stacked on #647 (feature-diagnostic-logging-crash) for QetLogger/
qWarning() plumbing this depends on -- diff includes its commits until
that merges.
Verified against the compiled binary, not just read: temporarily
injected a QThread::msleep(600) via a one-shot QTimer 2s after
startup, confirmed the exact expected warning
("EventLoopWatchdog: main thread stalled for 620 ms") at the right
severity through the real qWarning()/QetLogger path, then removed the
test hook and reconfirmed a normal run produces no output from this
class at all.
Stacked on the steps 1-3 branch (feature-diagnostic-logging, PR #646).
Kept as its own PR rather than folded into that one, matching the
discussion's own framing: step 4 is explicitly "the highest-risk piece
... lands last, behind its own switch."
## Step 4 -- crash-time ring flush (CrashHandler)
Installs a handler for SIGSEGV/SIGABRT/SIGBUS/SIGFPE/SIGILL (POSIX) /
SetUnhandledExceptionFilter (Windows) that flushes the in-memory ring to
a fixed crash_dump.log before the process dies.
This required reworking LogRing (step 3) to be genuinely lock-free, not
just mutex-protected: a signal handler that blocks on a lock the
crashing thread (or another thread) already holds turns a clean crash
into a hang -- no ring dump *and* no core dump, worse than doing
nothing. append() now claims a slot with a single atomic fetch-add;
dumpToFd() reads the preallocated entries directly and writes them with
write(2) only, looping on EINTR/short writes. Accepted tradeoff: at most
one entry can be read torn if a crash lands mid-append into that exact
slot -- documented in logring.h, and the alternative (a seqlock to
detect and retry) wasn't judged worth the complexity for that window.
Other invariants implemented per the discussion:
- sigaltstack with a static 64 KiB buffer, SA_ONSTACK -- a stack-
overflow SIGSEGV has no usable stack for a handler without one.
- Nothing under the actual handler touches Qt, QString or the
allocator: the dump path and a small header (version/git/OS/Qt) are
precomputed into fixed char buffers by install(), which runs once at
startup in normal context.
- Atomic test-and-set so only the first crash writes a dump; a second
concurrent/nested fault goes straight to restore-and-re-raise.
- After writing, the handler restores SIG_DFL and re-raises (POSIX) /
returns EXCEPTION_CONTINUE_SEARCH (Windows) so the OS's own crash
path -- core dump, Windows Error Reporting -- still runs. A handler
that "fixed" the crash by swallowing the signal would destroy exactly
the post-mortem evidence this whole design exists to preserve.
Tested in this environment: POSIX/Linux only, all five signals. Sent
each directly to a running process and confirmed (a) crash_dump.log is
written with the correct header and ring contents, mode 0600, and (b)
the process still terminates via the signal with the kernel's own
"core dumped" flag set (exit code 128+signal, confirmed for all five).
The Windows path is implemented per the discussion's guidance but is
untested -- no Windows build available in this sandbox.
## Step 5 -- getting the data back out
- QETApp::checkCrashDump(), called from checkBackupFiles() only when
there's no stale project file to recover this run (so the two
prompts never both show, per the discussion), offers an unretrieved
crash dump via DiagnosticsReportDialog and then deletes it regardless
of the user's choice -- offered exactly once.
- A new "Aide > Enregistrer un rapport de diagnostic..." action
(QETMainWindow) builds the same kind of report from the *current*
session (QetLogger::buildDiagnosticsReport(): header + this session's
log file) for a manual "attach this to a bug report" flow, not tied
to a crash.
- Both go through QetLogger::redact() before ever reaching the user:
the one redaction implemented is a literal replace of the home
directory with "~", since an absolute path under it leaks the
account name. The discussion's fancier "optionally redact project
filenames too" isn't attempted -- reliably telling a project path
apart from arbitrary log text is a much fuzzier problem than a
literal prefix match.
- DiagnosticsReportDialog shows the full (already-redacted) content
before saving, per the discussion: "the user is about to attach this
to a public tracker."
Verified in a real GUI session (Xvfb): triggered a SIGSEGV, relaunched,
confirmed the crash-report dialog appears with the right header/content,
confirmed it does not reappear on a second relaunch, and confirmed the
manual "Save report" action produces a correctly-formatted report and
saves it to a chosen path.
Built clean, no new warnings.
## Build systems
Registered in both: cmake/qet_compilation_vars.cmake, and
qelectrotech.pro. The .pro needed explicit globs for the new
sources/logging/ui/ subfolder -- sources/logging/*.{h,cpp} was already
globbed, but unlike the other ui/ subfolders that one had no entry of
its own, so diagnosticsreportdialog.{h,cpp} would not have been built
under qmake.
The job trigger was narrowed to push:tags a while back, but the
job-level 'if: github.ref == refs/heads/master' was left in place.
A tag push never has github.ref == refs/heads/master, so the two
conditions are mutually exclusive: the job trigger fires only on
tag pushes, while the guard only allows master-branch refs, meaning
the job has been silently skipped on every run since the trigger
was narrowed.
This is also the likely source of the recent Git LFS bandwidth/
storage overage: while the trigger was still push-to-master (pre-
narrowing), this job ran on nearly every commit and committed a
new QElectroTech.qch (LFS-tracked) each time, via the auto-generated
update-qch PR -- accumulating one LFS object version per run."
ProjectView::initWidgets() called insertSpacing(1, 10) on a QHBoxLayout
that was still empty, inserting past the end of the item list. The
corrupted layout crashed later in QWidget::setLayout() via
QLayoutPrivate::reparentChildWidgets() and QBoxLayout::itemAt().
Use addSpacing(10) instead, which is equivalent for an empty layout.
Never leave a collection folder without a name (replaces #622)
Mark elements-folder with an exclamation-mark, when "qet_directory" is missing or faulty.
The unconditional early return was narrowed to non-directories only, so
that the just-added warning badge could be picked up once setUpData()
resolved m_qet_directory_unreadable asynchronously. But every directory
then called setIcon() on every single data(Qt::DecorationRole) query --
not just once -- and QStandardItem::setIcon() -> setData() emits
dataChanged() unconditionally (QIcon has no equality check to suppress
it). QTreeView handles dataChanged() by recomputing the row's size hint,
which re-enters data() for the same index, calling setIcon() again:
unbounded mutual recursion, confirmed by an isolated reproduction to
overflow the stack in a single frame (100k+ frames) well before the
first paint completes. Matches plc-user's report of a segfault right as
the elements tree begins drawing.
The race the guard was widened for doesn't actually occur:
ElementsCollectionModel only attaches itself to the tree view (the only
way data() becomes reachable) from loadingFinished(), which fires after
the QtConcurrent::map over every item -- this one included -- has
already finished. m_qet_directory_unreadable is therefore always final
before setUpIcon() can run for the first time, so the plain, always-only-
once guard is sufficient and the badge still works correctly.
"Add folio" always appends to the end of the project, ignoring
whatever folio is currently selected in the left panel -- even though
the panel already tracks the selected diagram's position for its
existing move up/down/top actions, and QETProject::addNewDiagram(pos)
already accepts an arbitrary insertion index, pushed as an undoable
AddDiagramCommand (QetGraphicsTableFactory::create() already relies on
this exact mechanism to insert a folio right after a specific one).
Add two new context-menu actions that compute the target position from
the selected diagram's folioIndex() and pass it straight through the
existing machinery -- no changes needed to QETProject or
AddDiagramCommand. New requestForNewDiagramAt/addDiagramToProjectAt
signal/slot pair added alongside the existing
requestForNewDiagram/addDiagramToProject rather than changing it, so
the plain "Add folio" action's append-at-end behavior is untouched.
@plc-user asked (review on #633) for a way to see a broken folder
directly in the tree instead of only on tooltip hover, originally
suggesting a "FixMe: " text prefix on the displayed name. That name is
reused verbatim in dialog titles and status-bar messages elsewhere
(elementscollectionwidget.cpp), so baking a prefix into it would leak
into those too. An icon badge gets the same visibility without
touching the name value.
setUpIcon() overlays a small warning glyph on the folder icon when
m_qet_directory_unreadable is set. Also drop the "already has an icon,
skip" guard for directories specifically: that flag is only known once
the async setUpData()/localName() job completes (QtConcurrent::map), so
without this a directory painted before that finished would have its
plain folder icon cached forever and never pick up the badge.
Implements steps 1-3 of discussion #644 (deliberately not steps 4/5 --
no signal handler / crash flush, no diagnostics UI; see below).
## Step 1 -- fix the existing logger (bugs, no new behavior)
- One QFile handle held open for the whole session under a mutex,
instead of opening and closing the log file on every single message.
- The log directory and the session's date-stamped filename are
resolved exactly once, in the new QetLogger::init() called explicitly
from main() immediately before qInstallMessageHandler() -- not
recomputed per message, so a session that runs past midnight now
stays in one file instead of silently splitting.
- Age-based retention now uses lastModified() instead of lastRead():
opening a log to attach it to a bug report no longer resets its
retention clock.
- stderr and file output both encode UTF-8 explicitly (toUtf8()),
replacing stderr's toLocal8Bit() and the file stream's previously
Qt5/Qt6-inconsistent default encoding.
## Step 2 -- size-capped rotation + hardening
- The previously-unbounded daily file is now capped at 2 MiB and
rotated (kMaxFileBytes/kRotationKeep in QetLogger), keeping
<date>.log plus <date>.1.log .. <date>.4.log; oldest is dropped.
- Each message is truncated to 4 KB with a "...[truncated N bytes]"
marker before it reaches the ring or the file.
- Control characters (newlines, tabs, other non-printables) in message
content are escaped, since much of what QET logs is externally
controlled (file paths, element names, font strings out of a .qet
file) -- left unescaped, an embedded '\n' could forge log lines.
- The log file is refused if a symlink already exists at that path,
and is created/rotated owner-read/write only.
## Step 3 -- in-memory ring buffer
- LogRing (sources/logging/logring.h) is a fixed-capacity, always-on
ring of the last 4096 log lines, preallocated once at construction
(4096 * 512 B = 2 MiB) so append() never allocates. Entries are
stored as plain pre-formatted bytes in fixed-size slots -- the shape
discussion #644 specifies so a *future* crash handler could dump it
with nothing but write(2), even though no such handler exists yet.
Thread-safe via a plain QMutex (the lock-free requirement in the
discussion applies specifically to a signal-handler read path, which
this step doesn't add).
## Escape hatch
QET_LOG_DISABLE=1 in the environment at startup bypasses all of the
above -- no ring, no file, no rotation -- falling back to a minimal,
self-contained stderr passthrough that doesn't share any code with the
new formatting/sanitization path, so it stays usable even if that path
is what's misbehaving.
## Deliberately not included (per the discussion's own phasing)
- No signal handler / crash-time ring flush (step 4) -- the discussion
flags this as the highest-risk piece, explicitly meant to land last
and behind its own switch once the rest is proven.
- No diagnostics export UI (step 5).
- No log categories, session header, repeat collapsing or rate
limiting -- listed under "best practices worth building in", not
part of steps 1-3.
## Testing
Built clean, no new warnings.
Verified with real runs (QT_QPA_PLATFORM=offscreen, isolated HOME):
- Log file created at the expected dataDir()/YYYYMMDD.log path, mode
0600.
- A full startup's worth of real messages (translations, MachineInfo's
system dump, collection loading) written correctly; every one of the
231 lines in one run starts with a proper timestamp -- confirmed the
sanitizer correctly escapes the raw embedded newlines/tabs in
MachineInfo's multi-line CPU/GPU description fields into visible
\n/\t sequences rather than letting them fragment the log.
- QET_LOG_DISABLE=1: zero log files created, stderr still worked via
the independent legacy path.
- Rotation: pre-filled a log to just under the 2 MiB cap, ran a normal
session, confirmed it rotated to <date>.1.log (still 0600) with a
byte-clean split (no truncated/duplicated line at the boundary) and
a fresh <date>.log picked up from the next line.
Placing an auto-numbered element or conductor advances a shared
NumerotationContext counter (QETProject::addConductorAutoNum/
addElementAutoNum) as a side effect that sat entirely outside the undo
stack. Undoing the placement removed the visible number but left the
counter advanced, so every undo of an auto-numbered placement silently
burned a number, with no way to get it back short of a manual reset.
Adds SetAutoNumContextCommand, a small QUndoCommand storing the old/new
NumerotationContext and calling the matching add*AutoNum() setter on
undo()/redo() -- the same shape QPropertyUndoCommand already uses next
to it in ConductorAutoNumerotation::applyText().
Wires it into the two conductor call sites (the static newProperties(),
and numerateNewConductor(), both in ConductorAutoNumerotation) and the
element call site (Element::setUpFormula(), called from
DiagramEventAddElement::addElement() when a new element is dropped onto
a diagram). setUpFormula() now takes an optional parent QUndoCommand;
addElement() calls it before pushing its own undo_object so the counter
change lands in the same undo macro as the element's placement -- one
Ctrl+Z reverts both together, instead of leaving the counter adrift.
The project-properties config dialog's own add*AutoNum() calls (editing
the numbering rule itself, not a side effect of placing something) are
deliberately left untouched, as are the load-time folio-sequential
bookkeeping calls in Diagram::loadElmtFolioSeq()/loadCndFolioSeq() and
the bulk folio-renumbering passes in QETProject -- none of those run as
part of an undoable user gesture.
Implements the scope proposed in discussion #608.
Suggests the element's filename (without its .elmt extension) as the
default save name when exporting to SVG, instead of only defaulting to
the customElementsDir with no filename. Addresses plc-user's review
suggestion on PR #637.
Implements discussion #605. The diagram editor can already export a folio
to SVG; the element editor, where a single .elmt symbol is drawn, had no
export capability at all -- confirmed by grepping its header for "export"
before starting: nothing.
## Renders the live scene, not ElementPictureFactory's cache
The discussion proposed sourcing this from ElementPictureFactory's cached
per-element QPicture (m_pictures_H), the one used for the elements-panel
preview icons. Checked that cache's actual invalidation before building on
it: nothing in the editor ever tells it to drop an entry on edit, and it is
keyed by the element's on-disk uuid. So for any element already previewed
once in the panel, exporting from the cache would silently produce stale
content after any edit; for a brand-new, never-saved element, no entry
would exist at all. Neither is acceptable for a File > Export action a
user expects to reflect what's on screen right now.
Renders ElementScene directly instead, the same way
ExportDialog::generateSvg() already renders the live Diagram for the
diagram editor's own SVG export: no new drawing logic, only a new playback
target (QSvgGenerator instead of the screen), sized to the element's own
content bounds via the existing elementSceneGeometricRect() helper.
## Hotspot cross excluded from the export
ElementScene::drawForeground() draws the red origin/hotspot cross on every
render() call, unconditionally -- it's an editing aid, not part of the
element being drawn, and diagram editor's SVG export has no equivalent
problem since Diagram doesn't draw one. Added a settable
hotspotVisible flag, defaulting to true (the existing editing view is
completely unaffected) and turned off only for the duration of the export
render() call.
## Verified end-to-end via a real Xvfb session, not just a build
Opened a real shipped element (en_60617_05_06_04.elmt, "Phototransistor"),
exported it, and rendered the resulting SVG back to a bitmap with a small
QSvgRenderer-based harness -- pixel-identical in shape to the element as
shown in the editor. Confirmed the file is valid XML and contains no
red/#ff0000 stroke (the hotspot cross did not leak in).
Then the case the whole "render live, not cached" decision was about:
opened the same element, drew a new line with the line tool, and exported
again *without saving*. The new line is present in the exported SVG.
git status on the source .elmt file after both exports shows it completely
untouched -- the export is read-only and reflects live, unsaved editor
state, exactly the property a cache-based implementation would have gotten
wrong.
Built clean, no new warnings.
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.
Fixes https://qelectrotech.org/bugtracker/view.php?id=332
localName() set a non-root folder's label only inside the success path of
loading its qet_directory file. If that load failed -- file missing,
malformed, or unopenable because of the Windows path-encoding problem with
accented characters that plc-user diagnosed on the tracker -- nothing was
set at all, and since a fresh item's text() is null the folder rendered
with a completely blank label. That is the reported symptom.
Resolve the name into a local and always fall back to the folder's own
directory name, so the label is never empty whatever went wrong.
The fallback is applied *after* NamesList::name() rather than passed into
it. This matters: name() returns a caller-supplied fallback before it
reaches its "first available translation" step, so passing m_path in would
replace a perfectly good name in some other language with the raw
directory name. A folder named only in French, viewed under an English
locale, previously showed "Accentué" and must keep doing so.
Falling back on its own would then hide the broken file -- the user sees a
plausible name and never learns there is anything to repair. So a folder
whose qet_directory could not be read now says so in its tooltip, naming
the file, above the collection path that tooltip already carried.
Suggested by plc-user on PR #622. The flag is recorded in localName() and
consumed in setUpData(), because setUpData() assigns the tooltip after
localName() runs and would otherwise discard it.
Only a file-level failure is flagged. A readable qet-directory with no
entry for the current language is not an error; NamesList::name() resolves
that itself and no warning is shown.
Verified on a fixture collection of four folders -- valid, malformed,
missing, and one named only in French:
master this patch
fr-only Accentué Accentué (no warning)
malformed <blank> malformed (warning)
no qet_directory <blank> no_file (warning)
valid Valid Folder Valid Folder (no warning)
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
ElementsPanel and ElementsTreeView already force a fixed light palette
(white base, black text) on themselves, specifically because element
icons are rendered with colors read directly from each .elmt file --
almost always black linework, matching printed-schematic convention --
onto a transparent background. That only stays legible if the row
background is reliably light, regardless of the OS/desktop theme.
But QAbstractItemView paints row backgrounds using its viewport's
palette, not the view widget's own palette. setPalette() on the view
itself doesn't propagate to viewport() in the general case, so under
styles that actually respect the viewport's (unset, therefore
theme-inherited) palette -- e.g. KDE Plasma's Breeze Dark -- the row
background falls through to the app's dark palette while the element
linework is still literal black, making library icons and terminal
symbols invisible.
Apply the same QPalette to viewport() right after setPalette() in both
constructors, so the fix these two classes already clearly intended
actually takes effect under every style.
Fixes https://qelectrotech.org/bugtracker/view.php?id=335
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.
DialogWaiting pumps the event loop while the folios of a project are
built, so a second openAndAddProject() can run to completion nested
inside the first one (drop on another editor window, queued open) and
the plain reset/read counters would then report the wrong numbers.
Replace them with a RAII counting window (FontRestorationScope): the
constructor keeps the enclosing counts aside, the destructor restores
them. The nesting is strictly LIFO - the nested load completes inside
the pump of the outer one - so each load reports exactly its own
numbers, and the early-return paths of openAndAddProject() restore the
outer window automatically.
Suggested by ispyisail in the review of the reporting change.