Commit Graph

455 Commits

Author SHA1 Message Date
ispyisail 4fee464919 Merge pull request #1552 from ispyisail/fix/keep-unloadable-items
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m43s
Fix symbols and wires lost when their definition cannot be found
2026-10-19 20:58:13 +13:00
ispyisail 289e33d06e Merge remote-tracking branch 'upstream/master' into fix/keep-unloadable-items 2026-10-19 20:21:02 +13:00
ispyisail 46d8588113 Fix a closed project staying in the element collection panel
A project opened before the element collection panel's first load is
queued until the load starts; one opened during a load goes into the
model being loaded. ElementsCollectionWidget::removeProject() only
removed the project from the model already shown, so a project closed
in either window stayed in the collection, and the next reload read the
freed project (crash in XmlElementCollection::root()).

removeProject() now also takes the project out of the queue and out of
the model being loaded. That model waits for its loading threads, which
read the project, and drops the project's items from the list applied
when the loading ends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:28:35 +13:00
ispyisail 31730678c5 Fix a crash on clicking a sheet of a project closed without saving
Closing a modified project and choosing Discard left the project in the
project tree. The panel removed its item, but the project still emits
projectInformationsChanged() while it is torn down (setModified()), and
GenericPanel answered by adding the project back, with items for its
sheets. Once the project was freed, a click on one of those sheets
called diagram->showMe() on a freed Diagram: SIGSEGV in
QObjectPrivate::maybeSignalConnected(), the backtrace reported on the
forum (topic 3215).

The panel now stops listening to a project when it is closed, to its
sheets and to its embedded title block collection, before removing its
item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:28:31 +13:00
ispyisail 833ed0f523 Keep in a .qetz what QElectroTech cannot compute for a symbol it cannot build
The .qetz leaves out of its folios what QElectroTech computes when it
builds them (format 4): a symbol's terminal list and information texts,
a wire's description of its ends. A symbol whose definition the project
does not hold is never built, so none of it came back: through a .qetz,
the symbols and wires kept by the previous commit lost their terminal
lists, texts and end labels. QetContainer::dropComputed() now leaves a
symbol whose definition is not in the project's collection
(embeddedDefinitions()), and the wires ending on it, as they are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:21:45 +13:00
ispyisail 7f05b23062 tst_movedterminals: count the wires joined, a wire not joined is kept now
A wire whose terminal is not matched is saved back as it was read (F128),
so the saved file holds it: the cases count the wires joined (saved, less
those QElectroTech says it did not load), and the cases that refuse a
match check the wire is still in the file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:11:24 +13:00
ispyisail 0bcd0ca4b6 Keep the symbols and wires a folio cannot load, and save them back
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>
2026-10-19 16:45:55 +13:00
ispyisail 266d1e2573 Merge pull request #1540 from hairykiwi/fix/elements-panel-crashes
Guard elements-panel auto-select against a detached project item
2026-10-19 16:00:33 +13:00
ispyisail 3abaf21302 Merge pull request #1543 from ispyisail/fix/save-keeps-qet
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m54s
Fix Save writing a .qetz beside an opened .qet: keep the .qet
2026-10-19 15:07:41 +13:00
ispyisail 171a597b12 Merge pull request #1546 from ispyisail/refactor/link-from-db-rows
Link symbols on folios opened later from the project database
2026-10-19 15:07:21 +13:00
ispyisail 4d61909fd1 Merge pull request #1545 from ispyisail/refactor/save-links-from-db
Fix removed folios leaving links to their symbols in the saved file
2026-10-19 15:07:03 +13:00
ispyisail 9a41e01187 Link a symbol built to its partners from the project database's rows
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>
2026-10-19 10:47:41 +13:00
DaTweety ba64fd5164 %T last: a folio title keeps the variables it contains
The folio title was put into the formula before %c, %l, %prefix, the
wire variables, the title block and project variables and the
sequences, so a title such as "Schema %c" had them replaced inside it.
It is replaced after all of them now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:52:06 +02:00
ispyisail b473a07b7d Save a symbol's links from the project database
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
2026-10-19 01:50:35 +13:00
hairykiwi 959695ed5c Add regression test for elements-panel project-open auto-select crash
- tst_elementspanelprojectopen drives ElementsPanel::projectWasOpened() --
  the slot the application connects to a project being opened -- on a fresh
  panel whose first reload() has not run, reproducing the detached-item
  ordering deterministically offscreen.
- On master without the guard the test fails: the auto-select leaves
  currentItem() pointing at an item whose treeWidget() is not the panel (and
  on some builds crashes in QTreeModel). With the guard it passes: no item is
  selected while the project is detached.
- Registered via qet_add_app_test(), which links the application code and
  runs a real QETApp offscreen.

