Commit Graph

4948 Commits

Author SHA1 Message Date
ispyisail f7e0283e61 Include QDomDocument in wiringlistexport.h
The header declares projectDocument() returning a QDomDocument but did not
include it; it only built where the precompiled header pulled it in. CI
builds without one and failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:51 +13:00
ispyisail 4104bb8d53 Write only the wire attributes reading needs in a .qetz (stage D3b)
A wire's properties are one row per attribute in the project database,
45 071 rows for f2099's 2 139 wires. The database a .qetz holds now keeps
only the attributes reading would not give anyway: each is left out in
turn when the properties read without it are the same (ConductorProperties
::fromXml(), its defaults); rows by wire and name, so the file does not
depend on memory order. Reading writes every attribute back into the
table, so queries and saves see what they saw before.

f2099: 9 387 rows, the file 3.60 MB -> 3.22 MB (5.14 MB before D3b);
industrial.qet 0.71 -> 0.61 MB. Gates with the master merge and the wire
edits by uuid: ctest 99/99 lazily, 98/99 with every folio built
(modal_quit_regression, the race fixed next); hostile .qetz all refused;
342 corpus projects save, export, round trip and re-save byte-stable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:51 +13:00
ispyisail 798010cc83 Change a wire's properties by uuid without building its folios (stage D5)
Scripts get conductorPropertyByUuid() and setConductorPropertyByUuid(),
the wire named by its uuid (conductorUuids()) rather than by a symbol's
terminal. A change goes to every wire at the wire's potential, as
setConductorProperty() does. When those wires are all on folios not
built, none is numbered by a formula, and what changes in each is what a
.qetz database holds in its own columns (number, function, voltage,
colour and section of the wire, cable, bus), it goes through the project
database: the store and the conductor rows, undoable by uuid. The
potential is followed in the database (projectDataBase::potentialWires():
through wire ends, folio reports and terminal blocks, as
Conductor::relatedPotentialConductors() follows it; a terminal block that
isolates its terminals answers nothing). Building a wire takes its change
(Conductor::fromXml()); a .qetz save writes it in the database, the folio
file copied; a folio written from its XML gets it there; a .qet builds.
Anything else builds the folio, as before.

tst_lazyfolios: every wire's potential from the database = as followed,
1 342 of 1 342 (industrial, eager and from a .qetz); a cable set by uuid
on a 14-wire potential builds no folio, reaches every wire, saves as when
built, undoes, shows once built (fails when building ignores the change,
and when the .qetz writer does). The test's run limit is raised (500 s ->
1 500 s, ctest 1 800 s): under load it ran past 500 s. ctest 96/96 both
ways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:51 +13:00
ispyisail a6d81a21a8 Build the folios before reading the drawn items' view (stage D6)
A query of drawing_item_view, which is made of the shape, text and
picture tables, built no folio, and with folios built only when needed
(the default since D6) it found nothing: the drawn items are written to
the database by built folios. A query naming the view builds them, as
one naming the tables does.

