Third stage of the zipped project file (#1440). A project is saved as a
.qetz when its file name ends in .qetz -- Save As offers it as a second
file type, and --resave, --set-titleblock, the script API's save() and
the live-mode snapshot follow the name too. Saving a .qet project still
writes a .qet; nothing changes unless the user picks .qetz.
Opening recognises a .qetz by its content (a zip), not its name, and
reads it as bytes: text mode would alter it on Windows. The project
document is joined back and read by the same code as a .qet. Crash
backups stay plain XML. Drag and drop, the open dialog and the
numbering import accept .qetz; the database export names its file from
the project's name without assuming ".qet".
Parts are now written without added indentation (one line each): the
symbol definitions are kept as read, whitespace included, and parts
that gained indentation made the next save differ (five forum projects).
Tests: tst_resaveunchanged::containerSavesTheSameProject (four examples
saved as .qetz, then as .qet, equal the straight .qet; pictures checked
to fail without the fix), tst_qetcontainer::noWhitespaceAdded. Outside
ctest: 341 of 341 projects QET can open (examples, bugtracker, GitHub,
forum) give the same .qet through a .qetz; 181.6 MB of .qet became
36.9 MB of .qetz.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
Two defects found while re-testing the cable management after the
upstream merge, plus one change of intent for the numbering question.
The question "define a cable numbering rule now?" kept its answer in
the program settings, so saying no once silenced every project of the
program -- the exact opposite of what it should do. The answer now
travels with the project itself, stored on the <cable_autonums>
element as ask_numbering_rule="false"; a file which does not hold the
answer asks again, and taking the rule away in the project properties
or in the numbering window brings the question back for that project
alone. Read-only projects are no longer asked at all, since there is
nowhere in them to define a rule.
The numbering window could not hand out the first rule of a project:
it drew the rule, worked the numbers out with it, and only wrote the
rule into the project together with those numbers -- while the
planning refused to run on a project which had no rule saved yet.
The counters of a cable rule are kept under its fixed name anyway, so
planning now falls back to that name when the project holds no rule
yet: a rule which is being drawn in the window counts from the moment
it is complete, and the first renumbering of a project works.
Conflicts resolved in 24 files. Notable decisions:
- ico/icon-themes.qrc: upstream's icon folder restructure kept; the 36
cable icon entries re-added under the new qet/qet-dark path scheme
(XML valid, every referenced file exists).
- Toolbar system: upstream's DiagramToolbarSettings now owns the
toolbar filling. The cable tool is registered as
diagrameditor.add_cable (generic group loop) and listed on the
"diagram" toolbar right after the auto-break conductor action,
separated by a divider, exactly like before the merge.
- Paste: the cable lines of a copy are handed to
PasteNumberingImport::push() as extra commands, so paste + cable
lines + the numbering question stay ONE undo step on both paste
paths (DiagramView::paste and DiagramEventAddPaste::commit).
- PDF links: upstream's deterministic link sorting kept together with
the cable cross-reference injector (injectLinkTo -> links list).
- Element renumbering: upstream's ElementAutoNumSchemeCommand::renumber
replaces the old formula-based body of
QETProject::renumberElementsBySchemeTitle; the cable numbering rule
API (cableAutoNum, axis priority, rule name) is kept in full.
- Auto-numbering pages: upstream's scheme management kept, the Câbles
tab added as index 4 (changeToCableTab), both entry points
changeToCable() and changeToElement() kept side by side.
- SelectAutonumW: dirty tracking and the contextEdited signal moved
into upstream's insertPartRow() row API, so single-rule mode, the
rule-removable button and "confirm without change leaves the project
alone" keep working.
- xrefpropertieswidget: upstream's offset clamping kept; the cable
branch (Texte field, Police button, hidden display options) kept;
the stack-overlapping checkbox is hidden for cables.
Verified after the merge: build green (515/515, no errors), ctest
85/86 (only the pre-existing headless tst_menubarkeyboard failure),
offscreen smoke exit 124.
Cables are drawn line objects (toolbar button next to the auto
break/reconnect button, click-move-click, right click cancels), not
components. The type comes from a CSV catalog following the material
list pattern (designation, cores, core colours, editable through the
entry dialog with "Nouvelle entrée" / "Modifier l'entrée").
Each core is bound to the wire its colour label stands on, and the
cable reference is generated per core and written into the Conductor
entry via Conductor::setCableReference(label, cable uuid, slot). The
conductor entry is where it has to live because the terminal strip
plan (Klemmenplan) consumes it in the next step.
Numbering: "Cables" rule under Programmeinstellungen/Neues
Projekt/Nummerierung auto (mirrored in project properties), literal
"W" fallback without a rule, French gate dialog when no rule exists,
Nummerierung menu with the renumber dialog (preferred axis, per-folio
counter, rule and counter state written back, hand-typed names ask,
whole-project scope).
Also included: label block and under-line texts with per-text font and
alignment plus the material-list cell margins, free-core placement and
right-click removal, claim questions for wires of other cables,
multi-selection drag as one undo step, type change with confirmation,
cross-references for type "Cable" (%f-%l%c, own font, clickable links
in exported PDFs), report propagation of the cable definition,
Listes menu (table of contents, material list, terminal strip manager,
terminal generator plugin, cable list), cable list with freely
selectable columns (installation/localisation of cable, start and end,
Blatt start/end, length, used cores...), CSV export dialog with column
pick and preview in the Projekt menu, script API (addTable kind
"cable_list", exportCableList) and a unit test for the type catalog.
A project can now keep the texts drawn in its symbols, and the names of
their terminals, horizontal when a symbol is turned: Project properties >
General, "Garder horizontaux les textes des symboles pivotés". The box of
each text turns with the symbol; the text does not, and reads as it does
in the symbol itself.
It is a project setting, saved as <symbol_texts upright="true"/> and
only when on. A new project starts with it on; a project saved without
it (every existing one) reads with it off, so it looks and saves exactly
as before, and looks the same on every computer. The MCP server's new
projects start with it on too.
It builds on the mirror of #1354, which already redraws the texts of a
mirrored symbol readable: what the symbol does to its texts is now its
mirrors and, with the setting on, its turn (Element::symbolTextsTransform()).
ElementPictureFactory caches one drawing per such transform, terminal
names undo it the same way, and the DXF export places the texts alike.
The fields of a symbol (label, comment...) already keep their angle with
"Garder la rotation visuelle" and are left as they are.
Known limit: two texts stacked in a symbol end up side by side when it is
turned, and can overlap when kept horizontal; the setting can be turned
off for such a project.
Test: tst_uprightsymboltexts turns the symbols of a folio and checks the
angle of each motor's "M" in the DXF, with the setting off and on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Schemas now get saved with a UUID and do not have to be looked up according to the form. They can be renamed and it gets reflected in existing elements. The GUI for
editing them is now much easier, e.g. forms can be reshuffled. A formula can be applied. Many options now also for conductors and sheets.
The limits were per project only. They are now set once in Settings >
General, under the master switch: the most wires per terminal and "one
wire per folio report". Every project follows them.
Project properties > "Conducteurs par borne" gets "Utiliser les réglages
de l'application", ticked by default, showing the application's values
greyed. Unticked, the project keeps its own values, saved in the project
as before, so a drawing that needs its own limit carries it to any
computer. A project's own "no limit" overrides an application limit too.
QETProject::wiringRules() now returns the rules in force (the project's
own, or the application's), so the code that checks a wire is unchanged;
projectWiringRules() returns what the project itself stores. A project
that follows the application still writes nothing, so it saves exactly as
before.
tst_wiringrules: which rules win, the save round trip of a project's own
"no rule", and through the real binary that the application's limit
refuses a wire unless the project overrides it or the master switch is
off (checked to fail with the application's rules ignored). ctest 55/55.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
Discussion #1158: a real terminal takes one or two wires (four with
double ferrules), a folio report is a virtual point that carries one, but
QElectroTech lets any number of wires be connected anywhere.
Project properties > General gets a "Conducteurs par borne" group:
- the most wires a terminal may take (0, the default, is no limit);
- "one wire per folio report".
When set, a new wire that would go past the limit is refused: the
terminal shows red while dragging, as for any refused link, and a tooltip
says why on release. Auto-connect on placing, moving or multi-pasting a
symbol skips full terminals, and qet.addConductor() refuses with its own
message. Wires already drawn, opening, pasting and undo are never
refused.
Settings > General gets a master switch, on by default, that turns every
rule of this feature off in every project; the project group is greyed
with a note while it is off.
The rules live in wiringrules.cpp. The project setting is saved as
<wiring_rules .../> only when a rule is on, so a project that never used
it saves exactly as before; an older version drops it, which means "off".
tst_wiringrules covers the rules, the save round trip and, through the
real binary, that a wire past the limit is refused unless the master
switch is off (checked to fail with the limit check removed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
Where two wires cross without being connected, a project can now draw a
small arc (a hop) on one of them, instead of the user inserting a jump
symbol and splitting the wire. It is a project setting, off by default:
Project properties > Général > "Croisements de conducteurs", with no
hops, hops on horizontal wires, or hops on vertical wires. Choosing the
orientation rather than following drawing order keeps a project
consistent.
Only the drawing changes: no element is added and no wire is split. A
wire that ends or bends on another one is a junction and never hops,
and a crossing too close to the end of a segment for the arc to fit is
drawn plainly. PDF, SVG and image export and printing show the hops,
since they paint through Conductor::paint(); DXF export, which writes
the segments itself, does not.
The setting is saved as <wire_crossings hop="..."/> under the project
root, next to <usage>, and only when hops are on, so a project that
never used them saves exactly as before.
The geometry is in wirehops.cpp, free of any graphics item, and tested
on its own. Conductor::paintedPath() feeds it the conductors of the
folio from a snapshot of their scene points, rebuilt only when a
conductor changes shape, moves, or enters or leaves a folio; each hop
path is cached the same way. Asking the scene for the conductors in a
rect instead strokes every candidate's shape, which made a 366-wire
folio render 0.6 s slower; with the snapshot the difference is within
measurement noise.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Discussion #598, reviving PR #654. The crash-recovery backup written
every 20 minutes went to a single file. If the project was already in a
bad state when a backup ran, that bad state replaced the only recovery
copy.
QETProject now writes the backups in turn to three KAutoSaveFile slots
(BackupGenerations), so one bad write only replaces the oldest
snapshot. Scope is crash recovery only; the opt-in autosave is
unchanged.
After a crash, the recovery prompt groups the snapshots by project and
offers one row per project with a list to pick the snapshot to reopen,
newest selected by default. The snapshots not picked are deleted.
Ported onto current master: writeBackup() keeps the "skip if nothing
changed" check (bugtracker #273) and offerBackupFiles() keeps its place
after the stale-file filter and before the crash report (#901).
Tested with the backup interval shortened to 4 s (test build only):
after three changes, master holds one recovery file, overwritten each
time; this branch holds three, with 4, 5 and 6 folios. After killing
QET, the prompt lists the project; picking the oldest snapshot reopens
4 folios, the default reopens 6. Twice each. ctest 34/34 with and
without KDE Frameworks; the carried-over KAutoSaveFile test passes in
the nokde build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The project database -- behind parts lists, summary tables and the wiring
list -- was filled by walking the built folios: every element and
conductor object of every scene. Filling it from the document the project
was just read from instead is what the database needs before a project
could be opened without building every folio (qelectrotech-docker
DB-FROM-XML-SCOPE.md).
updateDB(document), called by readProjectXml(), fills diagram,
diagram_info, element, element_info, terminal and conductor from the
document, through the code the folios use: a BorderTitleBlock read from
each folio's XML gives the title-block values and each element's grid
cell, the embedded collection gives each definition, and the binders are
shared with the live path. Shapes, texts and pictures still come from the
folios (their boxes need fonts and pens).
It falls back to the folios, saying why in the log, when the document
does not carry what that needs: an item without a saved uuid (older files
-- the folios derive them on load), a conductor naming its ends the older
way, a %autonum folio number, a terminal showing its master's label, a
missing or unbuildable definition. A symbol label computed from a formula
is the one saved in the file, which QElectroTech writes as it computes it
on every save. QET_DATABASE_FROM_FOLIOS=1 forces the folio path.
tst_databasefromdocument saves every example once, then fills both ways
and requires identical tables (24/24, filled from the document each
time), and checks that an older file falls back with its reason. Red when
the document path is made to write a wrong grid cell. Database phase of
loading unchanged: industrial.qet 0.146 s vs 0.160 s, Polonez 0.040 s vs
0.038 s (median of 5).
QETProject::projectWideProperties() is split out of
updateDiagramsFolioData() so both fills use the same title-block context.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A symbol keeps its derived uuid once saved, even when it is moved. A
symbol saved without a uuid that later turns up on the spot it left -- a
hand edit, an older version, another tool writing the file -- derived the
same uuid, and the project had two symbols with one identity.
readDiagramsXml() now collects every symbol and wire uuid the file
carries, on any folio, before a folio loads; derivedItemUuid() moves to
the next counter value while a candidate is among them. The result still
depends on the file alone. Renamed from derivedUuid(), which QETProject
already has for the project's own uuid.
tst_derivedsymboluuid: newcomerOnAMovedSymbolsSpotGetsAnotherUuid fails
with the check switched off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A symbol saved without a uuid got a random one from Element::fromXml()
on every load, and the next save wrote it out: two loads of the same file
gave the same symbol two identities, and anything pointing at it by uuid
(a script, a comparison of two versions, a wire's identity) could not
follow it from one session to the next.
When a folio is loaded, such a symbol now gets a UUID v5 derived from
what it is and where it sits: its type, its position on the folio and its
orientation. Never the folio's index, so inserting or moving a folio does
not change it. Identical symbols stacked on one spot, or a copied folio,
are told apart by a counter kept per project (QETProject::derivedUuid()),
in load order among those symbols alone. A paste still renews uuids.
Symbols that have a uuid in the file keep it. All 24 example projects
already have one for every symbol, so they are unchanged; with the
symbols' uuids stripped, each saves byte-for-byte the same twice (master:
different every time).
tst_derivedsymboluuid runs --resave on a fixture with its uuids stripped:
same uuids on every load, same after a folio is inserted in front, saved
uuids kept, stacked copies differ. The first two fail without this change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
writeBackup() rebuilds the whole project's XML with toXml() on the GUI
thread before handing the file write to a worker thread. On a big project
that freezes the interface for seconds (bugtracker #273, and #329, where
the interval was raised from 2 to 20 minutes to make it rarer). It ran
right after every project was opened, and then every 20 minutes whether
or not anything had changed.
Only write a backup when something changed since the last one: the undo
stack moved, setModified(true) was called, or the embedded element or
title block collections changed. A project just opened from a file starts
clean, since the file is already what a crash would restore. New projects
and projects restored from a backup are backed up at once, as before.
Measured on examples/industrial.qet (50 folios), each backup blocks the
GUI thread for about 0.28 s on a fast machine; with a 2 s test interval,
the old code backed up on every tick, the new code only after a change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
QETProject::m_uuid was created in the constructor and never written, so
a project got a new uuid every time it was opened. Inside a running
instance it is only used to name the SQLite connection, but nothing
outside the instance could tell which project a file belongs to.
Motivation
A .qet file is increasingly handled by tools outside QElectroTech: Git
repositories on GitHub or GitLab, cloud storage, key-value stores,
per-project locks. All of them need a stable key for "this project":
- The file name and path are not stable: files get renamed, moved,
checked out in different places.
- The project title is user-editable and not unique.
- Folio uuids (persisted separately) are only unique within their
project; keying folios globally needs a project identifier as well,
e.g. projects/{projectUuid}/folios/{folioUuid}.
Change
Write the uuid as an attribute of <project> and restore it in
QETProject::openFile(), right after parsing and before the project is
built from the XML. Older versions ignore the attribute, so files stay
readable in both directions.
The project database is not affected: it takes its connection name
from the uuid created at construction (m_uuid is declared before
m_data_base), before the file is read. Two open projects carrying the
same persisted uuid therefore still get distinct connections.
Files without a uuid: why not a random one
Keeping the random uuid created by the constructor and saving it
conflicts with #754 / #779: saving an unmodified project must give the
same bytes every time. Every example project predates the attribute.
Measured on the 24 example projects (resaved 3x each from the same
original, QT_HASH_SEED=0 so that QDom's attribute order is stable,
isolated HOME per run):
upstream master 23/24 byte-identical
persist, random uuid 0/24
persist, derived uuid (this) 23/24
The remaining project, schema_indus.qet, differs only in element uuids,
the known residual #779 leaves for elements; its project uuid is
stable.
Instead, a project file without a uuid gets a name-based (version 5)
uuid derived from the raw content of the file:
QUuid::createUuidV5(<fixed QET project namespace>,
"qet-project-legacy\n" + file content without CR)
- The same file always yields the same uuid, so resaving an unmodified
legacy project stays reproducible.
- Different projects practically never share a uuid, because any
difference in content gives a different one. This is unlike folios,
where only data such as title and position could be used; the raw
file bytes are stable input for the whole project.
- Carriage returns are dropped before hashing. QFile's Text mode already
strips them on Windows but not elsewhere, and git's autocrlf can
change them on checkout; either way the uuid is the same on every
platform.
- The uuid is derived once, at load time, and saved from then on. After
that it is read, never recomputed: renaming the project, editing it
or changing it in the same session as the migration does not change
it.
- Two people opening the same legacy file on different branches get the
same project uuid.
The namespace uuid is fixed in the code and must never change, or every
legacy project would get a different uuid.
Known limitations, open for discussion
- Copies share the uuid. Two byte-identical legacy files get the same
uuid (examples/cablage-eclairages_sikli-v5.qet and
câblage-éclairages-sikli-v5.qet are such a pair), and so does a
migrated file copied in the file manager or saved with "Save as".
That is what identity means for a copy, and the same happens with Git,
but a tool that treats the uuid as globally unique has to cope with
it. Regenerating the uuid on "Save as" could be a follow-up, if that
is the preferred behaviour.
- A legacy file that differs from another only in formatting (e.g.
re-indented) gets a different uuid. The two sides of a merge only
agree if they started from the same bytes, which is the normal case.
Tests (Qt 6.4, offscreen, qelectrotech --resave / --set-titleblock /
--info)
- 24 example projects, 3 resaves each from the same original: results
above; the project uuid is identical across runs. All 24 uuids are
distinct, except the byte-identical pair mentioned above.
- Resaving an already migrated file is byte-identical to the first
output.
- The same legacy file with CRLF line endings gets the same uuid as
with LF.
- Changing the project title in a migrated file keeps its uuid.
- Migrating and modifying in the same run (--set-titleblock on a legacy
file) gives the same uuid as a plain resave.
- Re-indenting a legacy file gives a different uuid (expected).
- A migrated file opened with upstream master loads normally; the
attribute is ignored and dropped on save.
- --info on a migrated file still works.
Refs #754, #779
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDyt4txaott5JyPNGQaeVp
a) using a system provided KF6
b) downloading and compiling KF6
c) using the vendored-in re-creation of the functionality
The behaviour for both Qt5 and Qt6 is steered with the same two variables which were renamed to become version agnostic:
a) BUILD_WITH_KF=ON BUILD_KF=OFF
b) BUILD_WITH_KF=ON BUILD_KF=ON
c) BUILD_WITH_KF=OFF
The version is automatically derived from the chosen Qt major version.
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")
Three new QUndoCommand subclasses (AddDiagramCommand, RemoveDiagramCommand,
MoveDiagramCommand) pushed onto the project's existing (already
project-scoped) undo stack, so folio structure edits are undoable
alongside every item-level edit already on that stack.
- QETProject::addDiagram()/detachDiagram() are the shared attach/detach
primitives: they mutate the diagram list, connect/disconnect the two
per-diagram signals set up at add time, and emit diagramAdded/
diagramRemoved. AddDiagramCommand and RemoveDiagramCommand call these
(via friend access) for both redo and undo, so a removed diagram is
parked rather than destroyed -- it's only actually deleted if the
command itself falls out of undo history while still detached.
- ProjectView reacts to diagramRemoved the same way it already reacted to
diagramAdded (tearing down/rebuilding the tab), so both directions of
both commands go through the same reactive path every other diagram
listener (project database, cross-references, generic panel) already
relies on.
- MoveDiagramCommand wraps a new ProjectView::setDiagramPosition(), which
performs the tab move and the project's diagramOrderChanged() list
reorder synchronously in one step, instead of relying on the queued
tabMoved connection (needed for interactive drag-and-drop) to catch up
later -- avoiding a second, redundant reorder from that queued call.
- Multi-folio delete and multi-folio move (QETDiagramEditor::removeDiagrams()
and the moveDiagram*(QList<Diagram*>) batch slots) wrap their per-diagram
loop in QUndoStack::beginMacro()/endMacro(), so a multi-select action is
one undo step, matching current UX.
- Softened the delete confirmation's "this change is irreversible" wording
now that it no longer is.
Verified headlessly (Xvfb + xdotool + scrot): add/undo/redo, delete/undo/
redo (single and multi-select, single undo step for the batch), and
move/undo/redo all behave correctly against a 7-folio project.
Adds a ProjectUsageTracker (sources/project/) that accumulates how
long a project has been the active tab, using QElapsedTimer so the
value is computed on demand rather than via polling. It's hosted on
ProjectPropertiesHandler per that class's own stated design intent
("all new properties should be managed by this class").
The accumulated time is persisted as a new <usage time_spent="N"
enabled="true|false"/> element, a sibling of <properties> in the
project XML, written/read by new QETProject::writeUsageXml()/
readUsageXml(). It rides along on the existing autosave path for
free, since writeBackup() already serializes the full project via
toXml().
QETDiagramEditor::subWindowActivated() now pauses every open
project's tracker except the one whose tab just became current, so
switching between several open projects keeps each project's tracked
time isolated.
Surfaced in the existing Project Properties "Général" page: a
"Temps passé sur ce projet" display, a "Réinitialiser" button, and
an opt-out checkbox ("uniquement enregistré localement dans ce
fichier" - this is local-only, never transmitted anywhere).
Verified beyond compiling: full CMake build, then an actual runtime
session confirming the saved XML's time_spent value, that closing
and reopening the project round-trips and resumes timing, that the
reset button works, and - the key correctness check - that with two
projects open, the inactive one's time_spent stays frozen while the
active one accumulates real elapsed time, over the same interval.
See discussion #576.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Provide a small KAutoSaveFile-compatible implementation for the no-KF5 build path and use it to keep the existing crash-recovery code active when BUILD_WITH_KF5=OFF.
The normal KF5 build still uses the KDE KAutoSaveFile implementation.
Assisted-by: pi coding agent / Mika (OpenAI GPT-5.5)
writeBackup() fires QtConcurrent::run(QET::writeToFile, ..., &m_backup_file)
fire-and-forget: the QFuture was discarded and nothing kept m_backup_file
alive until the worker finished. If the QETProject was destroyed first, the
worker wrote through the freed member -> use-after-free crash in
QET::writeToFile (intermittent; ~1/6 on short-lived CLI runs).
Store the QFuture and waitForFinished() in ~QETProject (and before
setFilePath() re-points the managed backup file). Also skip launching a new
backup while one is still running, so two threads never write m_backup_file
at once.
The Qt6 path is still a TODO stub and the QtConcurrent block is KF5-only, so
this affects only the Qt5/KF5 build that actually has the backup code.
QETProject schedules an asynchronous crash-recovery backup on construction
(writeBackup() -> QtConcurrent::run(QET::writeToFile, ..., &m_backup_file)).
In one-shot CLI mode the QETProject is destroyed as soon as the command
returns, while that background write still references its m_backup_file
member — an intermittent use-after-free segfault during teardown (~1 in 6
runs; observed on --resave and --set-titleblock).
A crash-recovery backup is meaningless for a short-lived headless command,
so add QETProject::setBackupEnabled(false), called from the CLI entry in
main(). writeBackup() then early-returns, so no background write is ever
launched. Fixes the crash for all CLI commands. See #492.
TerminalStripLayoutPattern class is now a shared pointer between all
terminal strip item.
QETProject have now a new class : ProjectPropertiesHandler
the goal of this class is to manage every kind of properties used in the
project, this class will be strongly used in future.
* terminal_strip:
Terminal strip item can saved / loaded to .qet file
See previous commit...
Move terminal strip drawer class in is own file
Fix wrong use of QStringLiteral and QLatin1String
Double click a TerminalStripItem open the editor
Minor change about checkable QAction of QetDiagramEditor
Minor : corrects a minor aesthetic defect when unbridge terminals
Revamp code
Add and move terminal strip item are now managed by undo command
TerminalStripItem : Draw terminal bridge
Terminal strip item can be added to diagram
Minor : add QGIUtility namespace