draw-bezier-curves only exists as SVG in the qet icon theme; on
Debian/Ubuntu the SVG icon engine lives in qt6-svg-plugins, which
--no-install-recommends leaves out. breeze-icon-theme is not needed:
the test uses the qet theme compiled into the binary.
The ubuntu:26.04 container has no icon theme, so
QIcon::fromTheme() returns null icons and tst_qeticons fails
on first CI run. Install breeze-icon-theme so the test runs
against a real theme, as it does on developer machines.
openAndAddProject() shows BackupDialog as a stack object parented to the
editor and exec()s it; every QET::QetMessageBox does the same. exec() runs a
nested event loop, and closing the editor during it turns WA_DeleteOnClose
into a deleteLater() that the nested loop processes: ~QWidget() deletes the
editor's children, the stack-allocated dialog among them, and the process
aborts. Reported on macOS, where File > Quit lives in the application menu
and stays usable while the backup question is up.
The close is now refused while any modal widget is active, and the dialog is
raised so the refused quit is not silent. It is done in QETMainWindow::event()
rather than in closeEvent(), because QETDiagramEditor::closeEvent() starts
closing projects before it decides whether to accept. That covers the diagram
and title-block editors; QETElementEditor is a plain QMainWindow, so its
closeEvent() calls the same helper before canClose(), which itself opens a
modal. QETApp::quitQET() needs nothing: closeEveryEditor() goes through each
editor's close(), and quitQET() already only quits when every close succeeded.
Rejected alternatives, both suggested on the issue:
- Giving the dialog no parent stops the abort but not the deletion. One
caller of openAndAddProject() is the editor's own constructor, which goes
on to open the next file and call slot_updateActions() on this -- a loud
abort would become a silent use-after-free.
- Guarding only QETApp::closeEveryEditor(), which I first recommended on the
issue, misses the reported route entirely: File > Quit is connected to
QETDiagramEditor::close(), not to quitQET().
Verified on Linux, where there is nothing to click (the menu bar belongs to
the blocked window, and Qt ignores window-manager close requests for it), by
calling close() from gdb while the dialog's loop was running -- both
QETApp::quitQET() and QWidget::close() on the editor. Unfixed, both abort
with "free(): invalid size" in QObjectPrivate::deleteChildren() under
~QETDiagramEditor(), matching the report frame for frame; fixed, close()
returns false, the editor and the dialog stay up, and after answering the
dialog Ctrl+Q exits normally. The element-editor guard is the same helper
but was not exercised separately.
tests/modal-quit-regression/ turns that into a gate: it breaks on
QDialog::exec(), interrupts inside the nested loop, calls quitQET() and
checks the process survives. It matches no window titles (translated) and no
window ids, runs on the offscreen platform, and needs only gdb with Python.
Checked both ways: exit 1 with the backtrace above on a build without this
change, exit 0 with it.
ctest 8/8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
When duplicating a diagram page, copied slave elements retain stale
data from the source: labels, descriptions, link references, and PLC
master information (type, address, function, comment, cross-ref, timer
values) remain in the copies.
Fix by adding clearPendingLinks() to Element to prevent copies from
linking back to source elements via stale UUIDs, and by cleaning up
copied element data after fromXml():
- Slaves always lose their label, formula, comment, location, and
PLC master data, since their text comes from a master element which
is not available on the copy. The displayed text on slave elements
is cleared directly via setPlainText(), but UserText items (free
text typed by the user) are preserved.
- For PLC slaves, setElementInformations() is called without
m_block_alignment so that elementInfoChanged() can run
finishAlignment() to correctly adjust text positions for the
cleared content.
- Non-slave elements respect the existing erase-label-on-copy
preference, same as PasteDiagramCommand::redo().
- Conductor labels are also reset when erase-label-on-copy is
active, matching PasteDiagramCommand::redo() (issue #413).
- Text alignment is preserved by wrapping setElementInformations()
with m_block_alignment for non-slave elements, same as
Element::fromXml().
Three weaknesses, all visible in ChuckNr11's report on #898 -- "the report
appeared only once despite there being 10 or more crashes".
One dump per run instead of one per install
-------------------------------------------
crashDumpPath() was a single fixed crash_dump.log, and the handler opens it
O_TRUNC, so each crash destroyed the evidence from the one before. Ten
crashes left one dump. Dumps now go to a crashes/ directory named
crash_<timestamp>_<pid>.log, and every pending one is offered together,
newest first, with a banner saying how many there are. A crash that repeats
is exactly the case where the earlier dumps matter, because the difference
between them is the evidence.
The name is built in normal context and handed to CrashHandler::install(),
which copies it into a preallocated buffer as before -- the handler still
writes to one fixed path, so its no-allocation invariant is untouched.
The dump now says which signal fired
------------------------------------
The header is built once at install(), so every dump looked identical no
matter what killed the process -- and SIGSEGV and SIGABRT point at very
different bugs. Written with an async-signal-safe integer formatter into a
stack buffer, since snprintf is not on the POSIX safe list.
...and where it was
-------------------
The ring said what the program was doing; nothing said where it died. The
dump now carries a backtrace. backtrace() is warmed once in install() so
its first-call lazy resolution cannot allocate inside the handler, and
backtrace_symbols_fd() writes straight to the fd -- unlike
backtrace_symbols(), which mallocs and must never be used here. Guarded on
__has_include(<execinfo.h>) so platforms without it are unaffected.
QET's own frames currently resolve as offsets rather than names, since the
binary does not export its dynamic symbols. They are still resolvable
offline: the header records the exact git SHA. Building with -rdynamic
would give names directly, but that is a build-flag decision for its own
change.
Deliberately unchanged: the four invariants in crashhandler.h. Nothing
added here allocates, blocks, takes a lock, or swallows the crash.
Verified: three consecutive SIGSEGVs now leave three separate dumps, each
carrying "Signal: 11" and a backtrace with resolved Qt frames; launching
afterwards offers all three in one dialog, newest first, and clears them
once shown. ctest 8/8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
QETApp::checkBackupFiles() only reached checkCrashDump() when there was
nothing to recover:
if (stale_files.isEmpty()) {
checkCrashDump();
return;
}
A crash with a project open always leaves a stale KAutoSaveFile, so on the
next launch the recovery prompt won every time and the dump sat on disk
unoffered -- the report was unreachable in exactly the case it is most
wanted. Reported by ChuckNr11 as a side note in #898: "the report appeared
only once despite there being 10 or more crashes". It appears on the runs
that happen to have nothing to recover.
Discussion #644 step 5 asks that the two prompts never show at the same
time, which this keeps: the recovery prompt is answered first, then the
report. The recovery half moves into offerBackupFiles() so both paths fall
through to the same place.
Verified under Xvfb, from a real crash state (SIGABRT with a project open,
leaving both a stale file and a 5.4 KB crash_dump.log): the recovery prompt
appears, and dismissing it now brings up "Rapport de plantage" carrying the
version, git SHA, OS, Qt version and the log ring. Before this change the
report never appeared -- that half rests on the four lines above rather
than on a captured before/after, since re-creating the crash state for a
clean baseline run kept consuming it.
Not addressed: the dump is a single fixed path opened O_TRUNC, so
consecutive crashes still overwrite one another.
ctest 8/8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a) paste
- conductor does not move
- pressing escape does not abort
- pressing anything during the process crashes the program instead of aborting
b) similar issue with the escape not working fixed for graphics items, texxt fields (and probably others)
Refs discussion #886: a saved-report manager built on custom SQL and
nomenclature.json. Most of what was asked for already existed --
Projet > Exporter au format CSV already builds/saves/reuses named
SELECT queries via ElementQueryWidget and nomenclature.json, and
Projet > Ajouter une nomenclature already inserts one into a folio.
This fills the three real gaps.
- projectDataBase::isReadOnlySelect() rejects anything that isn't a
single SELECT/WITH statement. Checked in newQuery() itself, the one
choke point every query path already goes through -- including a
query loaded from a saved nomenclature/summary table's <query>
element on project open, not just the dialog's own custom-SQL box.
ElementQueryWidget shows the same check live as you type, and
BOMExportDialog surfaces it before running or exporting anything.
- BOMExportDialog gains a Preview button + table (QSqlQueryModel),
so a report can be checked on screen before committing to a CSV file.
- ElementQueryWidget gains Importer.../Exporter... buttons that
read/write nomenclature.json's saved reports as a JSON file, so a
report can be handed to a colleague or another install. Import asks
before overwriting a locally-saved report of the same name.
Verified: Qt 6.10.2, builds clean, ctest 8/8. Drove the real dialog
through Xvfb: typed "DROP TABLE element" into the custom-SQL box and
got the inline warning immediately, then confirmed Preview also
refuses it with a "Requête refusée" dialog rather than running it.
Preview against the real default query returned live column headers
and a row. Export opens a save dialog without crashing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Another exception: one non-converted code path in projectprintwindow.cpp to be followed-up.
One issue found in the Qt6 code path of diagramview.cpp which has been fixed.
Addresses scorpio810's review of PR #890 (bugtracker #734):
- The four cas "3"/"4" bridge-skip guards now compare qRound()ed
coordinates, matching how the bridge coordinate itself is computed --
an exact != would miss a pair already grid-equal after rounding but
off by a sub-pixel remainder, and still route a degenerate bridge for
it. Verified no behavior change on the shipped corpus: per-file
self-retrace counts are identical before/after (every coordinate
there already lands exactly on-grid).
- Added tests/qttest/tst_conductorselfretrace.cpp, fixture
qet_bug_repro_resaved.qet (the report's own canonical reproduction):
exports it via the built binary's --export-svg and asserts no
conductor path is self-retracing. Confirmed it actually catches the
regression, not just passes vacuously -- reverted conductor.cpp to
master and reran: fails, 1 self-retracing path found.
Not changed in code: the cas "4" descending-branch dead-code note (kept
for symmetry, as already agreed) and the schema_unifilaire_voltaique2.qet
trade-off (flagged for the reviewer's own visual check).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
update_translations dropped 3 .ui files' <location> entries this run
(graphicstablepropertieseditor.ui, inditextpropertieswidget.ui,
projectdbmodelpropertieswidget.ui), marking ~38-44 existing translations
per language as vanished -- lrelease drops those, so merging would have
lost real, in-use translations in about twenty languages. Cause not
yet identified.
scorpio810 will run lupdate separately after merge, so the 37 new
scripting strings get picked up without this loss and without
conflicting with translation work in progress.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses scorpio810's review of PR #891:
- addElement() now imports the element into the project's own embedded
collection first (QETProject::importElement()), same as the
drag-from-collection-panel path -- otherwise the saved .qet referenced
a definition outside the project, missing on another machine. Also
found and fixed while wiring this up: ElementsLocation::setPath()
forces any path to embed:// once a non-null project is passed, so the
unconditional ElementsLocation(locationPath, m_project) this method
used before silently broke every common://custom:// call. Refuses
on an import-collision case that would otherwise reach
QETProject::importElement()'s own modal ImportElementDialog, with
nobody there to answer it in a script.
- addElement()/setElementPosition()/moveElement()/deleteElement() refuse
on a read-only project, matching their GUI equivalents.
- save() goes through QETProject::write() when no path is given (read
-only handling, saveddate/savedtime, QSaveFile via writeXmlFile()) and
QET::writeXmlFile() directly for an explicit output path, instead of a
plain QFile that skipped all of that.
- Interactive "Run Script...": exports briefly disable project backups
around the temporary QETProject CLIExport::run() opens on the same
file, so it doesn't race the real open project's own KAutoSaveFile.
Restored right after -- the headless --run path is untouched, since it
depends on backups staying off for the whole run.
- A watchdog thread now calls QJSEngine::setInterrupted() after 30s,
so a runaway script (`while(true){}`) can't freeze the GUI or hang a
CI job forever. First version used sleep_for() and blocked every run,
fast ones included, for the full 30s on join() -- caught by testing a
one-line script, fixed with wait_for() on a condition variable so a
script that finishes early wakes the watchdog immediately.
- Interactive script errors now also show a QetMessageBox, not just
stderr (invisible on Windows).
- Ran update_translations (lupdate) to pick up the 37 strings this
feature had not yet added to the .ts files.
Not applied: wrapping the whole script run in one undo macro. It would
make the script's own qet.undo()/qet.redo() calls silent no-ops for the
run's duration -- QUndoStack ignores undo()/redo() while a macro is
open -- which would break that already-shipped, explicitly requested
capability to get one convenience Ctrl+Z instead.
Verified: Qt 6.10.2, builds clean, ctest 6/6. Ran each fix against a
real project: addElement() on a common:// path now succeeds and the
saved file embeds the definition (embed://import/...); read-only
project refuses addElement(); save("") and save(otherpath) both
produce a correctly embedded file; `while(true){}` under --run is
interrupted at 30s where it previously hung forever, and a normal
script now exits in ~0.3s instead of blocking for the full timeout
budget.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Add !parentGroup() guard to stale xref removal in updateXref()
to prevent deleting xref data that ElementTextItemGroup::updateXref()
just stored for a grouped text item.
- Replace QStringLiteral("xref") with QETInformation::ELMT_XREF
in linkelementcommand.cpp and masterelement.cpp.
- Fix indentation in plclinkwidget.cpp.