Commit Graph

9909 Commits

Author SHA1 Message Date
Laurent Trinques 14c217da34 Merge pull request #1194 from ispyisail/fix/element-nonfinite-coordinates
Fix bugtracker #266: symbol with a nan coordinate breaks the editor
2026-10-01 07:12:27 +02:00
Laurent Trinques 2ea570c8f2 Merge pull request #1192 from ispyisail/fix/editor-resize-zero-undo
Fix bugtracker #266: undo of a resize to zero width does nothing
2026-10-01 07:11:49 +02:00
Laurent Trinques 99422fe56c Merge pull request #1191 from ispyisail/fix/344-one-text-per-potential
Fix bugtracker #344: "one text per potential" now applies at once
2026-10-01 07:09:23 +02:00
ispyisail 75a7e642e0 Fix bugtracker #266: skip shapes with a nan coordinate in the element editor
A symbol with a "nan" or "inf" coordinate (bugtracker #266 had a line
with x1="nan" x2="nan") opened with that shape invisible and the view
off-centre. Moving the selection then snapped NaN through qRound() to
INT_MIN, and saving wrote width="-2147483630", breaking the symbol.

The folio already leaves such a shape out (ElementPictureFactory reads it
with QET::attributeIsAReal()). Do the same in the element editor, and
have --check-elements warn about it. Only geometry attributes are read,
so a text whose content is "inf" is not affected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:21:51 +13:00
ispyisail 5381d05d5d Fix bugtracker #266: undoing a resize to zero width did nothing
Resizing a selection in the element editor down to zero width or height
could not be undone: ScalePartsCommand scales back from the new rect, and
a flat rect cannot be scaled from, so undo silently left the shapes
collapsed and the flattening was saved. The same flat rect made
deltaForRoundScaling() divide 0 by 0 and fill the resize rect with NaN.

Never commit a resize rect with no width or height, and do not resize a
selection that is already flat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:53:02 +13:00
ispyisail 0cf7975be3 Fix bugtracker #344: folio "one text per potential" applies at once
Ticking or unticking "Afficher un texte de potentiel par folio" in
Folio > Properties only assigned Diagram::defaultConductorProperties.
Nothing recalculated the conductor texts, so texts hidden by the option
stayed hidden after it was turned off (and duplicates stayed visible after
it was turned on) until each wire was edited or the project was reopened.
That made the option look broken, which is part of what #344 reports.

When the option changes, reset each conductor's text visibility from its
own properties, then run calculateTextItemPosition() on every conductor of
the folio so the option is applied again from scratch.

Checked in the GUI on examples/industrial.qet folio 4 (26 wires with text,
19 potentials), master vs this commit: unticking now shows the hidden
texts immediately, ticking hides the duplicates immediately; master's
drawing does not change in either direction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:03:18 +13:00
ispyisail 7f799bb425 Merge pull request #1190 from ispyisail/feature/center-on-cursor
Add a Centre on cursor command to move around a zoomed-in folio
2026-10-01 10:57:21 +13:00
ispyisail 18fe429254 Merge pull request #1186 from neitri/master
Russian Translation
2026-10-01 10:48:20 +13:00
ispyisail ec49c7d1ad Russian translation: review fixes
- fill all three Russian plural forms for the PDF page count and the two
  font-recovery messages (lrelease dropped them, so French showed)
- restore the trailing and embedded newlines 12 translations had lost
- translate the remaining French AutoNumberingDockWidget tooltip
- wording: Повернуть %1, Сочетание клавиш / Горячие клавиши,
  Перетаскивание: наклон, горизонтали, Вспомогательное текстовое
  описание 1, со значением, ни при какой ширине

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:47:09 +13:00
ispyisail 0a1ea451ed Merge pull request #1188 from ispyisail/fix/1187-pasted-contact-label
Fix #1187: a contact pasted with its coil loses its label
2026-10-01 10:32:48 +13:00
ispyisail 8587c1785e Merge pull request #1189 from ispyisail/i18n/pt-br-forum-3205
Update the Brazilian Portuguese translation from forum topic 3205
2026-10-01 10:32:27 +13:00
ispyisail 2580163e33 Add a "Centre on cursor" view command
Scrolls the folio so the point under the mouse becomes the centre of the
view, keeping the current zoom. Requested on the forum (topic 3199) for
moving around a zoomed-in folio the way KiCad and EasyEDA allow;
"Zoom sur le contenu" (Ctrl+8) was the closest existing command, but it
also refits the zoom.

