Opening a project dropped, with only a debug line, a symbol whose
definition could not be found (missing from the project's collection and
from the library) and every wire on it; and a wire whose terminal could
not be matched, after the symbol's definition was redrawn. The next save
wrote the project without them. One save of a forum upload whose
collection was incomplete kept 1 of 219 symbols and none of its 299 wires.
A folio now keeps what it could not load as it was read
(Diagram::m_kept_symbols, m_kept_wires) and writes it back when it is
saved whole (Diagram::writeKept()):
- a symbol, its terminals given ids after those of the folio's symbols;
- a wire, an end found written from its terminal as Conductor::toXml()
does, an end not found as the symbol and terminal uuids it names; for a
wire saved by legacy ids, the terminal's uuid derived from where it was
saved (Element::savedTerminalUuid(), out of matchMovedTerminals()), so
it joins again once the definition has it there. A wire whose ends
cannot be read, or an end naming no terminal of the file, is kept so
that it joins nothing.
A symbol kept counts as waiting (projectDataBase::waitingSymbols()), so a
partner keeps its link to it. The open warning now lists the symbols not
loaded too, and says both are kept.
Once the definition is back, the symbol and its wires are built again:
741.qet with one transistor definition removed (8 symbols, 33 wires on
them) saves all 67 wires, and with the definition put back opens with
all 65 symbols and 67 wires, the same folio as before.
tst_resaveunchanged::unloadableItemsKept: fixtures/coil_contact_wired.qet
(coil, linked contact, two wires; built with the script API) with the
coil's definition removed, or the contact's terminals moved and given new
uuids: both symbols, wires and links saved, a second save the same, and
the coil's definition put back gives the undamaged project's symbols and
wires. Fails on master (1 symbol; 0 wires).
FINDINGS F127, F128.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A symbol built after opening (lazy folios) links to the partners its
link rows name, and to the symbols whose rows name it; its file's list
only adds what the rows miss. The rows keep a partner on a folio not
built until it is built, in its saved place, so a symbol no longer keeps
a list of its own: the list read from the file is dropped once opening
has filled the database. pendingLinks(), waitingLinks(),
keepsSavedLinks(), linksToSave() and QETProject::waitingLinkTargets()
go; the callers that asked a symbol for its waiting partners ask the
database (waitingPartners()).
When its links change, a symbol's rows are written from its built
partners and the waiting partners its rows held, in their order
(rowsToWrite()), so a full rewrite of the table keeps them too.
A symbol built alone as a partner on a folio not built
(Diagram::buildSymbols()) counts as built: its rows are kept, it is not
waiting (waitingSymbols(), waitingPartners()), so saving does not build
its folio, and removing it does not remove the rows naming it. A row
naming a placed symbol that has not linked yet (its folio being built)
is kept too. At opening the database holds no link until its fill
(forgetLinks()); the symbols read their files' lists until then, and the
fill takes their order: a symbol's own rows, its list, then the rows
naming it. The symbols waiting on folios not built are cached and
updated as folios are built.
QET_CHECK_LINK_ROWS now checks the rows against the links: each built
partner in its order, no other row but to a folio not built.
tst_resaveunchanged::undoKeepsLinksToUnbuiltFolios: m_000 opened with
lazy folios, a contact, its folio or its coil's folio (not built)
removed, then undone: every link saved as before.
QETZ-DB-FIRST stage 2a-3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TerminalStripItem was connected to nothing: after applying the terminal
strip editor (width, catalog terminal, color, type...), undo or redo, the
strip drawn on the folio kept its old drawing until something else made
the scene repaint, and its bounding rect was not updated when the width of
the terminals changed.
- TerminalStrip::dataChanged() is emitted when the data of the strip is
set, and when the data of the element of one of its terminals changes
(Element::setElementData(), Element::setElementInformations()).
- TerminalStripItem connects to dataChanged(), orderChanged(),
bridgeChanged() and bridgeColorChanged() of its strip, and updates its
geometry and redraws itself.
A symbol's <links_uuids> are now what the project database's link rows
list for it, in their order (projectDataBase::savedLinks()), instead of
a list the save built itself from the symbol's partners: QETZ-DB-FIRST
stage 2a, the save reads links from the database. A symbol the database
does not hold (saved outside a project) still lists them itself.
The rows leave out a partner the project no longer has, so removing a
folio with every folio built no longer saves links to its symbols, as
lazy folios already did (the links part of F113). Every other save is
unchanged.
tst_resaveunchanged removedFolioLeavesNoLinkToIt: affuteuse_250h with
every folio built, folio 3 removed: 4 links named symbols no longer in
the file, none now.
Needs #1522 (each symbol its own uuid) and #1523 (rows in saved order);
local only until both are merged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
A folio report whose text is a composite text replaced only %{label}
and the conductor variables. The report's own information is now
replaced as well, as on any other element, so a report can name the
device and terminal it leads to: "%{comment} %{label}" reads
"-XS27:N +UV1/2 UG Raum - 1.1" like EPLAN's interruption points.
Changing a report's information rebuilt such a text the way an ordinary
element's is built, turning %{label} into the report's stored label,
where its partner was when last saved. It now takes the same text
updateReportText() builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A folio report could name the folio it points to by position (%f), folio
number (%F), installation (%M) and location (%LM), but not by its title.
EPLAN and other tools print references like "+UV1/1 Versorgung UG/EG.0",
which with %T is the formula "+%LM/%F %T.%c".
- FormulaContext carries the folio title, filled from the title block.
- %T is replaced on its own only, not as the start of a longer word.
- A report whose formula uses %T follows the target folio's title block
(BorderTitleBlock::informationChanged), as %F already follows its
folio number.
- The report and cross reference property pages list %T.
tst_reportfoliotitle checks the replacement, that %total and title
block variables keep their values, and an empty title.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The project database's link table should say what each symbol saves:
QETZ-DB-FIRST stage 2a moves link reading and writing to it. Measured over
the corpus and 25 kinds of edit, its rows disagreed with the file for
three reasons; this fixes the one in the database:
- rows followed the order partners were built in, not the order the
symbol saves them (11 symbols, mostly .qetz files where an edit built a
partner's folio, with lazy folios);
- the rows were written by three pieces of code with three rules: the
rebuild and the flush from the built partners then the waiting ones,
the save from its own list.
Element::linksToSave() is now that one list: the order the symbol was
saved with, then partners linked since, a partner on a folio not built
included. toXml() writes it and the database's rows are written from it
(projectDataBase::insertLinkRows()), with their place in a new ord
column of the in-memory table (the .qetz file's own link table is not
touched). A built partner the project no longer has, on a removed folio,
still gets no row, as before.
The other two reasons are not here: symbols sharing a uuid (PR #1522)
and links saved to symbols on a removed folio (F113), which the save will
stop writing once it writes from the database (stage 2a-2).
QET_CHECK_LINK_ROWS=1 says at each save whether the rows are what the
symbols save, without building any folio. tst_resaveunchanged
linkRowsAfterLazyEdit moves a symbol of m_000 as a .qetz with lazy
folios: with the previous order, 2 symbols differ.
No saved byte changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
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>
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>
A report's texts that show a wire's properties (function, section...)
are only filled once the wires are read, and those are read after the
symbols: the fill comes from the 100 ms timer of
DynamicElementTextItem::conductorWasAdded(), by which time the project
state is Ok again and the alignment is live.
For a report whose partner is linked, that fill had already happened
inside the loading state (Element::initLink() -> linkedElementChanged()
-> reportChanged()), so the alignment was blocked and the text stayed
where the file put it. buildFolios() relies on that and says so. An
unlinked report, and one whose partner sits on a folio not built yet,
got nothing during loading: the first fill was the timer's one. It took
the rect of the still empty text for anchor and shifted the item by the
whole width of the text, on every open. Only the right, centred or
bottom aligned texts showed it -- the others have a null offset.
Fill those texts inside the loading state instead. Element::
updateConductorTexts(), already run when a wire is connected to a
terminal, now covers a report as well as a wire label, and
Diagram::refreshContents() runs it for every symbol while the project
is still parsing, right after initLink() has resolved the links. The
timer's fill that comes 100 ms later then finds the text already set,
and DynamicElementTextItem::setPlainText() returns on it unchanged, so
the alignment is never handed an empty rect to anchor. A wire that
appears, or a property that changes, while the project is open is
aligned as it was.
A report's "label" text is left to updateReportText(): it is the linked
folio's, not the symbol's own information.
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>
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>
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>
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>
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>
Each picture of a folio was decoded in turn while its item was built,
a quarter of a second for a 12 megapixel photo. Diagram::fromXml() now
reads every picture of the folio first, decodes them all at once on a
pool of its own (one thread per core, at most one per picture), then
builds the items in file order in the GUI thread as before.
The decoding (base64 text, then the picture file) moves out of
DiagramImageItem::fromXml() into decodePicture(), which touches only
its argument. QImage::fromData() then QPixmap::fromImage() is what
QPixmap::loadFromData() does, so the pixels are the same.
On a 263 MB project with 22 photos on one folio: --info 21.3 s ->
12.5 s, the folio's content 10.6 s -> 3.2 s, opening its .qetz 15.6 s
-> 9.3 s. Peak memory +220 MB (1.81 -> 2.03 GB), the pictures' text
being converted by several threads at once. A resave is byte-identical
to master's, and so is a PNG export.
tst_folioimageload: six pictures come back with their own pixels and
positions, and an <image> without a picture does not stop the others.
It fails when the decoded pictures are handed to the wrong items.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1470 keeps a .qetz's pictures out of the document; the index path still
reads them in, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
A .qetz keeps each folio picture as a PNG file. On opening, join() put
every picture back into the document as base64 text, and the folio then
decoded that text again, twice. On a project with 20 pictures of 7 MB
(140 MB of PNG, the shape of the project on PR #1462) that cost about
8 of the 12 seconds the open took:
QDomDocument::createTextNode() on the base64 3.9 s
DiagramImageItem::fromXml(), base64 decoded 3.9 s (2 x 1.9 s)
the PNG decoding itself 0.3 s
join() now takes an optional map. Given one, each picture stays a file:
the <image> keeps its qetz-file attribute and the bytes go in the map.
QETProject keeps the map for as long as the project is open, and
Diagram::fromXml() hands a picture's bytes to the new
DiagramImageItem::fromXml(element, picture). Kept, not cleared after
the load, so a folio built later finds its pictures too; the bytes are
shared with the PNG cache of the pictures built from them, so this costs
almost no memory. readDocument() without a map, as the automatic
numbering import uses it, still gives the whole document.
fromXml() of a .qet picture also decodes its base64 once instead of
twice: the first text node, which the PNG cache already used. e.text()
also collects <image_base>'s text, but loadFromData() stops at the end
of the first picture, so the picture shown is the same.
Measured, same file, 3 runs each, offscreen:
.qetz 11.8 s -> 3.8 s, peak memory 1034 MB -> 655 MB
.qet 15.7 s -> 13.6 s
Resaving either file with either binary gives the same bytes, a cropped
and colour-keyed picture included, and every folio exports to the same
PNG pixels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The checkbox of a terminal element was an all-or-nothing switch: while
it was unticked every conductor property (function, wire colour,
section, number...) crossed to the other side of the terminal, and
once it was ticked nothing did at all.
Rename it to "Function separation" and let one property still cross:
the voltage / protocol, which describes the electrical node itself
rather than one side of it. Conductor::functionSeparatedConductors()
returns the conductors reachable only by crossing such a terminal --
the potential computed with the separations ignored, minus the one
that respects them -- and
ConductorProperties::applyTensionProtocolChange() gives them that one
property, in the conductor properties dialog, the selection panel
multi-edit, the script API and search/replace.
The stored element information stays "potential_isolating", so
existing symbols and projects keep their setting, and the existing
sub-case of terminals sharing a potential-group identifier is
untouched. The German translation of the new label is deliberately
left out of this commit and belongs with the other .ts updates.
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>
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>
A folio report arrow's text looks for a wire to show: on the arrow
itself, else on the arrow it is linked to, through that arrow's first
terminal. Some report symbols have no terminal -- ref_voyant_2_h.elmt in
the collection, and older collections' arrows -- so a project linking an
arrow with no wire to one of them crashed QElectroTech as it opened.
The other arrow's terminals are now checked first, as the arrow's own
already were.
Found on a project attached to the forum (saved by 0.70), which crashed
on opening.
Test: tst_resaveunchanged::reportArrowWithoutTerminalOpens builds the
case from Projet_vierge.qet's own symbols; it crashes without the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
Element::linkedElements() sorted the element's own list of linked
elements by position before returning it, so any caller reordered what
is saved. #1441 restores the file's order after linking; #1442 fills the
link table through linkedElements(), which sorted it again, so on master
a coil's contacts are saved in position order and
tst_resaveunchanged::savedLinkOrderKept fails.
linkedElements() now returns a sorted copy. Callers still get the list
by position; the saved order is left alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
A summary or parts-list table split over several folios saves its data
with its first part only; each later part names the part before it.
Deleting a folio keeps its tables in memory for undo, still in the
chain, so saving then named the deleted part as the previous table of
the next one. On reopening, every part after the gap had no data, and
closing the project crashed: the first orphaned part, destroyed, handed
its missing data on to the next part through a null model.
toXml() now names the nearest previous part still in the project, or
saves the data if there is none. setPreviousTable() no longer copies a
null model, so files already saved this way open and close.
Tests (tst_resaveunchanged): missingPreviousTableOpens,
deletedFolioKeepsTableChain; both fail without the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf