Commit Graph

10554 Commits

Author SHA1 Message Date
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 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
ispyisail e8201abb8a Keep the previous versions of a project when saving it
Saving replaced the project file with nothing kept of what it was: one
bad save -- a fault in QElectroTech, a full disk, a wrong edit saved by
mistake -- and the last good version was gone. AutoCAD keeps a .bak on
every save, KiCad several backups.

Before a project is saved over, the file as it was is now copied to a
folder beside it, "<name>-backups", named by the time that version was
saved ("industrial 2026-10-16 08-30-12.qetz"); only the newest are kept,
5 by default (Settings > General > Projects, 0 for none). A copy is
never a move, so a save that fails leaves the file in place; a copy that
fails only warns, the save goes on. The same bytes as the newest version
kept are not kept twice.

The prompt on opening ("Create a backup copy?"), which writes one dated
copy beside the project and never removes any, is left as it is.

New tst_previousversions (newest kept, same bytes once, same second,
nothing for a new file or a count of 0, a failed copy said); it fails
with the oldest never removed. Checked with the program: two scripted
saves of a .qetz leave the original and the first save in
industrial-backups, the project holds the second. ctest 107/107.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:00:07 +13:00
ispyisail fcc797342e Fix a folio report pointing back at its partner's old place after its information changes
A folio report's label shows where its partner is (updateReportText()).
Changing any other information of the report -- a comment, a function --
ran elementInfoChanged(), which set the text from the label stored in
the report's information instead: the partner's position when the file
was last saved. Move the partner from column 4 to column 9, give the
report a comment, and it showed (and saved) 1-C4 again.

With lazy folios the partner is often built after the change, so the
report was recomputed and the two modes saved different files; that is
how this was found (FINDINGS F116, tools/lazy-folios/edit-diff.py).