Found by the qet MCP server's suite (four drawn-item tests failed lazily,
passed with every folio built). tst_lazyfolios: the view's rows from a
.qetz as when built (5 = 5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:51 +13:00
ispyisail 975ec57c74 Write a .qetz's database without keys or indexes (stage D3b)
The database a .qetz holds is read whole: its tables are now written
without their primary keys and indexes, in the order of their rows, and
the file compacted; the reader indexes what it looks folios up by once
it has read the file. Keys and indexes on text uuids were half of the
database once zipped. A file written before reads as before.

f2099 (136 folios) 5.14 MB -> 3.60 MB, industrial.qet 1.03 -> 0.71 MB;
opening and showing a folio take as long as before (2.5 s, 215 ms). Gates
on 342 corpus projects: saves, exports, round trip, re-save byte-stable,
all open; hostile .qetz all refused; ctest 96/96 both ways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:51 +13:00
ispyisail 60489c9893 Fix two faults the end-state gates found (stage D7)
A .qetz re-saved after opening from its index saved wrong folio numbers
in its database: reading each folio's header wrote its row with a
number counted from the folios read so far ("1/1", "2/2" for "1/3",
"2/3"), and an index-opened project keeps the rows it was saved with,
so nothing wrote them again. The folio table's rows are no longer
written while the project is being read (projectDataBase::
diagramInfoChanged()). Folio list tables showed it.

A .qetz whose project.sqlite is not a database crashed QElectroTech: the
first statement could not be prepared and SQLite was handed a null
statement. A statement that cannot be prepared now binds nothing and
steps to no row, and the file is refused.

Gates: 342 corpus projects,
written by QElectroTech 0.3 to 0.200, all open; .qet -> .qetz -> .qet
equals the plain resave with every folio built; a .qetz re-saved twice
holds the same bytes entry by entry but the save's own name, path and
time (the first fault made the first re-save differ); hostile .qetz
(truncated, garbage database, view for a table, trigger, escaping and
repeated names, later format, missing folio, 2 GiB zip bomb) neither
crash nor hang and none opens; an unknown extra entry still opens.
tst_lazyfolios: folio numbers with totals from the index as when built
(fails without the fix); tst_qetcontainer: garbage database refused
(segfaults without it). ctest 96/96 both ways; save and export gates
same on 342.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:51 +13:00
ispyisail 5c4fa8fadf Build folios only when needed, by default (stage D6)
Every project now opens without building its folios; a folio is built
when it is shown or something needs its items (QETProject::lazyFolios()).
QET_LAZY_FOLIOS is gone; QET_EAGER_FOLIOS=1 builds every folio on
opening, as before, for comparisons and troubleshooting. Comments that
named the old variable now say "lazy folios"; the tests switch modes
with the new one.

The first save of a .qet as .qetz still builds every folio: a .qet does
not say which program wrote it, so its folios are written again rather
than copied. A .qetz opens and saves without building.

ctest 96/96 by default and with QET_EAGER_FOLIOS=1; 342 corpus projects
save the same in all four arms and export the same. In the GUI
(qet-gui-dialog): a 136-folio .qetz opens with no folio built, shows
folios 1 to 4, saves building none, the save = the original but the time
spent in it; a .qet opens lazily, saves as a .qetz beside it after the
notice, the .qet unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:51 +13:00
ispyisail b151f52428 Save a new title, author or revision without building the folio (stage D5)
A folio not built whose own data changed was built to save, with the
folios its links reach. When what changed is only title block fields
nothing on a folio is computed from -- its title, author, date,
revision index, version, file name, template; a formula reads the
folio's label, plant, location, extra fields and grid -- its items as
read are what building it would write: it is saved from them with its
own data as it is now (Diagram::deferredXmlWithHeader()), .qet and
.qetz alike, with the information of its symbols changed through the
project database (or built, when one of those needs it). Any other
change still builds it.

136-folio .qetz, revision index and author set on every folio by a
script: 0.8 s, saved in 6.8 s, no folio built; with every folio built,
the same file. tst_lazyfolios: a new revision on every folio of a .qet
and of a .qetz saved as when built, building only the folios holding a
table (fails, 47 built, when every change counts); a folio whose label
changed is built (fails when none counts). ctest 96/96 eager and lazy;
342 corpus projects save the same in all four arms and export the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:44 +13:00
ispyisail 244de05421 Change a symbol's information without building its folio (stage D5)
A script changing the information of a symbol on a folio not built, when
nothing else shows that information (no symbol built holds it, no
partner is linked to it, no label formula), changes it through the
project database: the store and the symbol's element info row, undoable
by uuid (ChangeStoredElementInformationCommand). Reading it back reads
the store. Building the symbol later takes the change
(projectDataBase::takeUnbuiltEdit(), in Element::fromXml()), the label
and texts following as for any change. Saving a .qetz writes the
changed information in the database in place of the rows the folio was
read with, its file copied, the folio still not built; a symbol whose
file holds no information block, or a .qet, builds the folio to save.

136-folio .qetz, "comment" set on the 3 513 symbols the database can
change: 0.9 s, no folio built, saved in 6.1 s, none built (27 s and 116
folios when saving built them). The same edits on the 4 039 symbols with
no partner and no formula, lazily and with every folio built: the two
saved projects are the same file. tst_lazyfolios: a script changes a
symbol not built, building none; saved as when built (fails when
building ignores the change, and when the .qetz writer does); undone;
the folio built afterwards shows it. ctest 96/96 eager and lazy; 342
corpus projects save the same in all four arms and export the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:44 +13:00
ispyisail 9078e4e321 Answer scripts from the folio asked for, without building every folio (stage D5)
The scripting API (scripts and the assistant's live mode) reached a folio
through diagrams(), which builds every folio, for every call. A folio's
own data -- count, title, uuid, title block, border, wire defaults --
now comes from folios(), built or not; a call reading or changing a
folio's items builds that folio, as showing it does (built()); selecting
a symbol by uuid builds the folio the project database places it on.
Search and replace and the continuity check still build every folio.

DiagramContent worked out one wire of each potential when made, which
follows potentials through folio reports and built the folios at the
other end, for every caller; it is now worked out when first asked
(Search and replace asks; a merge asks both sides, as before).

tst_lazyfolios: on a .qetz, a script reads every folio's title, uuid and
border building none (fails with diagrams()), and the symbols and wires
of the folio with the most links to other folios as when built, building
that folio only (27 before potentials were lazy). ctest 96/96 but
modal_quit_regression in both modes: a race in that test (gdb interrupts
the main thread inside malloc while the folio view repaints during the
dialog, master too), 2 of 23 runs here, 0 of 10 on master, passing alone.
342 corpus projects save the same in all four arms, and export the same
wiring list and wire numbers lazily.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:44 +13:00
ispyisail 65b10b01d9 Export the wiring list without building every folio (stage D5)
The wiring list export read the project as QETProject::toXml() writes
it, which builds every folio. It now reads a document made without
building: a built folio written as saving writes it
(QETProject::folioXml(), which keeps its symbols' links to partners not
built), a folio not built copied from its XML, and what QElectroTech
computes for a wire's ends -- its symbols' labels, their last partner's
label, the terminals' names -- filled in from the symbols built and from
the project database. A symbol still waiting for a partner not built
takes its partners from the database, saved order.

136-folio .qetz, wiring list from the command line: 30.1 s with every
folio built, 4.5 s lazily, same file. tst_lazyfolios: the wiring list of
a .qet and of a .qetz as when built, building no folio. New corpus gate
(it fails on
all 6 projects tried with terminal names broken): --export-cables and
--export-wires lazily from the .qet and the .qetz = eager on 342
projects; one (forum f3386) differed until waiting symbols took their
partners from the database. ctest 96/96 eager and lazy (one earlier
run had modal_quit_regression and tst_resaveunchanged fail once each,
both passing on rerun in both modes); 342 corpus projects save the same
in all four arms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:44 +13:00
ispyisail b972dbec4b Number terminals and export wire numbers without building every folio (stage D5)
Terminal numbering builds only the folios holding a terminal. The wire
numbers export reads the wires of the folios not built from the project
database, and walks a built folio's own wires instead of a
DiagramContent, which followed their potentials through folio reports
and built the folios at the other end. The wiring list export names a
folio from its title block, which a folio not built has; it still
builds every folio, writing the whole project out (toXml()).

tst_lazyfolios: wire numbers exported as when built, from a .qet and a
.qetz, building no folio (fails with DiagramContent: 27 built);
terminal numbering builds 7 folios, those with a terminal (fails with
diagrams(): 47); the wiring list as when built. ctest 96/96 eager and
lazy; 342 corpus projects save the same in all four arms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:43 +13:00
ispyisail e7fd820db6 Add, move or remove a folio without building the others (stage D5)
Adding, moving or removing a folio rewrote every folio's position in
the project database through diagrams(), which builds every folio: 3 s
on a 50-folio project opened lazily. A folio not built has its position
and title block already, so folios() is enough. The list of tables a
table can continue builds only the folios holding a table (from the
index of a .qetz), and the wiring list's count of wires left out looks
at built folios only: reading a wire drops it unless both its ends are
found.

tst_lazyfolios: a folio added, moved and removed builds no other, with
the folios' rows as when every folio is built (fails with diagrams():
47 built); the tables listed as when built, building the 12 folios that
hold one (fails when every folio is built). ctest 96/96 eager and lazy;
342 corpus projects save the same in all four arms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:43 +13:00
ispyisail 8ae937007c Answer numbering, searches and unused symbols from the database (stage D5)
The project database's element table now holds each symbol's definition
(the path its file names it by) and its sequential numbers, filled from
the document and from built folios alike. Numbering, the searches for
symbols of a kind (a link tab's candidates) and the list of unused
symbols read them for the folios not built, instead of reading those
folios' files. Saving looks up a folio's tables in the index it was
opened from.

A .qetz asked its numbering, searches and unused symbols reads only the
files of the folios it builds (tst_lazyfolios, which fails when the
unused symbols are read from the folio files: 50 read, 22 built).
ctest 96/96 eager and lazy; 342 corpus projects save the same in all
four arms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:43 +13:00
ispyisail b891b6354a Show a folio without building its partners' folios (stage D5b)
Showing a folio built it and every folio holding a partner of its
symbols. Now a partner is built alone, as a symbol, into its folio's
scene (Diagram::buildSymbols()), and reused when that folio is built.

- A linked folio report built alone had no wires when it was linked,
  so its texts showing a wire's properties stayed empty: after any
  build, linked reports on the built folios and their partners look at
  their wires again, as opening does. Unlinked reports keep their texts,
  as opening leaves them.
- A report whose partner was built alone builds the partner's folio
  when its potential is followed.
- tableChainFolios() parsed every unbuilt folio's file. The index now
  lists the drawn tables and the table each continues (folio_table),
  and a .qetz opened from it uses that list. An index without it falls
  back to the folio files.
- A .qetz's project.sqlite is opened once per project
  (QetContainerDb::FolioReader), not once per folio built.

nuri (136 folios), opened fresh, one folio shown: median 1104 -> 184 ms,
each show builds one folio; industrial 234 -> 87 ms.

tst_lazyfolios: a folio whose table continues another is shown first
from a .qetz (fails when the index's chains are ignored). ctest 96/96
eager and lazy; 342-project corpus: master, lazy, .qetz lazy and .qetz
kept all the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:43 +13:00
ispyisail 2f94724df3 Save a .qetz without building partners: symbols save the links they wait for (stage D5a)
A built symbol whose partner is on a folio not built yet (QET_LAZY_FOLIOS)
now saves that link too, in the order it was saved with
(Element::toXml(); QETProject::waitingLinkTargets() keeps it to partners
the project has, so a link to a symbol gone is dropped as it always was).
With that, and with no partner's text in a .qetz's folio files (D3c),
saving a .qetz no longer builds the folios of the partners built folios
wait for; table chains still are (a table saves the table before it), and
a .qet saved as read (QET_LAZY_SAVE_KEPT) still builds them, its folio
files holding partners' texts.

industrial.qetz: an untouched save builds no folio (47 of 47 stay not
built; 19 before), after everyday edits on its busiest folio 29 stay not
built (7 before).

tst_lazyfolios compares the links an edited .qetz saved without building
holds, symbol by symbol in their order, with those the same edits saved
with every folio built -- it fails when waiting links are not saved (the
drawing alone cannot tell: links are kept on both sides, and a symbol
reread gets its partner's back, in another order). Corpus, 342 projects:
a .qetz saved without building, read and saved as .qet = eager = master.
ctest 96/96 with and without QET_LAZY_FOLIOS=1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:43 +13:00
ispyisail 8ad33f6281 Save a .qetz without building the folios not touched (stage D4)
With QET_LAZY_FOLIOS=1, a project opened from a .qetz's index saves a
.qetz without building the folios it never built, when building them
would write the same: their parts are copied as they were and their rows
of the document's engineering data from the project.sqlite they were read
from (QetContainerDb::extract() with kept_from, QetContainer::split() with
the old entries, pictures included). Whether a folio may be kept follows
the rules of stage 5.5 (QETProject::toXml() with kept_parts): nothing
project-wide changed since opening; a folio whose own data changed is
built with its partners; after an edit, folios holding a table filled
from the database are built; the partners still waiting of every built
folio are built. Trusted only for a project opened from a format 3 index
written by this version whose folios are named by their uuid.

Format 3 names a folio by its uuid in the database (doc_meta folio_ids =
uuid) when every folio has one of its own, so that a kept folio keeps its
rows whatever its place; copies keep the rows' order (ORDER BY rowid).
A folio not built keeps the name of its part until it is built
(Diagram::deferredPart()).

Measured, the 136-folio f2099 opened from its .qetz and saved as a .qetz:
4.75 s with no folio built, 33 s with every folio built; its 136 folio
parts copied byte for byte; a kept save of a kept save is the same bytes.
industrial.qetz untouched: 28 of its 47 folios not built are built to save
(link chains, as in 5.5); after everyday edits on its busiest folio, 7
stay not built.

tst_lazyfolios: an untouched .qetz saved without building, read back,
holds the project; edits saved without building hold, read back, what the
same edits saved with every folio built hold (read back too: a wire
numbered from its potential is saved with no sequential numbers whichever
the way -- reading it back renumbers it, a separate issue); fails when a
kept folio's symbol information is not copied. Corpus, 342 projects: a
.qetz saved without building, read and saved as .qet = eager = master on
all 342. ctest
96/96 with and without QET_LAZY_FOLIOS=1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:43 +13:00
ispyisail 8ea051acfc A .qetz keeps nothing QElectroTech computes in its folio files (stage D3c)
Format 3 folio files lose what building a folio computes
(QetContainer::dropComputed()): a wire's description of its ends
(element1_label, element1_name, element1_linked, terminalname1 and the
same for end 2), the content of a symbol text showing the symbol's
information or a composition of it, and -- when the project database's
index is written, every symbol with a uuid of its own -- a symbol's list
of its terminals.

What may go was found by test, not by reading: on 342 projects saved by
this version, each kind taken out of the file, the project opened and
saved: the wire ends came back in all 244 that had them, the symbol texts
in all 201, the terminal lists in 305 of 306 -- not in the file whose
symbols share a uuid (#1408), where wires find their ends by them; such a
project gets no index, so it keeps them.

Stays, for now: labels made from a formula and wire numbers made from one,
in the database (opening from the index and numbering read them), and a
table's size (the user's as much as computed).

tst_qetcontainer compares a joined document with the original less what is
computed, and computedNotKept checks a file holding both kinds (one filled
in by the test) loses them -- it fails when nothing is dropped. Through
QElectroTech, 342 projects: a .qetz opened from its index and saved as .qet
= eager = master; every folio of industrial and f2099 drawn and linked as
eager (f2099 but its 3 render-noise folios). ctest 96/96 with and without
QET_LAZY_FOLIOS=1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:43 +13:00
ispyisail 2f98872d7a .qetz format 3: one project database (stage D3a)
project.sqlite now holds both what QElectroTech keeps in its project
database (the index D2 wrote as index.sqlite) and the document's
engineering data, the latter's tables named doc_<name> beside the
former's -- element, element_info, link and conductor exist in both
(QetContainerDb's SQL names them as before; Database::sql() puts the
prefix on). The manifest says format 3, min-reader 3: a format 2 reader
would find no tables of its names. Format 2 files, with or without an
index.sqlite entry, are read as before: the prefix is found from the
file (doc_meta).

A .qetz with an index opens from project.sqlite. Measured: the 136-folio
f2099 1.8 s, 5.2 MB (the two table sets still overlap: D3b).

Checked: tst_qetcontainer with the format 3 names, 34/34; a format 2 file
of the last commit opens from its index.sqlite and saves the same; corpus
342: 340 .qetz carry the index in project.sqlite and open from it, saved
back = eager = master on all 342. ctest 96/96 with and without
QET_LAZY_FOLIOS=1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:43 +13:00
ispyisail e89ccf2a40 Open a .qetz from its database, each folio read when built (stage D2)
A .qetz now carries index.sqlite: the project database as QElectroTech
keeps it -- every table but the drawing items, which exist for built
folios only -- with each folio's own data (Diagram::headerXml()) and a
stamp of the schema it was written with (projectDataBase::indexFile(),
QETProject::writeContainerFile(): Save, --resave, --set-titleblock, a
script's and live mode's save). Written when every folio, symbol and wire
has a uuid of its own; a reader that does not know the entry skips it.

With QET_LAZY_FOLIOS=1, a .qetz with an index opens from it: the project
document is joined without its folios (QetContainer::join() with
folio_files), the database and the stores of symbol information and wire
properties are filled from the index (fillFromIndex()), each folio is made
from its own data and keeps the name of its part, read when the folio is
built (Diagram::deferredXml(), QETProject::readFolioPart(),
QetContainer::readFolio() -- pictures and engineering data back from
project.sqlite, QetContainerDb::restoreFolio()). A terminal strip finds its
terminals' folios in the database. Any other .qetz, or one whose index
cannot be used, is read whole as before.

The index is untrusted: refused unless written with this database's
schema, every table read a plain table (not a view), nothing in its schema
run (trusted_schema off); refused, the project is read whole. Checked: an
index of garbage, a wrong stamp, a table made a view -- each refused and
the project saved as a plain save.

The 136-folio f2099 as .qetz opens in 1.8 s (3.65 s read whole, 29 s
building every folio); what is left is mostly its 7 MB of symbol
definitions (0.8 s). The file grows from 2.6 to 5.2 MB, the index
overlapping project.sqlite until D3.

tst_lazyfolios: a .qetz opened from its index builds only the terminal
strip's folios, numbering read as when built, every folio shown in turn
draws and links as when built, the save as when built; fails when a folio
part's engineering data is not put back. Corpus, 342 projects: 340 .qetz
carry an index and open from it (2 have symbols sharing a uuid); saved
back as .qet = eager = master on all 342. Probe on industrial and f2099
.qetz: every folio as eager but the 3 render-noise folios of f2099. In a
real window the .qetz opens and pages building only shown folios and
partners. ctest 96/96 with and without QET_LAZY_FOLIOS=1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:43 +13:00
ispyisail ecc7a39131 Save as .qetz by default; a .qet opened is saved beside it (stage D1)
The zipped project (.qetz) is the format QElectroTech saves.

- Save As and the first Save of a new project offer .qetz first
  ("untitled.qetz"); .qet can still be chosen.
- A project read from a .qet is saved as a .qetz beside it, the .qet left
  as it is (QETProject::write(), so Save, autosave, a script's save and the
  assistant's live save all do it). The project then works on the .qetz.
  A path chosen afterwards (Save As, a script naming one) is saved to as it
  is. The editor says so before the first such save, and asks before
  replacing a .qetz already there.
- File > Save a copy for an older QElectroTech writes a .qet copy, for a
  version that cannot open a .qetz; the project keeps its own file.
- The operating systems know the file: a freedesktop MIME type
  application/x-qelectrotech-project+zip (*.qetz, and by its mimetype
  entry when the name says nothing), the desktop file, macOS's document
  types and the Windows installer's association.

Crash backups stay XML, written beside the project as before.

tst_resaveunchanged::qetSavedAsQetzBeside: a .qet saved where it is leaves
the .qet byte for byte and writes a .qetz beside it holding what a plain
save of the .qet holds; it fails when the switch is off. In a real window:
Ctrl+S on perceuse.qet shows the notice, writes perceuse.qetz and leaves
perceuse.qet's md5 unchanged; the copy for an older version (reached by
command search) equals a plain save but for time_spent; a new project's
Save offers untitled.qetz. xdg-mime finds the type by name and by content.
ctest 96/96 with and without QET_LAZY_FOLIOS=1; srclint R-C clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:33 +13:00
ispyisail 00e200b504 Remove references to notes that are not in this repository
Comments added with the lazy folios cited planning notes kept outside the
QElectroTech repository, which a reader of the source cannot open. The
sentences now stand on their own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:03:14 +13:00
ispyisail 1c4618c8dd Open with every folio built when the database must come from them
With QET_LAZY_FOLIOS=1, a project whose database cannot be filled from
the file (folios or symbols with no saved uuid, two symbols sharing one,
a folio number using %autonum, wires naming their ends the older way)
had its folios left unbuilt, then built all at once while the database
was filled from them. That is slower than building them as they are
read: a 17 MB project reported on the pull request took 24.8 s to open
instead of 19.2 s, and most files saved by an older version have no
folio uuids.

The file is now checked for those reasons before the folios are read;
when one applies the project opens as without QET_LAZY_FOLIOS. The rarer
reasons, which need the definitions or every folio's links, still fall
back as before.

tst_lazyfolios opens a copy of industrial.qet with one symbol's uuid
given to another, and checks no folio is built after reading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:02:55 +13:00
Laurent Trinques 77e2a9bfc5 Speed up opening big projects with lazy folios 2026-10-13 11:01:21 +02:00
Laurent Trinques a78e95be2c Merge pull request #1462 from ispyisail/feat/qetz-container
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m8s
Add the zipped project format (.qetz) as a save option
2026-10-13 08:08:11 +02:00
ispyisail 4b4b9a2ad0 Merge pull request #1435 from bhangart/fix/line-pivot-follows-resize
Keep a line's pivot at its centre when one of its ends is dragged
2026-10-13 16:29:20 +13:00
ispyisail 9dc03005da Merge pull request #1457 from ankostis/fix1376-no-bom-transfer
Skip boolean `"exclude_from_bom"' when pasting element infos
2026-10-13 16:22:08 +13:00
ispyisail fd34a10897 Merge master (cce75f430) into feat/qetz-container
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGxcrnd2rTYfHjwTdP79zo
2026-10-13 07:44:40 +13:00
Laurent Trinques b8dd03923a TerminalStripItem add pdfRefs
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m53s
2026-10-12 16:02:26 +02:00
Kostis Anagnostopoulos 12a695a503 fix #1375 don't paste bool info 'exclude_from_bom', >
it can never be "empty" so it is always overridden on target elements.
2026-10-11 21:49:11 +03:00
Laurent Trinques 9f5406bd8c Terminal strip: add a label filter to the free terminals table 2026-10-11 13:31:30 +02:00
Laurent Trinques 6f4171c926 Cable: use begin/endFilterChange() on Qt >= 6.9, invalidateFilter() on older Qt
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m51s
2026-10-11 05:40:03 +02:00
Laurent Trinques e335aa2e55 Merge branch 'test-1438' 2026-10-11 05:15:26 +02:00
ispyisail ee0593ce7d Merge pull request #1432 from bhangart/feature/image-crop-on-folio
Crop a picture directly on the folio
2026-10-11 16:12:14 +13:00
Laurent Trinques 78ac5222c0 Cable: use invalidateFilter() instead of begin/endFilterChange (Qt 6.8 compat) 2026-10-11 05:04:50 +02:00
Laurent Trinques 1cd8fadbc4 Reapply "Add cable management: drawing, cores, numbering, cross-references and cable list"
This reverts commit 2bb3343d2d.
2026-10-11 05:00:01 +02:00
ispyisail e52b89cb84 Save As: a name typed as .qetz is a .qetz whatever the selected type
Typing "plan.qetz" over the selected "untitled" of "untitled.qet" gives
"plan.qetz.qet" with the GTK dialog, which saved a plain .qet named
.qetz.qet. Every trailing .qet/.qetz is stripped, and the project is
zipped when the .qetz file type is chosen or the typed name says .qetz.
Checked in the GUI: Save As, "saved.qetz" typed, writes saved.qetz, a
zip; launching on it shows the project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
2026-10-11 06:24:29 +13:00
ispyisail a44e816d59 Keep terminal strips in a .qetz project's database too
The project's terminal strips -- strip information, the terminals of
each physical terminal by level, bridges and what they bridge -- move to
four tables of project.sqlite, when rebuilding them from the rows gives
the same XML exactly (as QElectroTech writes them). Anything else, or
strips that carry text a save would not write, stays in project.xml.

Test: tst_qetcontainer::terminalStripsInDatabase (two strips with a
bridge and a two-level terminal on industrial.qet's own terminals: out
of project.xml, back exactly; stays in the XML when the rebuild is
broken). Outside ctest: a strip fixture saved as .qetz then .qet by
QElectroTech equals its straight resave, and the strips are rows any
SQLite tool reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
2026-10-11 06:24:29 +13:00
ispyisail cbaf40000c Keep a .qetz project's engineering data in project.sqlite
Fourth stage of the zipped project file (#1440), format 2: what an
engineer would put in a list -- folio title-block fields and
properties, project properties, a symbol's uuid, type, prefix,
information and links, a wire's ends, number, function, colour,
section, cable and bus -- moves out of the XML into project.sqlite in
the .qetz, and lives only there. The folio files keep where things are
drawn. On opening it goes back into the document before the usual
reader runs, so nothing else in QElectroTech changes yet (the program
reading the database directly is the next step of the roadmap).

Each moved item keeps a key in the XML (qetz-key: its uuid where
unique); each moved block leaves a marker where it was, which keeps the
whitespace around it apart. A block of a shape this does not model
stays in the XML. A file is untrusted: the database is read with
trusted_schema off, query_only, and only from names that are plain
tables. Format 2 readers still open format 1; a format 1 reader
refuses format 2.

Tests (tst_qetcontainer): every example back exactly, whitespace
included; databaseHoldsTheData (industrial.qet: the folio files hold no
information, links, wire numbers or properties; the row counts match).
Outside ctest: 341 of 341 projects QET can open give the same .qet
through a format 2 .qetz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
2026-10-11 06:24:29 +13:00
ispyisail fac09253a0 Open and save projects as .qetz (zipped, opt-in)
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
2026-10-11 06:24:29 +13:00
ispyisail a475383915 Split a project into the parts of a .qetz file and join it back
Second stage of the zipped project file (#1440): QetContainer::split()
turns a project document into the entries of a .qetz -- mimetype first
and stored, a manifest with the format and the oldest format allowed to
read it, project.xml with each part replaced by <qetz-part file=.../>,
one file per folio, per symbol definition and per title block template,
and folio pictures as PNG files named by their SHA-256, stored once
each. join() puts it back and refuses a file of a later format, a
missing part or anything that is not a .qetz.

A picture leaves its folio only if its base64 comes back exactly, so
nothing here can change a project. Parts are written as QET writes a
.qet and read as it reads one. Still nothing opens or saves a .qetz.

Test: tst_qetcontainer, every example split, zipped, unzipped and
joined back to the same document (whitespace between elements aside);
pictures out of the folios once each; later format and missing part
refused. With QET_CONTAINER_CORPUS it also runs on a folder of projects:
341 of 341 readable projects collected from the bugtracker, GitHub and
the forum come back the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
2026-10-11 06:24:16 +13:00
ispyisail d7ac633043 Add a zip reader and writer for .qetz projects (bundled miniz)
First stage of the zipped project file discussed in #1440 (and #865):
the zip itself, nothing reads or writes a project with it yet.

miniz 3.1.2 (MIT, one .c and one .h, no zlib) is bundled unmodified in
sources/container/miniz, as the LZMA SDK is for .edz files, so builds
need nothing new. QetZip wraps it: plain ZIP with Deflate, entries stored
where asked (pictures, the mimetype entry), written through QSaveFile so
a failed save keeps the previous file, without timestamps so that the
same project gives the same bytes. Reading refuses unsafe or repeated
entry names, encrypted entries and archives over 2 GB once unpacked.

Test: tst_qetzip (round trip, same bytes twice, names refused both ways,
truncated and non-zip input). A written archive also passes unzip -t and
Python's zipfile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
2026-10-11 06:24:16 +13:00
Laurent Trinques 2bb3343d2d Revert "Add cable management: drawing, cores, numbering, cross-references and cable list" 2026-10-10 16:49:39 +02:00
Laurent Trinques 738ffbfc36 Merge pull request #1438 from Kellermorph/wiring-manager
Add cable management: drawing, cores, numbering, cross-references and cable list
2026-10-10 16:33:12 +02:00
Laurent Trinques 0fe88758aa Terminal strip: add resistor, varistor, test socket and reversed diode types
Add ElementData::TTResistor, TTVaristor, TTTestSocket and TTDiodeReversed
(xml values "resistor", "varistor", "test_socket", "diode_reversed"),
selectable in the element editor, the terminal strip editor and the free
terminal editor, and drawn in the terminal strip plan.
2026-10-10 16:03:47 +02:00
Laurent Trinques b089c5b245 Terminal strip: remove the circles at the end of connections and wires
Draw the top/bottom connections of the terminals and the wires under
the cable as plain lines, without the small circle at their end.
2026-10-10 15:46:14 +02:00
Kellermorph 9771ca96c9 Make the cable feature speak English like the rest of the interface
English has been the source language of the program for a while now
(upstream: "Make English the source language ..."), the cable feature
came in with French ones: about 200 translatable strings over 30 files,
the two settings pages built from .ui files, four comments and one test
which asserted the old wording (blocking review comment by ispyisail).

The wording follows what the code base already uses where it exists:
Plant (=) and Location (+) as qetinformation.cpp names those element
information, Designation for the field the cable form calls its BMK,
Length, Sheet for folio, Automatic numbering, Cross-reference, and Right
aligned / Centred / Left aligned the way cabletextformatdialog.cpp
already spells the three alignment entries.

Two things are more than a word-for-word swap:

- xrefpropertieswidget.cpp:291 wrote the label of the master field on
  every type change with a hardcoded tr("Maitre"), so the English
  "Master" coming from the .ui was overwritten for every type, cable or
  not: a real bug next to being French. The wording now comes back from
  the .ui and only a cable renames the field to "Text:", so the two
  cannot drift apart again.

- tst_cabletypelist kept asserting that a new catalogue is *labelled*
  in French. That label line is the translated header and reads English
  now. The test still reads files with French headers on purpose:
  headerAliasMap() keeps the five old French labels as aliases next to
  the English ones, so catalogues written by earlier builds, including
  the one the reviewer reads her own types from, keep loading.

The .ts files are deliberately not regenerated here. Upstream
regenerates them on master, as it did for the language change itself,
and a translation is keyed on the source string: while the sources were
French a German user saw French in the cable dialogs, and an English
source string with no translation yet shows as it is.

Build clean, ctest 92/93, the one failure being the pre-existing
headless tst_menubarkeyboard.
2026-10-10 13:48:45 +02:00
Beat Hangartner 446479e8d2 Fix a crop in progress outliving an undo or the picture's removal
While a picture was being cropped on the folio:

- Ctrl+Z or Ctrl+Y of an earlier crop of the same picture left the
  crop handles where they were, 10 px beside the frame for a crop
  that starts at (10, 10).
- undoing the picture's insertion dropped the crop but left the
  picture as the folio's keyboard item, also after a redo put it
  back.

setImageSource() and leaving the scene now end the crop without
applying it. Nothing is pushed on the undo stack from there, since
both can run inside an undo command. Delete and cut are unchanged:
they deselect the picture first, which applies the crop as its own
undo step before theirs.

No file-format change. Not covered by an automated test: crop mode
needs a folio and its view, which a test can only get from the
application object library of #1431. Checked with a throw-away test
on a local merge of both branches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 11:01:29 +02:00
Beat Hangartner 8261524208 Put the Escape comment back on m_drag_cancelled
The comment "Escape pressed: ignore the rest of this mouse gesture"
explains m_drag_cancelled. The crop commit moved it onto the
declaration of dragCropHandle(), added below it. No code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:54:42 +02:00
ispyisail ada516d103 Merge remote-tracking branch 'upstream/master' into fix/report-arrow-without-terminal
# Conflicts:
#	tests/qttest/tst_resaveunchanged.cpp
2026-10-10 21:12:49 +13:00