Generated-by: Claude Code
Signed-off-by: hairykiwi <174199+hairykiwi@users.noreply.github.com>
2026-10-18 13:44:35 +01:00
ispyisail 89c3979957 Fix Save writing a .qetz beside an opened .qet: keep the .qet
A project opened from a .qet was saved as a .qetz beside it, with a
notice, and the .qet left as it was: a project kept in git did not stay
a .qet. Save now writes the format the project was opened in; only Save
As changes it (scorpio810, discussion #1512). A .qetz is saved as a
.qetz as before.

The save-beside path goes: the flag set on opening, the .qetz path made
from the .qet's, write()'s switch to it and back on failure, and the
notice and replace question in ProjectView::doSave().

tst_resaveunchanged::qetSavedAsQetzBeside becomes qetSavedAsQet: Save of
an opened .qet writes no .qetz and the .qet holds what a plain save of
it holds (it fails without the fix: a .qetz is written beside).
tst_changedondisk makes its .qetz with Save As, as Save no longer does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:56:22 +13:00
DaTweety 1080188f6e Add %T, the target folio's title, to report and cross reference formulas
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>
2026-10-18 11:47:48 +02:00
ispyisail 8c5a16d50e Merge pull request #1535 from ispyisail/fix/link-rows-test-skip-without-qml
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m39s
Fix the Linux CI failing since #1523
2026-10-18 22:31:50 +13:00
ispyisail a715ff25ff Merge pull request #1534 from ispyisail/feat/multi-symbol-info
Add editing the information of several symbols at once (#1439)
2026-10-18 22:31:36 +13:00
ispyisail ff1a81d85d Fix the Selection properties panel losing the height the user gave it
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>
2026-10-18 22:29:54 +13:00
ispyisail e05b511cb2 Tests: skip linkRowsAfterLazyEdit on a build without --run
linkRowsAfterLazyEdit() (#1523) edits a project through --run, which a
QElectroTech built without Qt Qml does not have. The Linux CI builds it
that way, so the test has failed there since #1523 merged, on master
(e46bb97c5) and on every pull request after it: "MOVED true" is never
printed. It now skips, as the five other --run tests of
tst_resaveunchanged do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 21:51:25 +13:00
ispyisail ce0798bb18 Add editing the information of several elements at once (#1439)
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>
2026-10-18 21:44:00 +13:00
ispyisail 8db65ddaeb Merge master into fix/joined-wire-uuid
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>
2026-10-18 20:50:24 +13:00
ispyisail e46bb97c5b Merge pull request #1523 from ispyisail/refactor/link-rows-in-place
Fix the project database listing a symbol's links in another order than its file
2026-10-18 19:53:12 +13:00
ispyisail 5cba4dd7d7 Merge pull request #1522 from ispyisail/fix/shared-symbol-uuid-renew
Fix symbols sharing a uuid: give each copy its own when a project opens
2026-10-18 19:52:54 +13:00
ispyisail b36d9fc9a9 Merge pull request #1518 from ispyisail/fix/windows-test-failures
Fix the two tests that fail on Windows CI
2026-10-18 19:52:27 +13:00
ispyisail ee2a060b16 Write a symbol's link rows in the order it saves them, with its waiting partners
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
2026-10-18 16:35:58 +13:00
ispyisail 1c0deb2bc8 Give each copy of a symbol sharing a uuid its own uuid when a project opens
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
2026-10-18 15:01:26 +13:00
ispyisail f49abcc6cc Fix a deleted symbol's joining wire getting a random uuid on every run
Deleting a symbol whose terminal held several wires joins the wires
left behind with a new one. That wire got a random uuid, so the same
deletion saved a different file every time: a diff for nothing in a
project kept in git (discussion #1512), and two copies with the same
edit could not be compared.

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
2026-10-18 10:51:16 +13:00
ispyisail ea9da9b2c8 Fix the saved date and time lagging one save in the title block
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
2026-10-18 10:51:16 +13:00
ispyisail 3c40bf5bd1 Tests: fix the two tests that fail on Windows
Windows CI stopped running tests (bdd3b2050) after these two failed:

- tst_reporttextafterinfo wrote "columns numbered from 1" to a .conf
  under XDG_CONFIG_HOME, which only Linux reads. On Windows the setting
  stayed in the registry, columns counted from 0 and the report read
  1-C8. The setting now goes to an INI file QET_SETTINGS_DIR points at
  (#1178), read on every system.
- tst_projectlock::readOnlyFolderDoesNotBlock relies on a read-only
  folder refusing new files. Windows ignores that flag on folders, so
  the lock was taken; the case is skipped there.

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

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

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

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

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
2026-10-18 08:04:13 +13:00
ispyisail f4dcbcc366 Merge pull request #1515 from ispyisail/test/lazy-save-parity
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m54s
Test that saving with lazy folios equals saving with every folio built
2026-10-18 06:00:12 +13:00
DaTweety a8f70fe071 Write qet-assistant.json after the settings dialog applies the paths
GeneralConfigurationPage::applyConf() wrote qet-assistant.json before it
applied the collection and title block paths, so the file kept the paths
the dialog had just replaced. The qet MCP server hands the user and
company collections from that file to every headless run it starts: a
company collection chosen in the dialog stayed unknown there, and
company:// paths failed to resolve until QElectroTech was started again.

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

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

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

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:33:35 +13:00
Laurent Trinques cc9fe1a5c8 Merge pull request #1511 from ispyisail/fix/recover-test-damaged-message
Fix the recovery test failing on master since #1504
2026-10-16 07:08:02 +02:00
Laurent Trinques 2d32be22eb Merge pull request #1508 from ispyisail/fix/report-text-shift-lazy
Fix report text shifted by its width after a lazy-folio save
2026-10-16 07:07:03 +02:00
Laurent Trinques e612a59888 Merge pull request #1507 from ispyisail/feat/project-lock
Add a project lock: tell a second person the project is open
2026-10-16 07:05:44 +02:00
ispyisail 10988770c6 Merge master into feat/project-lock
Conflicts: the lock dialog and the recovery dialog both kept in
qetdiagrameditor.cpp; the lock and previous-versions settings both kept.
The spacer in the General settings page moves to row 9, below the lock
checkbox, which the merge had left on the same row.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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