The command is in the Affichage menu and in the shortcut editor. It has
no default key: F4 and the single letters are already taken or would
clash with typing. It does nothing when the cursor is not over the view,
so choosing it from the menu is harmless. Near the folio border the view
cannot scroll past the edge, so the point ends up as close to the centre
as the scroll bars allow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:29:34 +13:00
ispyisail ce2b33cd34 Update pt_BR translation from forum topic 3205
wanderson_rds posted a corrected qet_pt_BR.qm on the forum
(https://qelectrotech.org/forum/viewtopic.php?id=3205). The .qm was
decompiled with lconvert and merged into the current qet_pt_BR.ts,
changing only the <translation> of each message, so line locations and
the lupdate layout are untouched.

- 266 strings that had no translation now have one (all plurals too).
- 29 existing translations replaced: "forme" was translated as "form"
  (formulário) instead of "shape" (forma); zero-width spaces removed
  from several "variáveis"; smaller wording and grammar fixes.
- "TextLabel" kept as master had it; vanished entries not touched.

lrelease: 2949 translations and 266 untranslated before,
3215 and 0 after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:06:54 +13:00
ispyisail a3ba364c9d Keep the label of a contact pasted together with its coil (#1187)
A slave pasted with its master is relinked to the master's copy by
initLink() (#997), so the slave data wipe that follows is only needed
for a slave that comes out of it unlinked. Linked, it wiped the
contact's label text while the link stayed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:04:52 +13:00
ispyisail ee781f8dc3 Merge pull request #1183 from ispyisail/fix/headless-settings-dir
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 3m54s
Fix the MCP server's elements_dir on Windows (QET_SETTINGS_DIR)
2026-10-01 09:12:03 +13:00
ispyisail 74664c0bcd Merge pull request #1184 from ispyisail/fix/wiring-list-folio-arrows
Fix the wiring list export dropping wires at folio reports
2026-10-01 09:11:33 +13:00
Mikhail aa893d610d Russian Translation 2026-10-01 00:57:19 +05:00
ispyisail 50b52d50c8 Merge pull request #1182 from ispyisail/fix/element-editor-space-rotates-selection
Fix #1177: Space rotated the selection, not the part being added
2026-10-01 08:43:15 +13:00
ispyisail d66ab752a8 Merge pull request #1181 from ispyisail/fix/windows-crash-report-location
Fix Windows crash reports not saying what crashed or where
2026-10-01 08:42:53 +13:00
ispyisail 367d168c6f Fix #1177: Space rotated the selection, not the part being added
In the symbol editor, Space is the shortcut of the Rotate action, which
rotates the selected parts. The action is enabled whenever something is
selected, and a window shortcut is handled before the key reaches the
scene, so while a terminal or arc was being placed with something already
selected, Space rotated the selection and never reached ESEventAddTerminal /
ESEventAddArc. With nothing selected the action was disabled and the key
fell through, which is why it only happened with a selection.

The scene now emits behaviorChanged() when it enters or leaves AddPart,
and updateAction() keeps the selection actions (rotate, fine rotate, flip,
mirror, cut, delete, depth) disabled while a part is being added. They come
back when the tool ends. The selection itself is left alone.

ESEventInterface::init() also gives the view keyboard focus: a selection
made in the parts list left the focus there, so Space still never reached
the tool.

~ElementScene blocks its signals before deleting the event interface,
whose destructor resets the behavior while the editor is being destroyed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:17:38 +13:00
ispyisail 65a1080cd3 Keep headless runs' settings in a folder of their own (QET_SETTINGS_DIR)
The MCP server gives each QElectroTech run a private HOME and writes the
element collection path for it (elements_dir) into
~/.config/QElectroTech/QElectroTech.conf there. Only Linux reads that
file: Qt keeps settings in the registry on Windows and in the system
preferences on macOS. So on those systems elements_dir did nothing, and
every run read the user's own settings (#1178).

When QET_SETTINGS_DIR names a folder, QElectroTech now keeps its
settings in <folder>/QElectroTech/QElectroTech.ini on every system. It is
set in main() before the first setting is read. Without the variable
nothing changes.

The server sets it for each run and writes the collection path to the
.ini as well as the .conf, so an older QElectroTech keeps working on
Linux. The path is written with forward slashes: Qt reads a backslash in
these files as an escape. The README notes that elements_dir needs this
on Windows and macOS, and that long arguments go on stdin with "-"
(Windows refuses a command line over 32,767 characters).

tst_settingsdir: a script places a symbol that only the folder's settings
can resolve, while the usual settings file points at an empty collection.
It fails on master and passes here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:00:55 +13:00
ispyisail dad99fb08c Say what crashed and where in Windows crash reports
A Windows crash report held the header and the log and nothing else: no
exception, no address, no stack (issue #1179, two reports that could not
be traced). It now also has:

- the exception, with its name ("access violation", "stack overflow"...)
- where it happened, as module+offset, and for an access violation the
  address that was read or written
- a backtrace, one module+offset per frame, walked with the unwind
  tables every x64 image carries (RtlLookupFunctionEntry and
  RtlVirtualUnwind): no dbghelp, no symbols, no allocation. addr2line
  on the same build turns each offset into a function and line.

The dump is written by a reporter thread started at install(), not on
the crashing thread, which may have no stack left or hold the loader
lock. The crashing thread waits for it for at most ten seconds.

Two kinds of crash wrote no report at all before and now do:
- abort() (std::terminate(), a failed assert): no SEH exception, so
  SIGABRT is handled as well.
- qFatal(): Qt ends it with TerminateProcess() on Windows, so the
  message handler now asks for the dump once the message is logged.
  Elsewhere reportFatal() does nothing; SIGABRT follows there.

Tested with a cross-built RelWithDebInfo build under Wine 11, crashing
on purpose from inside the event loop: a null pointer, a call through a
null pointer, a stack overflow, abort() and qFatal(). Each report
resolved to the line that crashed. Linux reports are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 07:40:57 +13:00
Laurent Trinques 2a6155e0c9 Merge pull request #1168 from Kellermorph/prefix-button
Settings: configure the folder prefixes of the user collection
2026-09-30 16:07:18 +02:00
ispyisail 972127aa05 Wiring list export: keep every wire at a folio report, print the folio number
A wire drawn to a folio report waited, keyed by its report, for the wire on
the linked report, in a QMap whose insert() replaces: a second wire on the
same report pushed the first out of the list, and a pair with several wires
on a side was joined by whichever came first. On the shipped examples,
re-saved as the export reads them, 130 of 3,189 wires were missing from the
list (industrial 86, m_000 41, affuteuse_250h 3).

All the report halves are now collected first, and two are joined only
when each report of a linked pair has exactly one wire. With several on a
side the diagram does not say which terminal is wired to which, so each
wire gets its own row ending at the report, as an unmatched half already
did. The result no longer depends on the order the folios are saved in.

The Page column printed each folio's number template ("%id/%total") and
the rows were sorted by it. It now prints the number the folio shows, and
the rows are sorted by the folio's position in the project.

tst_wiringlistexport checks a fixture with one report pair of each kind,
and the page column on every example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:59:49 +13:00
Kellermorph 48aac69391 Merge upstream/master into prefix-button
Only one conflict: the include block of
sources/ui/configpage/generalconfigurationpage.cpp, where upstream adds
editor/terminalnamecheck.h next to the two includes of the prefix
editor. All three are kept. The file list in
cmake/qet_compilation_vars.cmake merged itself this time.
2026-09-30 11:10:02 +02:00
Kellermorph 1d339a8b27 Settings: only write the prefix rows that were edited
Review findings on the prefix editor:

- OK without touching anything changed the file: every row was written
  back, so an explicit <prefix/>, which reads as an empty field and
  cancels the inheritance, was dropped and that folder started
  inheriting its parent's prefix again. A row is now written only when
  it was edited or no longer holds what the file has, and the two kinds
  of empty field look different: hasPrefix() tells an explicit
  <prefix/> from a missing one, which is what the field hint shows.
- load() copied a broken file aside before the user had chosen
  anything, so every "Corriger le fichier" left a .bak behind while the
  message said nothing had been modified. The copy is now made by
  save(), right before the file is replaced: repairing or cancelling
  leaves nothing behind, and a copy that cannot be made stops the write
  rather than destroying the only copy.
- The button followed the combo box only for "Parcourir...": put back
  on "Par defaut" it fell back to the previously saved path instead of
  the default one. The directory of the entry being displayed is now
  used, "Par defaut" being dataDir()/elements/.
2026-09-30 11:01:31 +02:00
ispyisail a1a0a81c1b Merge pull request #1176 from ispyisail/fix/1155-text-anchor-position
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m28s
Fix #1155: Position X/Y of a symbol text follows its alignment
2026-09-30 19:01:55 +13:00
ispyisail bdc1d5878d Position X/Y of a symbol text is the point chosen by its alignment
The text properties showed and edited the top-left corner of a text,
whatever its alignment, and changing the alignment did not move the
text. A right-aligned label next to a symbol therefore needed a
different X for every text length to keep the same gap (#1155).

Position X/Y is now the anchor point: the point of the text chosen by
the alignment (right edge for "right", middle for "centre"...). Typing a
position puts that point there, and changing the alignment moves the
text so that the new anchor point is on the same X/Y. It is the same
point that already stays fixed when the text changes.

Applies to the element text properties on a folio and to the text
field properties in the symbol editor. The saved x/y is still the
top-left corner, so projects and symbols load and save unchanged and
look the same; only the numbers shown for texts that are not top-left
aligned change.

The geometry is in textanchor.h, shared by DiagramTextItem and
PartDynamicTextField, and tested in tst_textanchor.

Not changed: static texts of the symbol editor (PartText), whose
position is tied to the font baseline; texts inside a text group, which
have no position or alignment of their own; rotation still pivots on
the top-left corner (or the centre), not on the anchor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:12:43 +13:00
ispyisail 784a663c19 Merge pull request #1159 from ispyisail/feature/terminal-name-check
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m44s
Add a terminal name check when saving a symbol (#1157)
2026-09-30 15:30:28 +13:00
ispyisail 696783d350 Merge master into feature/terminal-name-check
Resolve the tests/qttest/CMakeLists.txt conflict with tst_configprofile
by keeping both tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:04:37 +13:00
ispyisail b2f0216d04 Merge pull request #1171 from ispyisail/fix/template-placed-under-cursor
Fix templates landing far from the cursor
2026-09-30 14:57:13 +13:00
ispyisail 4a89b6db68 Merge pull request #1172 from ispyisail/feature/color-dialog-double-click
Add double-click to choose a wire colour
2026-09-30 14:56:42 +13:00
ispyisail cb5247e07c Place a template under the cursor, and list it as soon as it is saved
A template (.qetmak) was previewed and placed offset from the cursor by
the position its items had on the folio it was saved from: the preview
pixmap kept that offset, and addMacro() added it back before handing the
position to fromXml(). A template saved from the middle or the lower
right of a folio therefore landed far below and to the right of the
click, often off the folio, which reads as "the template does not drop"
(forum topic 3190). Anchor both the preview and the placement on the
template's own top-left corner instead.

fromXml() skips translating for a null position, so a click on the folio
origin is nudged by a fraction of a pixel that the grid snap removes.

Also:
- a template saved with "Create a template" only appeared in the
  templates tab after reloading the collections or restarting; it is
  now added to the tab of every open editor when it is saved;
- the placement status bar message ended in an untranslated
  "(Makro-Anker)"; it now reads "x : y" like element placement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 13:47:45 +13:00
ispyisail 8b82e348f2 Choose a conductor colour by double-clicking it
In the F2 conductor colour dialog and the toolbar's "Other colour..."
dialog, a double-click on a basic or custom colour swatch now chooses it
and closes the dialog, as OK does (forum topic 3190). Qt's dialog only
selects the swatch on a double-click.

ColorDialogDoubleClick filters the dialog's QWellArray swatch grids,
found by class name once the dialog is shown, because Qt builds them
only then when a native dialog was offered but not shown. Where the
platform's native colour dialog is used instead (macOS, some Linux
desktops) there are no swatches to watch and nothing changes. The
toolbar dialog is now an instance rather than QColorDialog::getColor(),
so the filter can be installed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 13:47:45 +13:00
ispyisail 63196bf4ac Merge pull request #1167 from ispyisail/revive/654-rotating-backups
Keep three crash-recovery snapshots instead of overwriting one
2026-09-30 12:18:26 +13:00
ispyisail a3f10b5959 Merge remote-tracking branch 'upstream/master' into revive/654-rotating-backups
# Conflicts:
#	sources/qetapp.cpp
2026-09-30 11:44:49 +13:00
ispyisail eda9012097 Merge pull request #1169 from ispyisail/revive/499-shift-text-hint
Spell out the Shift-drag hint on symbol texts
2026-09-30 11:37:48 +13:00
ispyisail d698d0366a Merge pull request #1170 from ispyisail/revive/641-cabinet-thumbnails
Add cabinet thumbnails generated from a device's manufacturer and reference
2026-09-30 11:37:23 +13:00
ispyisail 2086270a35 Merge pull request #1166 from ispyisail/revive/640-config-profiles
Add saving and loading the configuration to a file
2026-09-30 11:35:54 +13:00
ispyisail a70e27b4c4 Merge pull request #1165 from ispyisail/revive/691-dialog-geometry
Remember dialog sizes, and drop a light-grey box in dark themes
2026-09-30 11:35:25 +13:00
ispyisail ed1ee0feea Merge pull request #1164 from ispyisail/revive/643-editor-background-frame
Add an adjustable background frame to the symbol editor
2026-09-30 11:34:49 +13:00
ispyisail 327ee11963 Merge pull request #1163 from ispyisail/revive/516-thread-safe-setup-data
Fix the elements panel being updated from worker threads
2026-09-30 11:33:42 +13:00
ispyisail 77b2393d10 Merge pull request #1162 from ispyisail/fix/terminal-strip-dock-use-after-free
Fix a crash when closing a project after its terminal strip window is gone
2026-09-30 11:33:15 +13:00
ispyisail 46598c4ed2 Merge pull request #1161 from ispyisail/fix/wire-nonfinite-values
Reject non-finite values when loading a wire
2026-09-30 11:32:53 +13:00
Kellermorph 03bffa96c7 Merge remote-tracking branch 'upstream/master' into prefix-button
Conflicts in cmake/qet_compilation_vars.cmake only, where upstream added
terminaluuids.cpp/.h next to the qetlabelsfile.cpp/.h this branch adds :
both are kept, qetlabelsfile first to stay alphabetical.
2026-09-30 00:25:14 +02:00
ispyisail aa09d0eb2a Store the thumbnails folder's French name as French, not through tr()
tr() returns the interface language, so an English or German session
would have written its own text under the "fr" name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:15:11 +13:00
ispyisail 6c4ee10f44 Generate cabinet placement thumbnails from manufacturer/reference info
Discussion #602. Right-click a selection holding an element with both a
manufacturer and a manufacturer reference: "Générer une vignette
d'armoire" adds a thumbnail element for each such device to a
"Cabinet thumbnails" folder of the project's embedded collection. The
thumbnail is a 120x30 frame whose dynamic text shows
%{manufacturer} / %{manufacturer_reference}; both values are also in
the definition's element information, so it shows them when placed.
A device already filed there is skipped, so running it again, or on
several copies of one device, adds nothing.

Revives #641, closed 2026-09-10 only to clear a review backlog. Changed
from it: the frame and text are placed around the hotspot (they were
drawn half outside the element), the frame is 120 wide instead of 60
(device names wrapped out of it), the file name goes through
QET::stringToFileName() (a '/' in a reference made a folder), the
version and font come from QetVersion and QETApp, and the action is
left out of the menu when no selected element qualifies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:08:55 +13:00
ispyisail 0f259bafb5 Spell out the Shift-drag hint on symbol texts
Hovering a symbol's text shows its value and the hint "<Shift> to move"
("<Shift> pour déplacer" in French), which does not say what moves: a
plain drag moves the whole symbol, Shift+drag moves the text alone
(DynamicElementTextItem::mousePressEvent).

English: "Hold Shift and drag to move this text on its own".
French: "Maintenir Maj et faire glisser pour déplacer ce texte seul".

Translations only, as for #1124: the source string is unchanged, so the
other languages keep their current text.

Revives #499 (closed 2026-06-11 by its author to test locally first).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:03:52 +13:00
Kellermorph 6ea626fec7 Settings: configure the folder prefixes of the user collection
The prefixes of a collection's folders live in a qet_labels.xml that so
far could only be edited by hand, and a hand-edited file is easy to
break: one extra </category> and the file stops being well formed, which
the lookup answers with "no prefix at all". Labels then degrade
silently, without any error anywhere - the file is simply ignored. Add
a way to edit it from the settings.

- "Configurer les préfixes…" next to the user collection path opens
  PrefixConfigurationDialog, which lists every folder of that
  collection, subfolders included, one line edit each, with "Tout
  déplier"/"Tout replier" and OK/Abbrechen. It writes on OK only :
  cancelling leaves the collection exactly as it was, and an emptied
  field drops the <prefix> again so the folder goes back to inheriting
  its parent's.
- QetLabelsFile owns the reading, scanning, structure building and
  writing of the file. prefixFromLabelFile() moves there unchanged from
  assignvariables.cpp as prefixForPath(), so the lookup used by label
  assignment and the one used by the dialog are one piece of code.
- Entries whose folder no longer exists on disk are offered as
  Conserver or Supprimer and only applied on OK.
- An unparsable file is copied to qet_labels.xml.bak first, and the
  dialog then reports the line and column of the syntax error, with
  "Corriger le fichier" as the default choice - such a file may be one
  forgotten tag away from being valid - and rebuilds the whole
  structure only when "Reconstruire" is picked. A broken file that
  cannot be backed up is refused rather than overwritten.

The scan reads non-hidden directories recursively in name order,
without following symlinks. A collection without any subfolder has
nothing to configure and says so instead of opening an empty dialog.

Tests: a standalone harness kept outside the tree (84 checks :
structure, inheritance, explicit empty prefix, orphan handling, broken
file, wrong root element, broken file whose backup cannot be written,
reload after save) and the dialog driven offscreen (29 checks,
including "reject creates no file"). ctest 27/28, the failing
tst_menubarkeyboard being the headless F10 test, unrelated to this.
The new strings are French source strings like the rest of the code;
the .ts files are left to the translation update.
2026-09-29 23:52:22 +02:00
ispyisail 1dd56df048 Keep three crash-recovery snapshots instead of overwriting one
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>
2026-09-30 10:45:25 +13:00