New tst_reporttextafterinfo: move the partner, comment the report, save,
check the report says 1-C9, with folios built when needed and with every
folio built. Both rows fail without this change ("1-C4").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 08:54:27 +13:00
ispyisail e3fa4a049c .qetz: read the project database the way SQLite advises for untrusted files
A .qetz's project.sqlite comes from a file, which may be damaged, made
by another tool, edited by hand (issue #1458) or made to harm. SQLite's
advice for such files (https://www.sqlite.org/security.html) was only
partly followed: trusted_schema was off and every name had to be a plain
table. readAsUntrusted() now also turns on the defensive mode, turns off
triggers and views, checks each cell's size as its page is read, limits
a value to 256 MiB, and runs quick_check before anything is read, for
the database read on opening and for the one a save copies kept folios
from.

A damaged database is now refused as "the project database is damaged",
where it was "cannot be read: database disk image is malformed" or a
missing row. Damage on disk was already caught before SQLite: the zip
checks each entry (one flipped bit in project.sqlite: refused, "unexpected
decompressed size"). quick_check does not see a changed value inside a
row (49 of 400 random small changes read silently, quick_check "ok"):
only the zip's checksum does.

New test damagedDatabaseRefused (a page overwritten at three places):
fails without this change on the message. Opening nuri (136 folios):
3.56-3.66 s with and without. ctest: the 7 container, save and lazy
folio suites pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 08:41:27 +13:00
ispyisail 7e96bb51d2 Sync the folder after saving, and save from the command line through a temporary file
QSaveFile writes a new file, forces it to disk and renames it over the
old one, but does not force the folder to disk. On ext4 or XFS a power
cut soon after a save can then lose the rename: the previous file comes
back, or none. QET::syncFolderOf() now syncs the folder after every
project, symbol and title block save (QET::writeXmlText()) and every
.qetz save (QetZip::write()). Nothing to do on Windows.

--resave and --set-titleblock wrote a .qet straight over the output file
(QFile, truncate, write): a run stopped part-way left half a project.
They now write through QET::writeXmlText() like the GUI's save. On
Windows their .qet now has LF line ends, as the GUI writes.

Checked with strace on --resave: fdatasync of the new file, linkat into
place, fsync of the folder. The .qet written is byte-identical to before.
ctest 106/106.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 08:23:31 +13:00
Laurent Trinques c4b5cf2f11 Merge pull request #1496 from ispyisail/fix/qetz-upgrade-step-by-step
Read older .qetz files by upgrading them one format at a time
2026-10-15 20:47:54 +02:00
ispyisail 8ef92c75c8 Merge pull request #1465 from itseogo/feature/text-format-painter-and-chained-reports
Add Element Text Format Painter tool
2026-10-16 07:18:24 +13:00
Itsego 8d56d927e1 Add Element Text Format Painter tool
- Implement ElementTextFormatPattern to capture and reapply dynamic element text formats (font, color, alignment, rotation, frame, positions, groups) across elements.
- When applying a format, missing ElementInfo texts present on the source template are added to the target element with the template's layout and styling, matching other texts in order.
- Register actions under Edit menu, diagram context menu, and ShortcutManager (customizable via Toolbar settings).
- Bind default shortcuts Ctrl+Alt+C (copy text format) and Ctrl+Alt+V (apply text format).
- Provide full undo/redo support via ApplyElementTextFormatCommand.
- Add SVG icons format-copy and format-apply.
- Include English source strings and German/French translations.
2026-10-15 20:07:01 +02:00
ispyisail d50f27dc8b .qetz: bring an older database to the current format one step at a time
The reader of project.sqlite knew two formats at once: every query asked
the database which column names to use (v2() ? "folio_id" : "folio_uuid"
and so on), and each new format would have added another branch to every
query. Asked in discussion #1440.

An older database is now brought up a format at a time when a .qetz opens
(readyToRead()): format 2 to 3 renames its tables' columns, adds the show
columns and puts the symbols' and wires' uuids and the symbols' type back
in the folio files; 3 to 4 has nothing to change for reading. The reader
then knows only the current format. The next format adds one step.

Checked: the format 2 test file (tests/qttest/fixtures/qetz_format2.qetz)
resaved as .qet and as .qetz gives the same files as master. format2Read
now also checks that every symbol and wire gets its uuid and type back:
it failed with the type left out of the step, which the test did not see
before. ctest 106/106.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 06:59:46 +13:00
Laurent Trinques 8d59d25796 Improve terminal_strip
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m45s
2026-10-15 16:26:45 +02:00
Laurent Trinques e883d2c3bf Improve terminal_strip 2026-10-15 14:38:11 +02:00
Laurent Trinques 892a899a20 Merge pull request #1492 from Xyli0/fix/snap-common-collection-layout
snap: restore layout so the QET collection is found
2026-10-15 13:02:18 +02:00
Xyli0 902f12e333 snap: restore layout so the QET collection is found 2026-10-15 11:52:30 +02:00
ispyisail 224324c56f Merge pull request #1491 from ispyisail/fix/reattach-wires-moved-terminals
Fix wires lost when a symbol's terminal moved a few units
2026-10-15 22:22:16 +13:00
ispyisail b085dcd5dd Pair terminals moved by one shift, and terminals sharing a place
The first pass refused a pair whenever a saved terminal had two live
candidates in reach. That happens when a whole drawing moves by half the
spacing of its terminals (pins 10 apart moved 5), and when two terminals
share a place. When one offset fits every unmatched terminal, as when a
whole drawing moved, that offset now settles all pairs; terminals sharing
a place pair in order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:42:43 +13:00
ispyisail 786f8574d9 Reattach wires to a terminal that moved a little since the project was saved
A project saves, for every placed symbol, where each terminal docked, its
orientation and its number. When the symbol's definition is replaced by one
whose terminal sits a few units elsewhere, wires were matched by exact
position and so were dropped on the next open: legacy wires by Terminal::
fromXml(), uuid wires by the uuid derived from the terminal's new place.

Element::fromXml() now pairs each saved terminal that nothing matched
exactly with the live terminal of the same orientation whose docking point
is within 5 units in x and y, only when each is the other's only candidate.
A pair registers the saved number, and the uuid derived from the old place,
which Diagram::findTerminals() checks after the derived uuid of the current
place. Wire ends reattached this way are counted and logged once per folio.

tst_movedterminals covers a move of 3 and 5 units (number and derived
uuid), 6 units, two candidates, and an unmoved terminal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:26:30 +13:00
Laurent Trinques 6af9c097d8 Update French translations files
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m54s
2026-10-15 08:22:05 +02:00
Laurent Trinques d27187ddeb Merge pull request #1487 from Kellermorph/german-translation
german translation
2026-10-15 07:54:29 +02:00
ispyisail d5c90a0861 Merge pull request #1486 from ispyisail/fix/mirror-keys-and-labels
Fix the M and F mirror keys doing nothing on a folio
2026-10-15 18:54:08 +13:00
Kellermorph 3c6d8dcb09 german translation 2026-10-15 06:55:10 +02:00
ispyisail 74b480934f Fix the M and F mirror keys doing nothing on a folio
#1346 rebuilt the Edit menu from a list of named actions, written before
#1354 added Horizontal mirror and Vertical mirror. The two actions ended up
in no menu and on no toolbar, so their shortcuts (M, F) never fired; only
the right-click menu reached them. Add both to the Edit menu after
"Choose texts orientation".

Also correct two labels left behind by later changes:
- Settings > General: the fine-movement step is now used with Ctrl, not
  Alt (#1395).
- Terminal editor tooltip: the checkbox is now called "Function
  separation" (#1456).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:06:29 +13:00
ispyisail fba67c3b2a Merge pull request #1483 from ispyisail/fix/qetz-1472-followups
Fix saving to a read-only folder, and the #1472 review follow-ups
2026-10-15 13:31:03 +13:00
ispyisail c65a81b0ca Merge pull request #1484 from ispyisail/fix/1482-crlf-spec-test
Fix tst_qetcontainer failing on Windows (#1482)
2026-10-15 13:30:41 +13:00
ispyisail a4ca61d48b CI: run the unit tests in the Windows job again
f06fd1130 turned them off because tst_qetcontainer failed there (#1482);
the previous commit fixes that test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:11:29 +13:00
ispyisail 41baea32a8 Fix tst_qetcontainer on a Windows checkout (#1482)
specMatchesSchema() finds the schema in QETZ-FORMAT.md by looking for
"```sql\n". A Windows checkout gives the file CRLF line endings, so the
marker is never found and the test fails with "no schema in
QETZ-FORMAT.md". Drop the carriage returns after reading the file.

Checked by converting QETZ-FORMAT.md to CRLF: the test failed with the
CI message before this change and passes after it; with LF it passes
both ways. All 51 cases of tst_qetcontainer pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:11:29 +13:00
ispyisail 3f3453b3ce Update TS files
lupdate for the strings added by #1472 and this branch; the rest of the
change is moved line numbers. Review of #1472 (scorpio810, point 11).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:21:03 +13:00
ispyisail e5c5670f24 Register .qetz in the remaining packaging files
- register_filetypes.bat (portable Windows build, two copies): .qetz
  opens with QElectroTech like .qet.
- AppStream metadata: lists the four file types QElectroTech opens.
- macOS Info.plist: declares the .qet and .qetz types (UTIs), which
  recent macOS needs to associate an extension with the application.

Review of #1472 (scorpio810, point 3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:20:57 +13:00
ispyisail cd3a726e26 Name the .qetz MIME type application/x-qet-project+zip
Every other QElectroTech file type is application/x-qet-*; the zipped
project was application/x-qelectrotech-project+zip. No release has
shipped it yet, so it is renamed now.

Files written with the old name still open: the reader accepts both, and
the shared-mime-info file keeps the old name as an alias and matches
either on content.

Review of #1472 (scorpio810, point 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:20:57 +13:00
ispyisail e398f65f0a Warn when a terminal strip lists a terminal no symbol has
A terminal strip saved with a terminal whose symbol is gone left that
terminal out without a word. It still does, and says so in the log.
The same happens with every folio built: the database is filled from
the document, so a symbol in the file is never missing from it.

Review of #1472 (scorpio810, point 8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:20:57 +13:00
ispyisail 56b142127b Use one file filter for opening a project
File > Open and Import numbering from a project each had their own
filter for .qet and .qetz, worded differently. Both use
QETProject::openFileFilter() now.

Review of #1472 (scorpio810, point 11).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:20:57 +13:00
ispyisail 9d807101e4 Keep a .qet project when its .qetz cannot be saved beside it
Saving a project opened from a .qet writes a .qetz beside it. In a
read-only folder that write fails, but the project had already been
switched to the .qetz path: the window then named a file that does not
exist, and the error said only "Permission denied".

The project now stays the .qet it was when the write fails, and the error
names the file: "Cannot save /path/name.qetz: Permission denied". Save as
still saves it elsewhere.

Found answering the review of #1472 (scorpio810, point 12).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:20:56 +13:00
ispyisail f244f6dda0 qet-mcp: clean up .qetz copies, never keep an unfinished one, bound scans
Review of #1472 (scorpio810, points 4-7):

- The .qet made from each .qetz lived in its own temporary folder that was
  never removed. Only the newest 8 are kept now, an older version of the
  same file is dropped when a newer one is read, and all of them are
  removed when the server exits.
- A --resave that failed or timed out could leave a partial .qet behind,
  which was kept and read. Only a run that succeeded is used now.
- qet_scan read every .qetz in the folder, each through QElectroTech with
  up to 900 s. It reads the first max_qetz (default 20) and lists the rest
  as skipped.
- test_edit_writes_a_qetz checks the rotated symbol is the one reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:00:23 +13:00
Laurent Trinques f06fd11305 Update windows-build.yml
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m39s
2026-10-14 18:28:20 +02:00
Laurent Trinques dbecfae35b Merge pull request #1481 from ispyisail/fix/pdf-orientation-per-folio
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m39s
Add per-folio page orientation to the PDF export (#1480)
2026-10-14 17:27:15 +02:00
Laurent Trinques 2ce508e373 Merge pull request #1472 from ispyisail/feat/qetz-db-truth-v2
Save as .qetz by default, with the project database as the source of truth
2026-10-14 17:22:44 +02:00
ispyisail f42958d2c2 PDF export: turn each page to its folio's orientation (#1480)
The print window exported every page in one orientation, landscape
unless changed, so a portrait folio was shrunk onto a landscape page.
With "Fit in page" on, a PDF export now turns each page to portrait or
landscape as its folio is drawn. The preview keeps one orientation
(QPrintPreviewWidget lays out every page at one paper size), and a
printer keeps it too (only Qt's PDF engine accepts a new page layout
once printing has started). The orientation chosen in the window is
put back afterwards, so it is still the one saved.

The cross-reference links took their target from painter->viewport(),
which QPainter sets once when it opens, so on a turned page they missed
their text. They now use the device's size, as render() does.

tst_printpdforientation exports through the window: an A3 landscape and
an A3 portrait folio get one page each way; tiled pages keep the chosen
orientation; and the links on examples/industrial.qet's turned pages
land where they do when the export starts portrait.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 23:38:00 +13:00
ispyisail 33a731b56f Merge pull request #1477 from ispyisail/perf/parallel-picture-decode
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m6s
Speed up opening folios with photos: decode pictures in parallel
2026-10-14 19:10:59 +13:00
ispyisail c0a5084bc4 Merge master into feat/qetz-db-truth-v2
The .qetz files take this branch's side: it already holds #1473, and
adds the folio parts to join().

qetscriptapi.cpp: #1456 gave setConductorProperty() the voltage that
crosses a function separated terminal element; here that function hands
its work to applyConductorProperties(), so the change goes there, with
the properties captured before the pushes. The database path for folios
not built already gives up at such a terminal, so it builds the folio and
takes the same route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:57:29 +13:00
ispyisail 373a705bbb Merge master into perf/parallel-picture-decode
tests/qttest/CMakeLists.txt: this branch's tst_folioimageload and
master's tst_svgsmallpictures (#1478) were added at the same place;
both are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:36:36 +13:00
ispyisail af142aba90 Merge pull request #1473 from ispyisail/fix/qetz-format-ids-types-version
Fix .qetz database: rows keyed by position, untyped, unversioned
2026-10-14 18:29:57 +13:00
ispyisail 8cda53f4ce Merge pull request #1456 from Kellermorph/terminal-potential
Terminal: rename potential separation to function separation
2026-10-14 18:26:00 +13:00
ispyisail 4e8af81fc6 Merge pull request #1478 from ispyisail/feature/svg-picture-limit
Add an SVG export option to store pictures smaller
2026-10-14 18:22:28 +13:00
ispyisail 9920c13bd9 Merge pull request #1476 from ispyisail/perf/derived-uuid-no-copy
Speed up opening older projects: work out the project uuid in place
2026-10-14 18:22:13 +13:00
ispyisail 1768518b44 Merge master into fix/qetz-format-ids-types-version
join() and readDocument() gained a parameter on both sides: master's
Pictures map (#1470) and this branch's format. Both are kept, pictures
first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:56:32 +13:00
ispyisail da6eb83035 Add an SVG export option to store pictures smaller
An SVG keeps every picture at full resolution, whatever size it is
drawn at: a folio with 22 phone photos shown at a tenth of their size
gave a 270 MB SVG and took 65 s to write, most of it compressing the
photos again.

The export dialog gets "SVG: smaller pictures" (off by default, kept
with the other export settings), and --export-svg gets
--svg-small-pictures. With it, a picture is stored at four times the
size it is drawn at when that is smaller than the picture; smaller
pictures stay whole. Four times is about 375 dpi on a printed folio.

The exporter sets Diagram::smallPictures() for its own drawing only,
so screen, print, PDF and PNG are unchanged. It is not part of
applyProperties(), which every format uses.

On that project: --export-svg 84.9 s -> 34.5 s (opening is 20 s of
both), 270 MB -> 57 MB, peak memory 2.45 -> 1.86 GB. Rendered at twice
the folio size, the two SVGs differ by more than 32/255 on 0.23 % of
pixels, along photo detail.

tst_svgsmallpictures: an 800x600 picture drawn at 0.1 is stored at
320x240 with the option, through --export-svg and through the
dialog's QPicture route, and at 800x600 without it; a 40x30 picture
stays whole. It fails when the factor is changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:12:38 +13:00