Opening the terminal block manager reads every terminal of the project,
and builds the folios not built yet (lazy folios): on a big project it can
take a while, and the user could think that nothing happens.
TerminalStripEditorWindow::instance(), used by the menu action and by a
double click on a terminal strip of a folio, now shows a busy message
("Opening the terminal block manager...") and a wait cursor while the
window is built or switched to another project. Moved from the header to
the .cpp file.
The redraw on change was connected in setTerminalStrip() only, used when
an item is read from the project. An item added with the "Add terminal
strip" dialog is built with TerminalStripItem(strip), which did not
connect: it was not redrawn after the terminal strip editor was applied,
until the project was reopened.
Both constructors now go through connectStrip().
A spare (reserved) terminal is a terminal of a strip which is drawn on no
folio. RealTerminal already foresaw it ("a terminal set by user but not
present on any folio") but every terminal needed an element.
- RealTerminal::createSpare(): a terminal without element, with its own
data (label, type, function, led, informations: section, width, catalog
terminal, color, manufacturer...). Saved in the strip:
<real_terminal spare_uuid="..." type="..." function="..."><informations>
Older versions ignore these terminals.
- TerminalStrip::insertSpareTerminals() / insertTerminals(): insert at a
position of the strip. Undoable commands AddSpareTerminalsCommand and
ChangeSpareTerminalDataCommand.
- Terminal strip editor: "Add spare terminals..." button. The terminals
are inserted after the selected terminal (at the end if none), numbered
from its label ("X1:12" -> "X1:13", "X1:14", leading zeros kept), with
its properties if asked. The labels can be changed or removed in the
table, like the other cells; the Xref column shows "Spare". Moving a
spare terminal to the independent terminals removes it (undoable).
- Nomenclature: the spare terminals are in the new spare_terminal table,
added to element_nomenclature_view (UNION ALL, same columns), after the
folios, in the order of their strip. exclude_from_bom is honoured.
- The modified cells of the editor model are kept by real terminal, not by
element, since a spare terminal has none.
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.
The glob "????-??-?? ??-??-??" contains the trigraph "??-", which GCC
reports with -Wtrigraphs (8 warnings). The pattern is now written with
"\?" escapes, in one place; it is the same string.
The realistic styles drew every terminal of a strip with the clamp of the
layout (RealisticScrew or RealisticPushIn), whatever terminal was chosen:
a Phoenix Contact ST (spring) was drawn with screws in a screw layout.
Each entry of the terminal catalog now has its clamp: screw (Phoenix
Contact UT, Weidmüller WDU, ABB Entrelec M, Legrand Viking 3), spring
(Phoenix Contact ST) or push-in (WAGO TOPJOB S). A terminal chosen in the
catalog is drawn with its own clamp, the spring-cage ones like the push-in
ones; the clamp of the layout is kept for the other terminals, so a strip
mixing screw and spring terminals is drawn as it is.
The manufacturer chosen to filter the terminals of the Width column is
stored in the data of the terminal strip (information "manufacturer" of
terminal_strip_data, written only when set), and selected again when the
strip is shown in the editor. Each strip keeps its own manufacturer.
Older versions ignore this information.
TerminalStripEditor::apply() now starts from the current data of the strip
instead of a new TerminalStripData: the uuid of the strip was replaced by
a new one at each apply, and the new information would have been lost.
Several terminals of the catalog have the same width (6.2 mm: Phoenix
Contact UT 4 and ST 4; 5.2 mm: WAGO 2002 and Phoenix Contact ST 2,5...).
Only the width was stored, so on reopening the first entry with that
width was shown instead of the terminal that was chosen.
- New element information terminal_catalog_entry: the label of the
terminal chosen in the catalog (e.g. "Phoenix Contact ST 4").
- Choosing a terminal also fills the manufacturer and manufacturer
reference informations (bill of materials, %{manufacturer_reference}),
only when they are empty or still hold what the previous catalog entry
wrote: a value typed by the user is never overwritten.
- Element editor: the manufacturer and the exact terminal are restored on
reopening. A width typed by hand, or stored before this change, stays a
plain width instead of taking the first terminal with the same width.
- Terminal strip editor: new "Manufacturer" combo filtering the terminals
proposed in the Width column (with the section filter); the column shows
"6,2 - Phoenix Contact ST 4", and copy/paste keeps the terminal.
Existing projects are unchanged: without the new information, only the
width is used, as before.
checkContinuity() reported potential_mismatch on "cable" whenever the
conductors of one potential ran in different cables. A conductor held
by a cable carries its own core's label in the cable field (15W1:BN,
written by CableManager); a potential spread over several cables is the
normal case, e.g. a neutral fed from a terminal strip into each room.
On a house installation every N and PE potential was flagged.
Conductors held by a cable are left out of the cable comparison; the
free-text cable field of other conductors is still compared.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ST 1,5 (4.2 mm, 1.5 mm²), ST 2,5 and ST 2,5-QUATTRO (5.2 mm, 2.5 mm²)
and ST 4 (6.2 mm, 4 mm²), next to the UT screw terminals already listed.
Widths from the Phoenix Contact catalog and data sheets (ST 2,5-QUATTRO
3031306, ST 4 3037151, ST 1,5 3031076).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel shrank to its title bar whenever the selection had nothing it
could edit (a click on an empty spot, several elements, a mix of items):
its content layout used QLayout::SetMaximumSize, and an empty vertical
layout's maximum height is 0. The other right-hand panels took the room,
and when an element was selected again the panels were split again from
their size hints, so the height the user had dragged the panel to was
lost (reported by ankostis in #1439).
With the default size constraint the panel keeps its height and stays
blank while there is nothing to edit. The element, wire, text and shape
editors draw the same: their screenshots in a 730-860 px tall panel are
pixel-identical before and after.
Test: tst_selectionpanelheight (on master the height goes from 560 to
19 px).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selecting several elements left the Selection properties panel blank and
Edit (Ctrl+E) disabled, so giving many elements the same supplier or
manufacturer meant editing them one by one.
The panel now shows the Information fields for two or more elements, or
for elements selected with other items (left unchanged, and counted in
the panel's header). Ctrl+E opens the same editor as a dialog when only
elements are selected.
- A field shows the value every element shares, or "Multiple values".
Left alone, each element keeps its own value; typed text goes to every
element; the erase box clears the field on every element.
- A restore button per field, and Reset, bring back the values the
elements had.
- One apply is one undo step. The keystrokes typed in the panel merge
into one step but never into an earlier change of the same elements
(a paste of element information, another edit).
- Numbering scheme, label freezing and the number picker are left out:
they are about one element. Custom properties are kept as they are.
Also:
- An element's own texts, selected with it by Select all, count as part
of the element, not as other items.
- ElementInfoWidget::reset(): Reset in the element dialog now brings back
the Information tab's values; it did nothing.
- The panel opens an element's editor on the tab picked last, instead of
the first tab, after the selection went to something else and back.
Test: tst_multielementinfo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The conflict was tests/qttest/CMakeLists.txt, where #1520 added
tst_savedstamps at the place this branch adds tst_joinedwireuuid. Both
are kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The symbol editor has no sheet to fit: its "Fit in view" action zooms on
the drawn parts (elementSceneGeometricRect()), which is what the folio
editor calls "Zoom content". Use that command's zoom-draw icon and name,
and give the action a status tip like the folio editor's.
The object name and the shortcut id stay the same, so saved shortcuts
and toolbar layouts keep working.
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
Older versions' copy and paste could leave two symbols with the same
uuid. Wires, links and the project database name a symbol by its uuid,
so a copy was ambiguous: #1417 worked around it for wires, but the
project database keeps one row per uuid, and a link to the uuid could
mean either copy.
When a project opens, every symbol carrying a uuid another symbol also
carries gets a uuid of its own, derived from the shared one and its
folio's uuid, so the same file gives the same result on every open. The
copy that keeps the shared uuid is the first one linked to another
symbol (its partner's link names that uuid), else the first. It is done
once the folio's wires are connected, so #1417's choice of which copy a
wire meets is kept. Folios holding such a copy are built when the
project opens, also with lazy folios.
Only files with shared uuids change, once: 3 of the 341 corpus projects.
tst_sharedsymboluuid: the lamps given one uuid now come back with three;
a linked contact and an unlinked symbol sharing its uuid keep every link
and wire, the linked one keeping the uuid. Both fail without the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
Deleting a symbol whose terminal held several wires joins the wires
left behind with a new one. That wire got a random uuid, so the same
deletion saved a different file every time: a diff for nothing in a
project kept in git (discussion #1512), and two copies with the same
edit could not be compared.
Its uuid now comes from its two ends (each end's symbol and terminal,
sorted), the recipe a wire saved without a uuid already gets on load
(Diagram::fromXml(), derivedItemUuid()). Undo and redo keep the same
wire, so its uuid too.
tst_joinedwireuuid deletes the coil KA1 of examples/affuteuse_250h.qet
in two runs: both saves are identical and no wire has a random uuid.
Without the fix one wire has a random uuid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
The title block variables of a save (saveddate, saveddate-eu,
saveddate-us, savedtime, savedfilename, savedfilepath) were set after
the file was written, and also when a project was opened. A file
therefore held the previous save's date and path: after editing and
saving, the title block showed the date of the save before. And every
save but the first in a session wrote the time of the one before, so a
project saved twice without a change gave two different files, a diff
for nothing in a project kept in git (discussion #1512).
stampSaved() now sets them before writing, when the project has
unsaved changes (the modified flag or the undo stack moved since the
last save, as the window's "[modified]" uses) or is written to another
file. Otherwise they keep what the file holds, and opening a project no
longer sets them.
tst_savedstamps: an unchanged project saved twice in place is
byte-identical; a moved symbol then a save gives today's date and the
file's own path. Both fail without the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
A terminal in a symbol's definition saved without its orientation
attribute crashed QElectroTech whenever the project was opened, in the
GUI and with --resave alike: Qet::orientationFromString() read the
first character of an empty string. Hand-edited or generated symbols,
or a damaged file, can hold such a terminal.
An empty string is now an inconsistency like any other, and the terminal
faces north, as the function's comment already said. Every non-empty
string gives the same orientation as before.
tst_terminalnoorientation runs the real binary's --resave on
fixtures/terminal_no_orientation.qet (unlinked_contact_label.qet with one
terminal's orientation removed): it crashes without the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
In the realistic styles, some types of terminal get the parts seen on
the real products:
- fuse terminal: black hinged fuse holder on the middle of the terminal,
with its hinge pin, a window on the fuse, a ribbed grip and, for a
fuse terminal with led, a red led
- sectional (disconnect) terminal: orange knife with its hinge and grip
- test terminal: two brass test sockets between the wire entries and
the middle band (in the middle band for a terminal with levels)
The marker tag and the type symbol are drawn on the lever. The lever is
shorter with screw clamps and stays on the middle band for a terminal
with levels.
The realistic styles don't write the xrefs, so each terminal becomes a
clickable zone, like the xref text of the schematic style:
- double-click on a terminal goes to its element on the folio
- the terminal under the mouse is outlined in blue, and a tool tip
shows its label and its xref
- the zones are exported as clickable links in the PDF
A terminal with one level is clickable on its whole body; a terminal
with levels is clickable on the rows of wire entries of each level.
Only the terminals with an xref (element placed on a folio) are
clickable. The zones use the same physical / real indexes as the
schematic style, so the double-click and the PDF export need no
other change.
Add two realistic drawing styles for the terminal strip plan, besides
the existing schematic one:
- Realistic (push-in) and Realistic (screw): the strip seen from above,
at scale (real width of each terminal), like a cabinet mounting
drawing: end stops, group marker carrier with the strip name, one row
of wire entries per level, bridge channels with red bridges, marker
tags with the terminal number, type symbol (fuse, LED, diode,
sectional, ground...), green/yellow PE and terminal color.
The style is chosen for each plan in the "Add terminal strip plan"
dialog and saved with the item (attribute draw_style of
terminal_strip_item). Items saved before keep the schematic style.
Several plans of the same strip can use different styles.
The values used by the realistic styles are in the layout (scale in
px/mm, terminal length in mm, end stops, group marker) and can be set
in the layout editor, whose style combo box only changes the preview.
Separator plates: a new "Separator plate after" check box column in
the terminal strip editor, stored in the terminal element informations
(key terminal_separator). Drawn as a plate at scale in the realistic
styles and as a thick line in the schematic style.
New files: realisticterminalstripdrawer.{h,cpp} (realistic drawing) and
terminalstripdrawerhelpers.h (helpers shared with the schematic drawer).
GeneralConfigurationPage::applyConf() wrote qet-assistant.json before it
applied the collection and title block paths, so the file kept the paths
the dialog had just replaced. The qet MCP server hands the user and
company collections from that file to every headless run it starts: a
company collection chosen in the dialog stayed unknown there, and
company:// paths failed to resolve until QElectroTech was started again.
Write the file last. tst_assistantinfopaths picks a new company and user
collection on the page, applies it and reads the file back; it runs in
QStandardPaths test mode from main() on, since QETApp writes the file to
the standard data folder as its editor opens, whatever --data-dir says.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With lazy folios, removing a folio left the tables on folios not built
yet as the file had them. A table split over several folios keeps its
query in its first part; when the folio holding that part was removed,
the next part, built later from the file, named a part that was gone and
was saved with no query. A parts list that listed the removed folio's
symbols also kept its old size. With every folio built neither happens.
Removing a folio that holds a table or a symbol now builds the folios
holding tables first, while the removed folio is still in the project.
A folio with neither builds nothing, as tst_lazyfolios checks.
buildFolios() now holds the project database's update signal back and
sends it once at the end, as pasting does: without that every item built
made every table lay itself out again, and the save after a removal on a
50-folio project took about 270 s instead of 4.5 s. The rows are still
written as each item is built, so no full refill of the database is
needed (one per build made a 136-folio project's edit 20 % slower).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
Every marker tag of a strip has the length of its longest terminal
number (at least two digits), so three-digit numbers are no longer
reduced. The font is still reduced if a number doesn't fit the text
zone of the layout.
- The xref is drawn as plain text, without marker tag.
- Every marker tag of the terminal numbers has the same length
(two digits), the font is reduced when the number is longer.
- The body of every terminal reserves the zone of the longest xref
of the strip, so all terminals of a strip are drawn the same way.
- Draw only the terminal number (part after the last ':'), the strip
name is already in the header; the manager and the tree still show
the full label.
- Clip texts to the width of their terminal and shrink the font in
narrow terminals so they no longer overlap.
- New housing: body with narrower wire entry caps and a slope between
them, grey by default (terminals with a color or a ground type keep
theirs); stripes are clipped to this shape.
- White marker tags behind the number and the cross reference, fitted
to the text and its alignment; black ribbed tags for isolating
(sectional) terminals.
- Clamping parts (screw in a window, castle tab, big center circle, tab,
screw) drawn in the free zone between the two tags, whatever the
layout; fewer parts if the zone is small.
- Remove the wire entry mark that overlapped the cross reference text.
- Draw only the terminal number (the part after the last ':') instead of
the full label (e.g. "1" instead of "XAUZ1:1"); the terminal manager
and tree still show the full label.
- Clip each text to the width of its terminal and shrink the font in
narrow terminals so that texts no longer overlap their neighbours.
- Remove the wire entry mark in the lower part of the terminal body,
which overlapped the cross reference text.
Extend the terminal catalog (terminalcatalog.h) with the widths of
more terminal blocks, from the catalogs of Phoenix Contact (UT),
Weidmuller (WDU), WAGO (TOPJOB S 2002/2006), ABB Entrelec (M series)
and Legrand (Viking 3). For Legrand and ABB Entrelec the value is the
pitch given by the catalog.
Each entry now carries its manufacturer, and the element properties
dialog gets a "Manufacturer" combo box before the width one. The list
of widths is filtered by manufacturer and by the selected section; if
the manufacturer has nothing for that section, all its terminals are
listed. The combo box is created by the code, no change in the .ui.
The section combo boxes of the terminal strip editor and of the
element editor now show the nearest AWG / kcmil size next to the
section in mm2, for example "2.5 mm² (AWG 14)".
The correspondence is approximate (the two systems don't have the
same steps) and is stored in TerminalCatalog::awgForSection().
The stored value is unchanged (terminal_section, in mm2).
Terminal block elements can now define, in the element properties
dialog (Terminal block element group):
- the section in mm2 (combo box with the usual sections, editable)
- the width / manufacturer reference (Weidmuller, Phoenix Contact,
WAGO, ABB Entrelec), filtered by the selected section
- the color, using the palette of the terminal strip (editable,
accepts a name, #rrggbb or #rrggbb/#rrggbb)
Values are stored in the element informations (terminal_section,
terminal_width, terminal_color) with the dot as decimal separator,
the keys already read by RealTerminal. An empty value removes the key.
The sections and the manufacturer widths move from
terminalstripmodel.cpp to the new header-only terminalcatalog.h,
shared by the terminal strip editor and the element editor.
Conflicts: the lock dialog and the recovery dialog both kept in
qetdiagrameditor.cpp; the lock and previous-versions settings both kept.
The spacer in the General settings page moves to row 9, below the lock
checkbox, which the merge had left on the same row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgPFANhWgD66V3v9KVKV58
Two people opening the same project on a shared drive could both edit it,
and the last save silently wiped the other's work. A project now has a
hidden lock file beside it (.~plant.qetz.lock, small JSON: user, host,
process, version, since, heartbeat). Opening a project another live session
holds shows who has it, since when and how long ago they were last active,
and offers Open read-only (default), Open anyway, or Cancel.
- The lock is made with an exclusive create, refreshed every minute and
deleted when the project closes. It is not a QLockFile: that treats any
lock from another machine as stale after 30 s. Here only a lock made on
this machine by a process that no longer exists is taken silently.
- A folder the lock cannot be made in, or the setting turned off
(Settings > General > Projects), opens as before.
- Save As and .qet saved as .qetz move the lock to the new file, and refuse
a file someone else has open. A project opened read-only for another's
lock does not save over that file.
- --resave and --set-titleblock refuse a file someone has open unless given
--ignore-lock; the read-only verbs never look at locks and headless runs
never take one. A script's qet.save() refuses the same way.
Checked: 17 new unit tests (5 go red when stale detection and the exclusive
create are broken), full ctest 107/107, the dialog and both choices in the
GUI under Xvfb, and --resave refused/allowed from the command line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Saving replaced the project file whatever had happened to it since it was
opened. If another user, another program or a second QET had saved the same
file in between (a shared drive, a copy synced from another PC), their work
was lost without a word.
QETProject now remembers the project file's size and modification time when
it reads it and after each save of its own. Before writing, write() compares
them with the file on disk and, if they differ, writes nothing and returns
"someone else changed the file on disk at <time> since it was opened".
- Interactive save (Ctrl+S, and the save on closing a project) asks:
"Someone else changed <file> on disk at <time> since you opened it" with
Save as a copy (opens Save As), Overwrite, or Cancel.
- Scripts (--run), the command line, MCP and the autosave timer have no one
to ask, so they keep the refusal (autosave logs it). Setting
QET_OVERWRITE_CHANGED=1 makes a script overwrite on purpose.
- A file that does not exist yet, and a Save As to another name, are not a
conflict; after Save As the remembered state is the new file's.
No change to what is written, so .qet and .qetz stay as they were.
Evidence: new tst_changedondisk (unchanged file saves twice; a file written
behind the open project is refused; the override overwrites; a Save As
target is no conflict) fails on the changed-file case without the source
change and passes with it. Full ctest 107/107. Physical check with
qet-gui-dialog.sh: file modified behind the open project, Ctrl+S shows the
three-button dialog.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgPFANhWgD66V3v9KVKV58
With a .qetz and lazy folios (both the default), changing only the
title, author, date or revision of a folio holding a picture, while that
folio was not displayed, then saving, gave a .qetz that refused to open:
'picture "images/<sha>.png" is missing'.
Such a folio is saved from its items as read, with its new header
(Diagram::deferredXmlWithHeader()). Read from a .qetz, its <image> names
its picture's file (qetz-file) instead of holding the picture, and
QetContainer::split() takes out only pictures held inline: the file was
never written, while the folio still named it. The folio now gets its
pictures written in, from the bytes the project keeps
(QETProject::containerPicture()), as a built folio writes them.
On master, a picture on folio 2 of grafcet, iso_sfc_example, 741,
tremie_vibrante or Projet_vierge is lost this way; found on three forum
projects by comparing lazy saves with saves of every folio built. Saving
as .qet was not affected.
New tst_qetzheaderpictures (grafcet.qet: picture on folio 2, saved as
.qetz; its title changed with folios built when needed; reopened with
its picture and title): fails without this change ("picture ... is
missing"). ctest 107/107.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>