Commit Graph

10598 Commits

Author SHA1 Message Date
ispyisail f49abcc6cc Fix a deleted symbol's joining wire getting a random uuid on every run
Deleting a symbol whose terminal held several wires joins the wires
left behind with a new one. That wire got a random uuid, so the same
deletion saved a different file every time: a diff for nothing in a
project kept in git (discussion #1512), and two copies with the same
edit could not be compared.

Its uuid now comes from its two ends (each end's symbol and terminal,
sorted), the recipe a wire saved without a uuid already gets on load
(Diagram::fromXml(), derivedItemUuid()). Undo and redo keep the same
wire, so its uuid too.

tst_joinedwireuuid deletes the coil KA1 of examples/affuteuse_250h.qet
in two runs: both saves are identical and no wire has a random uuid.
Without the fix one wire has a random uuid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
2026-10-18 10:51:16 +13:00
ispyisail 732017b015 Merge pull request #1517 from ispyisail/fix/apptest-standardpaths-testmode
Fix app tests hanging on an autosave and rewriting the user's files
2026-10-18 10:02:27 +13:00
ispyisail eccf11d792 Merge pull request #1516 from ispyisail/fix/terminal-orientation-empty
Fix a crash opening a project whose symbol has a terminal with no orientation
2026-10-18 10:01:47 +13:00
ispyisail ea0d86e8f4 Merge master (0b0c20fdb: realistic drawer in the CMake sources) into fix/terminal-orientation-empty
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
2026-10-18 09:01:04 +13:00
ispyisail 0a0809d7f4 Tests: keep every app test out of the user's standard folders
QetAppTest::run() moved settings, configuration and data into a
temporary folder, but QStandardPaths still pointed at the user's own.
Two things follow on a developer's machine:

- qet-assistant.json, live-mode token included, is rewritten by every
  app test as its editor opens.
- A .qetautosave file left in the user's data folder makes QETApp offer
  to restore it in a modal dialog at start. Offscreen nobody answers,
  so all six app tests hang until ctest times them out.

Test mode is now enabled first thing in run(). tst_assistantinfopaths
(#1513) did this in its own main(); it goes back to QET_APP_TEST_MAIN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:33:17 +13:00
Laurent Trinques 0b0c20fdb5 Terminal strip: add the realistic drawer to the CMake sources 2026-10-17 21:32:12 +02:00
ispyisail 108f49443d Merge pull request #1513 from DaTweety/fix/assistant-info-collection-paths
Write qet-assistant.json after the settings dialog applies the paths
2026-10-18 08:25:51 +13:00
ispyisail ae151b350a Fix a crash opening a project whose symbol has a terminal with no orientation
A terminal in a symbol's definition saved without its orientation
attribute crashed QElectroTech whenever the project was opened, in the
GUI and with --resave alike: Qet::orientationFromString() read the
first character of an empty string. Hand-edited or generated symbols,
or a damaged file, can hold such a terminal.

An empty string is now an inconsistency like any other, and the terminal
faces north, as the function's comment already said. Every non-empty
string gives the same orientation as before.

tst_terminalnoorientation runs the real binary's --resave on
fixtures/terminal_no_orientation.qet (unlinked_contact_label.qet with one
terminal's orientation removed): it crashes without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
2026-10-18 08:04:13 +13:00
Laurent Trinques 3d2da9ed4e Terminal strip: specific drawings of fuse, disconnect and test terminals
In the realistic styles, some types of terminal get the parts seen on
the real products:
- fuse terminal: black hinged fuse holder on the middle of the terminal,
  with its hinge pin, a window on the fuse, a ribbed grip and, for a
  fuse terminal with led, a red led
- sectional (disconnect) terminal: orange knife with its hinge and grip
- test terminal: two brass test sockets between the wire entries and
  the middle band (in the middle band for a terminal with levels)

The marker tag and the type symbol are drawn on the lever. The lever is
shorter with screw clamps and stays on the middle band for a terminal
with levels.
2026-10-17 20:56:23 +02:00
Laurent Trinques fed204eb78 Terminal strip: clickable terminals in the realistic styles
The realistic styles don't write the xrefs, so each terminal becomes a
clickable zone, like the xref text of the schematic style:
- double-click on a terminal goes to its element on the folio
- the terminal under the mouse is outlined in blue, and a tool tip
  shows its label and its xref
- the zones are exported as clickable links in the PDF

A terminal with one level is clickable on its whole body; a terminal
with levels is clickable on the rows of wire entries of each level.
Only the terminals with an xref (element placed on a folio) are
clickable. The zones use the same physical / real indexes as the
schematic style, so the double-click and the PDF export need no
other change.
2026-10-17 20:41:25 +02:00
Laurent Trinques 34557928d1 Terminal strip: realistic drawing styles and separator plates
Add two realistic drawing styles for the terminal strip plan, besides
the existing schematic one:
- Realistic (push-in) and Realistic (screw): the strip seen from above,
  at scale (real width of each terminal), like a cabinet mounting
  drawing: end stops, group marker carrier with the strip name, one row
  of wire entries per level, bridge channels with red bridges, marker
  tags with the terminal number, type symbol (fuse, LED, diode,
  sectional, ground...), green/yellow PE and terminal color.

The style is chosen for each plan in the "Add terminal strip plan"
dialog and saved with the item (attribute draw_style of
terminal_strip_item). Items saved before keep the schematic style.
Several plans of the same strip can use different styles.

The values used by the realistic styles are in the layout (scale in
px/mm, terminal length in mm, end stops, group marker) and can be set
in the layout editor, whose style combo box only changes the preview.

Separator plates: a new "Separator plate after" check box column in
the terminal strip editor, stored in the terminal element informations
(key terminal_separator). Drawn as a plate at scale in the realistic
styles and as a thick line in the schematic style.

New files: realisticterminalstripdrawer.{h,cpp} (realistic drawing) and
terminalstripdrawerhelpers.h (helpers shared with the schematic drawer).
2026-10-17 20:21:35 +02:00
ispyisail f4dcbcc366 Merge pull request #1515 from ispyisail/test/lazy-save-parity
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m54s
Test that saving with lazy folios equals saving with every folio built
2026-10-18 06:00:12 +13:00
ispyisail c13595f8a4 Merge pull request #1514 from ispyisail/fix/lazy-table-chain-folio-removed
Fix a table losing its query when a folio is removed with lazy folios
2026-10-18 05:59:34 +13:00
DaTweety a8f70fe071 Write qet-assistant.json after the settings dialog applies the paths
GeneralConfigurationPage::applyConf() wrote qet-assistant.json before it
applied the collection and title block paths, so the file kept the paths
the dialog had just replaced. The qet MCP server hands the user and
company collections from that file to every headless run it starts: a
company collection chosen in the dialog stayed unknown there, and
company:// paths failed to resolve until QElectroTech was started again.

Write the file last. tst_assistantinfopaths picks a new company and user
collection on the page, applies it and reads the file back; it runs in
QStandardPaths test mode from main() on, since QETApp writes the file to
the standard data folder as its editor opens, whatever --data-dir says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:27:42 +02:00
ispyisail bc6da100d3 Fix a table losing its query when a folio is removed with lazy folios
With lazy folios, removing a folio left the tables on folios not built
yet as the file had them. A table split over several folios keeps its
query in its first part; when the folio holding that part was removed,
the next part, built later from the file, named a part that was gone and
was saved with no query. A parts list that listed the removed folio's
symbols also kept its old size. With every folio built neither happens.

Removing a folio that holds a table or a symbol now builds the folios
holding tables first, while the removed folio is still in the project.
A folio with neither builds nothing, as tst_lazyfolios checks.

buildFolios() now holds the project database's update signal back and
sends it once at the end, as pasting does: without that every item built
made every table lay itself out again, and the save after a removal on a
50-folio project took about 270 s instead of 4.5 s. The rows are still
written as each item is built, so no full refill of the database is
needed (one per build made a 136-folio project's edit 20 % slower).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
2026-10-18 00:53:54 +13:00
Laurent Trinques 894f9e39c0 Update translations files 2026-10-17 13:25:20 +02:00
Laurent Trinques 6ec9108ac3 Update translations files 2026-10-17 13:02:48 +02:00
Laurent Trinques 04f48d1de4 Update EN translations files 2026-10-17 12:36:02 +02:00
ispyisail 61bf2e1afa Test that saving with lazy folios equals saving with every folio built
The same 48 edits (industrial.qet and affuteuse_250h.qet, as .qet and
.qetz) are made with lazy folios and with QET_EAGER_FOLIOS=1; the saved
files must be equal but for the saved date and path. Answers the question
raised on #1440 about the hand-kept list of what marks a kept folio stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:33:35 +13:00
Laurent Trinques 391ed4a252 Complete the Italian translation (qet_it.ts)
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m3s
Translate the 2660 untranslated strings of qet_it.ts, including 91
plural forms (singular and plural), so the file no longer has any
unfinished entry.

- Reuse the terms already used in the file (pagina, elemento,
  conduttore, terminale, morsetto/morsettiera, cavo, progetto,
  cartiglio, collezione, riferimento incrociato, potenziale,
  Installazione (=) / Posizione (+)); new cable terms: anima (core),
  schermo (shield).
- Style editor colours translated as descriptive names, like the
  existing ones.
- Keep placeholders (%1, %n, %{...}), HTML tags, accelerators and
  line breaks.
- The "unfinished" drafts were retranslated from the current source
  text.
- PlcLinkWidget: the English source "Log in" is translated as
  "Collega" (link), which matches the context.
- Checked with lrelease: 4137 translations, none unfinished.

Machine-assisted translation: a review by a native speaker is welcome.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 08:49:49 +02:00
Laurent Trinques 10ba853b28 Complete the Simplified Chinese translation (qet_zh.ts)
Translate the 1378 untranslated strings of qet_zh.ts, including 82
plural forms, so the file no longer has any unfinished entry.

- Reuse the terms already used in the file (图页, 元件, 导线, 端子排,
  线缆, 工程, 标题栏, 库, 交叉引用, 电势); new cable terms: 芯线
  (core), 屏蔽 (shield).
- Keep placeholders (%1, %n), HTML tags, accelerators and line breaks.
- The 134 "unfinished" drafts were retranslated from the current
  source text.
- Checked with lrelease: 4137 translations, none unfinished.

Machine-assisted translation: a review by a native speaker is welcome.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 08:23:43 +02:00
Laurent Trinques b1cd3a8a6e Terminal strip: marker tags calibrated on the longest number of the strip
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m39s
Every marker tag of a strip has the length of its longest terminal
number (at least two digits), so three-digit numbers are no longer
reduced. The font is still reduced if a number doesn't fit the text
zone of the layout.
2026-10-16 16:42:26 +02:00
Laurent Trinques 5a60dac2aa Terminal strip: xref as plain text, same size for every marker tag
- The xref is drawn as plain text, without marker tag.
- Every marker tag of the terminal numbers has the same length
  (two digits), the font is reduced when the number is longer.
- The body of every terminal reserves the zone of the longest xref
  of the strip, so all terminals of a strip are drawn the same way.
2026-10-16 16:36:12 +02:00
Laurent Trinques bdd3b20500 Update windows-build.yml 2026-10-16 13:38:35 +02:00
Laurent Trinques d2c2a09dae Terminal strip drawer: draw terminals like a real terminal block
- Draw only the terminal number (part after the last ':'), the strip
  name is already in the header; the manager and the tree still show
  the full label.
- Clip texts to the width of their terminal and shrink the font in
  narrow terminals so they no longer overlap.
- New housing: body with narrower wire entry caps and a slope between
  them, grey by default (terminals with a color or a ground type keep
  theirs); stripes are clipped to this shape.
- White marker tags behind the number and the cross reference, fitted
  to the text and its alignment; black ribbed tags for isolating
  (sectional) terminals.
- Clamping parts (screw in a window, castle tab, big center circle, tab,
  screw) drawn in the free zone between the two tags, whatever the
  layout; fewer parts if the zone is small.
- Remove the wire entry mark that overlapped the cross reference text.
2026-10-16 13:31:56 +02:00
Laurent Trinques 7d87c0e789 Terminal strip drawer: show only the terminal number, fit texts to cells
- Draw only the terminal number (the part after the last ':') instead of
  the full label (e.g. "1" instead of "XAUZ1:1"); the terminal manager
  and tree still show the full label.
- Clip each text to the width of its terminal and shrink the font in
  narrow terminals so that texts no longer overlap their neighbours.
- Remove the wire entry mark in the lower part of the terminal body,
  which overlapped the cross reference text.
2026-10-16 12:43:16 +02:00
Laurent Trinques 3365817cf1 Terminal catalog: more manufacturer widths and manufacturer filter
Extend the terminal catalog (terminalcatalog.h) with the widths of
more terminal blocks, from the catalogs of Phoenix Contact (UT),
Weidmuller (WDU), WAGO (TOPJOB S 2002/2006), ABB Entrelec (M series)
and Legrand (Viking 3). For Legrand and ABB Entrelec the value is the
pitch given by the catalog.

Each entry now carries its manufacturer, and the element properties
dialog gets a "Manufacturer" combo box before the width one. The list
of widths is filtered by manufacturer and by the selected section; if
the manufacturer has nothing for that section, all its terminals are
listed. The combo box is created by the code, no change in the .ui.
2026-10-16 12:10:30 +02:00
Laurent Trinques 5e28ec4246 Terminal sections: show the AWG equivalent in the section lists
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m42s
The section combo boxes of the terminal strip editor and of the
element editor now show the nearest AWG / kcmil size next to the
section in mm2, for example "2.5 mm² (AWG 14)".

The correspondence is approximate (the two systems don't have the
same steps) and is stored in TerminalCatalog::awgForSection().
The stored value is unchanged (terminal_section, in mm2).
2026-10-16 07:57:00 +02:00
Laurent Trinques 5cbc1c4b8d Element editor: add section, width and color to terminal elements
Terminal block elements can now define, in the element properties
dialog (Terminal block element group):
- the section in mm2 (combo box with the usual sections, editable)
- the width / manufacturer reference (Weidmuller, Phoenix Contact,
  WAGO, ABB Entrelec), filtered by the selected section
- the color, using the palette of the terminal strip (editable,
  accepts a name, #rrggbb or #rrggbb/#rrggbb)

Values are stored in the element informations (terminal_section,
terminal_width, terminal_color) with the dot as decimal separator,
the keys already read by RealTerminal. An empty value removes the key.

The sections and the manufacturer widths move from
terminalstripmodel.cpp to the new header-only terminalcatalog.h,
shared by the terminal strip editor and the element editor.
2026-10-16 07:50:34 +02:00
Laurent Trinques cc9fe1a5c8 Merge pull request #1511 from ispyisail/fix/recover-test-damaged-message
Fix the recovery test failing on master since #1504
2026-10-16 07:08:02 +02:00
Laurent Trinques 2d32be22eb Merge pull request #1508 from ispyisail/fix/report-text-shift-lazy
Fix report text shifted by its width after a lazy-folio save
2026-10-16 07:07:03 +02:00
Laurent Trinques e612a59888 Merge pull request #1507 from ispyisail/feat/project-lock
Add a project lock: tell a second person the project is open
2026-10-16 07:05:44 +02:00
Laurent Trinques 1c55c3f7b8 Merge pull request #1506 from ispyisail/feat/changed-on-disk-check
Ask before saving over a project someone else changed
2026-10-16 07:05:22 +02:00
ispyisail 10988770c6 Merge master into feat/project-lock
Conflicts: the lock dialog and the recovery dialog both kept in
qetdiagrameditor.cpp; the lock and previous-versions settings both kept.
The spacer in the General settings page moves to row 9, below the lock
checkbox, which the merge had left on the same row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgPFANhWgD66V3v9KVKV58
2026-10-16 17:44:44 +13:00
ispyisail cc5a634cab Merge master into feat/changed-on-disk-check
Conflicts: QETProject::write() now checks for a change on disk before it
keeps the previous version; both new tests kept in tests/qttest/CMakeLists.txt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgPFANhWgD66V3v9KVKV58
2026-10-16 17:44:42 +13:00
ispyisail 0f65e912e7 Expect a damaged-database message when recovering an overwritten page
Since the integrity check on opening a .qetz database (#1499) runs before
anything is read, an overwritten page is reported as "the project
database is damaged", not "cannot be read". recoverLeavesOutOnlyTheDamage
was written before that check and has failed on master since #1504.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgPFANhWgD66V3v9KVKV58
2026-10-16 17:14:12 +13:00
ispyisail 6b08b87d16 Merge pull request #1503 from ispyisail/fix/qetz-header-folio-pictures
Fix a .qetz project that cannot be opened after changing a folio's title
2026-10-16 13:41:11 +13:00
ispyisail 3521c64ba3 Tell a second person who has a project open, and open it read-only
Two people opening the same project on a shared drive could both edit it,
and the last save silently wiped the other's work. A project now has a
hidden lock file beside it (.~plant.qetz.lock, small JSON: user, host,
process, version, since, heartbeat). Opening a project another live session
holds shows who has it, since when and how long ago they were last active,
and offers Open read-only (default), Open anyway, or Cancel.

- The lock is made with an exclusive create, refreshed every minute and
  deleted when the project closes. It is not a QLockFile: that treats any
  lock from another machine as stale after 30 s. Here only a lock made on
  this machine by a process that no longer exists is taken silently.
- A folder the lock cannot be made in, or the setting turned off
  (Settings > General > Projects), opens as before.
- Save As and .qet saved as .qetz move the lock to the new file, and refuse
  a file someone else has open. A project opened read-only for another's
  lock does not save over that file.
- --resave and --set-titleblock refuse a file someone has open unless given
  --ignore-lock; the read-only verbs never look at locks and headless runs
  never take one. A script's qet.save() refuses the same way.

Checked: 17 new unit tests (5 go red when stale detection and the exclusive
create are broken), full ctest 107/107, the dialog and both choices in the
GUI under Xvfb, and --resave refused/allowed from the command line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:01:54 +13:00
ispyisail fcc11bff74 Keep a right-aligned text where it was when lazy folios refill it
A report symbol's right-aligned text (the function of its partner's
wire) was saved at another x than with every folio built after deleting
a coil on its folio: moved left by its own width. Cosmetic, nothing lost.

setPlainText() keeps a right-aligned text's right edge, except while the
project is being opened (ProjectParsingRunning) or the symbol is built.
With lazy folios the text is emptied outside that state (the partner's
folio is not built, so no wire is watched) and the edge is kept, then
filled again while the late folios are built, which holds the project in
the opening state: the edge was not kept, and x stayed where emptying
moved it. With every folio built both happen during the opening.

A text emptied with its alignment kept is now filled again the same way,
whatever the project state; a text set while the project is opened and
never emptied that way is untouched, so what a plain opening writes does
not change.

Evidence: forum f1537 (resaved with every folio built), deleting coil
{794d8944} on folio 9 saved x=11.7969 lazily, x=60 eagerly; both 60
now. Resave of the 342 corpus projects, lazy and eager, identical to
before. tst_reporttextlatefolio fails without the fix (x=28.6875 lazily).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 12:57:17 +13:00
ispyisail e8a5d6aec0 Ask before saving over a project file someone else changed
Saving replaced the project file whatever had happened to it since it was
opened. If another user, another program or a second QET had saved the same
file in between (a shared drive, a copy synced from another PC), their work
was lost without a word.

QETProject now remembers the project file's size and modification time when
it reads it and after each save of its own. Before writing, write() compares
them with the file on disk and, if they differ, writes nothing and returns
"someone else changed the file on disk at <time> since it was opened".

- Interactive save (Ctrl+S, and the save on closing a project) asks:
  "Someone else changed <file> on disk at <time> since you opened it" with
  Save as a copy (opens Save As), Overwrite, or Cancel.
- Scripts (--run), the command line, MCP and the autosave timer have no one
  to ask, so they keep the refusal (autosave logs it). Setting
  QET_OVERWRITE_CHANGED=1 makes a script overwrite on purpose.
- A file that does not exist yet, and a Save As to another name, are not a
  conflict; after Save As the remembered state is the new file's.

No change to what is written, so .qet and .qetz stay as they were.

Evidence: new tst_changedondisk (unchanged file saves twice; a file written
behind the open project is refused; the override overwrites; a Save As
target is no conflict) fails on the changed-file case without the source
change and passes with it. Full ctest 107/107. Physical check with
qet-gui-dialog.sh: file modified behind the open project, Ctrl+S shows the
three-button dialog.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgPFANhWgD66V3v9KVKV58
2026-10-16 12:17:21 +13:00
ispyisail b4bbd17933 Fix a .qetz that cannot be opened after only a folio's title changed
With a .qetz and lazy folios (both the default), changing only the
title, author, date or revision of a folio holding a picture, while that
folio was not displayed, then saving, gave a .qetz that refused to open:
'picture "images/<sha>.png" is missing'.

Such a folio is saved from its items as read, with its new header
(Diagram::deferredXmlWithHeader()). Read from a .qetz, its <image> names
its picture's file (qetz-file) instead of holding the picture, and
QetContainer::split() takes out only pictures held inline: the file was
never written, while the folio still named it. The folio now gets its
pictures written in, from the bytes the project keeps
(QETProject::containerPicture()), as a built folio writes them.

On master, a picture on folio 2 of grafcet, iso_sfc_example, 741,
tremie_vibrante or Projet_vierge is lost this way; found on three forum
projects by comparing lazy saves with saves of every folio built. Saving
as .qet was not affected.

New tst_qetzheaderpictures (grafcet.qet: picture on folio 2, saved as
.qetz; its title changed with folios built when needed; reopened with
its picture and title): fails without this change ("picture ... is
missing"). ctest 107/107.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 11:33:51 +13:00
ispyisail 973417a694 Merge pull request #1505 from ispyisail/feat/qetz-open-failed
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m4s
Offer a damaged project's previous version, or what can be read of it
2026-10-16 11:27:21 +13:00
ispyisail d97434cec4 Merge pull request #1504 from ispyisail/feat/qetz-recover
Add recovering what can be read of a damaged .qetz project (--recover)
2026-10-16 11:26:55 +13:00
ispyisail 8301856ee7 Merge pull request #1502 from ispyisail/fix/report-text-after-info-edit
Fix a folio report pointing at its partner's old place after its properties are edited
2026-10-16 11:26:16 +13:00
ispyisail 7e82d290f9 Merge pull request #1501 from ispyisail/feat/keep-previous-versions
Add keeping the previous versions of a project each time it is saved
2026-10-16 11:25:52 +13:00
ispyisail c7ae853e8f Merge pull request #1499 from ispyisail/fix/qetz-sqlite-checks
Check a .qetz project's database the way SQLite advises before reading it
2026-10-16 11:25:05 +13:00
ispyisail 4df4458d7b Merge pull request #1498 from ispyisail/fix/save-sync-folder
Fix a saved project being lost to a power cut just after saving
2026-10-16 11:24:31 +13:00
ispyisail ea8871af1b Offer a damaged project's previous version, or what can be read of it
A project file that could not be read showed "The file ... does not
appear to be a QElectroTech project file", for a .qetz missing one
picture as for a picture file renamed .qet: wrong, and no way out.

Now the message says what is damaged (QETProject::openError()) and
offers, when there are any:
- the newest previous version kept beside it (PreviousVersions);
- for a .qetz, what can be read of it (QetContainer::recover()), then a
  list of what was left out.
Either opens as a new project, "<name> (recovered)" beside the file,
numbered if that is taken, not saved yet. The damaged file is never
moved or written.

Checked in the program (scripts/qet-gui-dialog.sh, File > Open on a
.qetz whose picture is missing, a previous version beside it): the
dialog says which picture; "Recover what can be read" opens plant
(recovered).qetz without the picture, saved with Ctrl+S it opens again;
"Open the version saved ..." opens plant (recovered 2).qetz with its
picture; plant.qetz unchanged (same sha256). ctest 107/107.

Stacked on "Keep the previous versions of a project when saving it" and
"Add recovering what can be read of a damaged .qetz".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 10:39:29 +13:00
ispyisail ec8fd5f8d6 Merge branch 'feat/keep-previous-versions' into feat/qetz-open-failed 2026-10-16 10:20:21 +13:00
ispyisail 2bd19e04be Add recovering what can be read of a damaged .qetz (--recover)
A .qetz that does not open as it is -- a picture's file missing (as
the title-change fault wrote them), a folio file missing, a row deleted
from project.sqlite by hand (issue #1458), a damaged database -- was
refused whole, and nothing in QElectroTech could get anything back.

QetContainer::recover() reads what it can and says what it left out:
an entry that cannot be unpacked, a missing folio file or picture, a
symbol or wire that does not match the database, and, when the
database cannot be read at all, the information it held (the wires go
too, as it holds their ends). join(), restore() and QetZip::fromBytes()
take an optional list for this; given none they refuse as before.
`qelectrotech --recover damaged.qetz out.qet` writes what was read to a
.qet and lists what was left out.

Measured on industrial (50 folios, 618 symbols, 671 wires): a wire row
deleted -> 670 wires, the rest whole; a folio file missing -> 49
folios; a database page overwritten -> every folio and symbol drawn, no
wires and no symbol information. A zip cut short (no central directory)
still cannot be read.

Tests: recoverLeavesOutOnlyTheDamage (nothing, folio file, wire row,
database page), recoverMissingPicture, recoverNotAZipRefused; they fail
with the database side kept strict. The strict cases pass unchanged.
ctest 106/106.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 10:15:37 +13:00