Commit Graph

9795 Commits

Author SHA1 Message Date
ispyisail fdef38eba8 qet-mcp: tests for what a second mutation audit found untested
tools/qet-mcp-audit/mutate.py over the code changed since #1095
(_build_script, the diff helpers, _parse_script_output, tool_items and
the wire-end keys of #1118), tests needing no QElectroTech only: master
f530b7636's suite noticed 531 of 578 planted bugs (92 %). With these
tests, 568 (98 %). The 10 left are equivalent: rsplit("/", 1) vs 2, a
1e-6 tolerance compared with < or <=, a one-letter orientation sliced
[:1] or [:2], branches that only touch parts without terminals, and the
placeholder terminal 0 a conductor uuid overwrites.

- Every argument kind refuses what it cannot take, before any launch:
  malformed indices, bool, points and nodes, search_and_replace with an
  unknown kind or conductor field or an empty element_info field,
  "conductor" given with "element" or "terminal" alone, an op that is not
  an object -- and the same kinds accept what they should.
- _parse_script_output reports capabilities (none logged = unknown, not
  "nothing missing"), notes on the right op, save and stopped_early;
  ignores a line without the marker even when it would parse, and a line
  of a kind it does not know.
- A uuid wire end in a column of terminals (same x) keys like the
  numbered one: the match is on x, y and orientation together.

Tests only; 264/264.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:09:08 +13:00
ispyisail f530b7636e Merge pull request #1128 from ispyisail/feature/qet-mcp-call
Add a one-shot --call to qet-mcp and document desktop app setup
2026-09-29 11:37:14 +13:00
ispyisail 080317485b Merge pull request #1126 from ispyisail/feat/qet-mcp-terminal-uuid
Let qet_edit name a terminal by its uuid
2026-09-29 11:36:51 +13:00
ispyisail 8f253be4ce Merge pull request #1125 from ispyisail/feat/script-terminal-uuid
Add a scripting lookup for terminals by uuid
2026-09-29 11:36:31 +13:00
ispyisail 03fc51d0fe qet-mcp: add --call, and document using it from the Claude desktop app
A web chat in a browser cannot start a local program, so it cannot run
this server. Two ways round that, documented in the README:

- the Claude desktop app runs stdio servers; a step-by-step setup;
- a chat that can execute Python can upload qet_mcp.py and run
  `--call <tool> '<json>'` (or `-` to read the arguments from stdin).

--call goes through the same dispatcher as the stdio server, so the
workspace policy applies unchanged. Exit status 0 success, 1 the tool
reported an error, 2 the call was malformed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:15:26 +13:00
ispyisail ccc063a19e qet-mcp: report every wrong terminal uuid of an op, each by its argument
Review of the previous commit: an add_conductor with both terminal uuids
wrong logged two notes under one op index and the second replaced the
first, and neither said which end it was about. Notes of one op are now
joined, and a terminal note starts with its argument ("from_terminal:",
"to_terminal:", "terminal:"). The integration test's both-ends case fails
with the old replacing behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:00:16 +13:00
ispyisail 3112727ee7 elementTerminals(): keep a "%3" or "%4" in a terminal's name as written
Review of the previous commit: the line was built with chained arg()
calls, so a "%3" in a terminal's name was replaced by the conductor
count -- as before -- and now a "%4" by the uuid as well. One
multi-argument arg() substitutes each placeholder of the pattern once.

percentInNameKept renames a terminal of perceuse.qet "x%3y%4": listed as
"x1y{uuid}" before, "x%3y%4" now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:00:16 +13:00
ispyisail f58bf7502e qet-mcp: qet_edit names a terminal by its uuid
"terminal", "from_terminal" and "to_terminal" take the terminal's uuid
(as qet_element_info lists it) in place of its index. It names a
terminal of the op's own element -- for add_conductor, of that end's
element, "$name" references included -- and is turned at run time into
the index the call takes by qet.terminalIndex(); if the element has no
such terminal the op fails with a note. Unlike the index, a sort by
position, it is defined between two terminals at the same point. The
lookup is required only when a uuid is used, so index-only edits still
run on older builds.

README: the terminal uuids; wires of older projects now get a lasting
uuid (#1107) instead of staying unnamed.

Tests: script generation for every terminal-taking op, "$name" and folio
uuid resolution, the index still passed through; through the binary,
bobine_ka_a_remanence (file order A2, A1; index order A1, A2) wired A2 to
A1 by uuid lands on exactly those ends, and an unknown uuid stops the run
with its note. 255/255 against a build with qet.terminalIndex(); a build
without it is reported as missing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:00:16 +13:00
ispyisail 9fdd6ac333 Scripting: find a terminal by its uuid
The conductor calls name a wire end as element uuid + terminal index.
The index is the terminal's place in Element::terminals(), a sort by
position that is undefined between two terminals at the same point, and
the documentation ruled terminal uuids out as "empty for most of the
installed base". Since #1118 every terminal of an opened project has one.

- elementTerminals() ends each line with the terminal's uuid
  (Terminal::stableUuid()); the text before it is unchanged.
- terminalIndex(folio, elementUuid, terminalUuid) returns the index the
  calls take, or -1 if the element or terminal is not there, or if two of
  the element's terminals carry that uuid.
- The class documentation says what does address a terminal: its uuid
  together with its element's.

tst_scriptterminaluuid runs --run on perceuse.qet (552 elements, two
terminals at one point in some): every terminal listed with a uuid,
distinct within its element, found again at its own index; -1 for an
unknown or malformed uuid, an unknown element and a bad folio. Red when
terminalIndex() returns the wrong index. qet-mcp suite 253/253.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:00:16 +13:00
Laurent Trinques b411a08340 git submodule update --remote elements 2026-09-28 23:34:05 +02:00
Laurent Trinques d3ca8483cf Merge pull request #1122 from ispyisail/feat/editor-derived-terminal-uuids
Give old symbols' terminals their lasting uuid in the symbol editor
2026-09-28 23:30:27 +02:00
Laurent Trinques 3df6d450fe Merge pull request #1121 from ispyisail/fix/titleblock-custom-vars-1113
Fix #1113: title-block variables shown by name and missing in Replace
2026-09-28 23:28:52 +02:00
Laurent Trinques a00e9439b7 Merge pull request #1120 from ispyisail/fix/1117-collection-icon-after-save
Fix #1117: element icon disappears from the collection after saving
2026-09-28 23:18:59 +02:00
ispyisail 22f1455868 Symbol editor: imported elements get new terminal uuids
Review of the previous commit: "Import scaled element" and "Import DXF"
add a definition to an open symbol through OpenElmtCommand, which, unlike
a paste, kept the imported terminals' uuids. With derived uuids, an old
symbol imported into itself (or into another with a terminal at the same
point) gave two terminals one uuid; importing any symbol whose terminals
already carried uuids did the same before this series. OpenElmtCommand now
renews the imported terminals' uuids, as PastePartsCommand does.

Checked in the editor with a stand-in scaler at scale 1: open
6es7_212-1ae40-0xb0__p3.elmt, import it into itself, save -- 6 terminals,
3 distinct uuids without this commit, 6 with it, the original 3 keeping
their derived values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:08:39 +13:00
ispyisail 2815dbaf47 Symbol editor: give old terminals their derived uuid, not a random one
A symbol file whose terminals have no uuid got a random one for each
terminal when opened in the element editor (PartTerminal's constructor),
written on save. Every copy of the same old symbol therefore ended up
with different terminal uuids, none of them the one a project gives the
same terminals on opening (TerminalUuids::fillMissing()).

ElementScene::loadContent() now reads a copy of the definition filled by
TerminalUuids::fillMissingInDefinition(), the same rule as a project,
including the next occurrence for the second of two terminals at one
point. Terminals that have a uuid keep it; a paste still renews them all
(PastePartsCommand).

Checked in the editor: 6es7_212-1ae40-0xb0__p3.elmt (no terminal uuids
in the collection) and tm3saf5r_layout.elmt with its uuids stripped (two
terminals at one point), opened, nudged back and forth, saved: this build
writes exactly the derived values (computed independently in Python),
the previous one random ones. Select all, copy, paste, save: 6 terminals,
6 distinct uuids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:08:39 +13:00
Laurent Trinques 90084ecf40 Merge pull request #1119 from Kellermorph/fix-material
Fix material list reading, selection and article fields
2026-09-28 23:04:32 +02:00
ispyisail 08bce54a2f Fix #1113: custom title-block variables in Find/Replace and unset placeholders
Two problems with a custom variable newly added to a title-block template:

1. Find/Replace > Folio > Custom showed an empty table, so the user had to
   know and type each variable name. It now lists every custom variable the
   folios already carry plus those their templates use, with empty values.
   An empty value now means "leave unchanged", like every field of the main
   tab; only the variables actually filled in are written to the folios.

2. The title block showed the variable's own name ("%doc-type") until the
   folio's properties were opened, because interpreteVariables() only
   replaces names present in the context. Placeholders in the template text
   that no key resolves now render blank, as auto-added unset ones already
   did since #973. Only the template's own text is considered, so a value
   that contains "%something" is never touched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:59:21 +13:00
ispyisail 34a2ceff70 Fix #1117: element icon vanishes from the collection after saving
Saving an element from the element editor calls
ElementsCollectionWidget::locationWasSaved(), which runs clearData() on
the panel item: the icon is set to a null QIcon. The icon only comes
back through FileElementCollectionItem::setUpIcon(), but since the
#633 recursion guard that returns for good once m_icon_initialized is
set, and nothing ever reset it. The row stayed without an icon until
the whole collection was reloaded.

#1008 refreshed the picture caches on save, but no one asked them for
the new picture, so it could not fix this. Resetting the flag in
clearData() lets the next paint rebuild the icon, which then comes
from those refreshed caches, so the panel shows the new drawing.

The flag is reset after the base clearData(): its setIcon() emits
dataChanged(), which re-enters setUpIcon() and must still return early.
setUpIcon() sets the flag before its own setIcon(), so the #633 guard
is unchanged.

The same reset should also bring back a folder's icon after editing its
properties (editDirectory() calls clearData() too); read, not tested.
Project collection items were never affected: their setUpIcon() guards
on icon().isNull().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:55:10 +13:00
Laurent Trinques df2395e5c6 Merge pull request #1118 from ispyisail/feat/fill-terminal-uuids
Give terminals in older projects a lasting uuid
2026-09-28 22:42:22 +02:00
Kellermorph 8b54ea400b Fix material list reading, selection and article fields
Follow-up to #1088, covering its review comments and one behaviour
change found while testing the feature:

- MaterialList::load() names the columns from the machine key line when
  the file has one, instead of from the translated label line above it.
  A catalogue written in another language fills the element fields
  again, and saving no longer replaces the key line with labels.
- The entry picked after "New entry" is found by comparing the columns
  one by one instead of comparing the two maps as a whole: the entry
  form leaves the empty columns out, so the record never matched the
  line that had just been written and the search was cleared for
  nothing. The search is now only given up when it really hides the new
  line.
- Applying a catalogue entry pushes an undo command only when the live
  edit is on. In the properties window, where there is none, the fields
  wait for "Apply", so "Cancel" gives the element its own values back
  instead of leaving the picked part in place.
- Cells a file holds past the header are kept in MaterialRecord::extra
  and written back, so appending an article never shortens a line.
- An empty cell of a column describing the article itself (MaterialList::
  isArticleBound: description, designation, manufacturer, order number,
  supplier, model, ratings, dimensions, auxiliary block) clears the
  field, so an element never keeps the manufacturer of the part picked
  before. An empty cell of any other column (function, comment, notes,
  plant, location, quantity, unity), and any column the file does not
  hold at all, leaves the field alone.
- Comments left where the review asked for them: the corner button
  lookup, the ';' separator fallback, the search filter cost.
2026-09-28 22:38:41 +02:00
Laurent Trinques 56e69d5956 Merge pull request #1116 from ispyisail/fix/replace-symbol-keeps-wires
Fix wires lost after replacing a symbol already in the project
2026-09-28 22:30:09 +02:00
ispyisail eb3b10a48e Tell apart two terminals at one point; qet_diff: same wire in both forms
Review of the previous commit:
- The load fallback compared a saved uuid with occurrence 0 only, so a
  wire on the second of two terminals at one point of a symbol was lost
  once the definition was replaced, and one on the first could go to
  either of the pair (Element::m_terminals is sorted, not in definition
  order). Element::parseTerminal() now records each terminal's rank among
  the terminals of the definition at the same point, derivedUuid() uses
  it, and fillMissing() starts from the same rank.
  derivedUuidFoundAfterReplacement runs on perceuse.qet and industrial.qet
  too: 154/156 and 670/671 wires without the rank, all with it.

qet-mcp: the first save of an older project now rewrites its wires from
the numbered form to the uuid form, and qet_diff keyed the two forms
differently, so an untouched resave showed every wire removed and added
(4 failures in test_qet_mcp.py). A uuid end is now resolved to the same
key as a numbered one: the terminal's definition position, moved to where
the wire docks, is the placed symbol's <terminal> record.
test_conductor_key_same_in_both_forms fails without it; 253/253 pass on
this build and on the previous stage's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:10:38 +13:00
ispyisail 4e6f59e011 Give terminals in older projects a lasting uuid
Most symbols stored in older projects have no uuid on their terminals
(706 of the 900 in the 24 examples), so a terminal's identity is worked
out from where it sits in its symbol on every load (stableUuid()). That
is only sound while nothing keyed on it is kept between loads.

- On opening a project, every terminal of its embedded symbols without a
  uuid gets that same derived value (TerminalUuids::fillMissing(), from
  XmlElementCollection's loading constructor, before any folio is
  built). The next save writes it, and the wires on it in the form that
  names terminals by uuid, which QElectroTech reads since 0.8.0.
- The recipe moves to TerminalUuids::derived(), which stableUuid() now
  calls, so the two cannot drift apart. A second terminal at the same
  point of a symbol gets the next occurrence, and no value is given
  twice within a symbol.
- findTerminal(): a wire whose terminal uuid is not found is matched to
  the terminal whose derived value it is, so a saved uuid still finds its
  terminal after the symbol's definition was replaced by one whose
  terminals carry other uuids.

The project database's terminal and conductor tables are identical before
and after on all 24 examples except the 3 terminals that share a point
with another in their symbol (industrial.qet 1, perceuse.qet 2), which
now have an identity of their own. Every example keeps every wire through
a resave, and a second save changes nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 08:50:27 +13:00
ispyisail 079d085a87 Keep terminal uuids when a whole category is replaced; never duplicate one
Review of the previous commit:
- keep() could give an old uuid to a new terminal while another terminal
  of the new definition already carried it (a moved terminal), leaving
  two terminals with one uuid. A terminal carrying any old uuid is now
  left alone, and an old uuid already in use is never handed out.
- copyDirectory() replaced a whole category of the embedded collection
  (drag a folder onto the project's folder of the same name) without
  carrying terminal uuids over. keepInDirectory() walks both trees by
  name and calls keep() on each symbol.
- The "wire(s) not loaded" log line repeated the folio's list on every
  paste; it is now written only when a folio is loaded.

tst_terminaluuids: 3 new cases, each red on the previous keep().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 08:11:15 +13:00
ispyisail e6ac117f9b Keep wires attached when a project's copy of a symbol is replaced
A wire whose terminals have uuids is saved against them, and on load it
is reattached to a terminal with that uuid or dropped, with only a qDebug
line. Re-importing a changed symbol and choosing "replace" swapped the
project's definition for one whose terminal uuids differ; the placed
symbols kept the old ones until the project was reopened, so every wire
on them was lost at the next open, silently, and gone for good at the
next save.

- XmlElementCollection::copyElement(), where an embedded definition is
  overwritten, carries each old terminal uuid onto the new terminal at
  the same place and orientation (TerminalUuids::keep()). Terminals that
  moved, and new ones, keep their own.
- Diagram::fromXml() records wires it could not reattach, logs them, and
  the editor lists them in one warning after opening a project.

Measured on 2612_ats_singlephase.qet with the stored splice's terminal
uuids made to differ from the collection's: replace, save, reopen loads
34 of 131 wires on master, 131 with this change (GUI, both arms).
tst_terminaluuids covers keep() and runs the real loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 08:11:15 +13:00
Laurent Trinques 9cbf26dcc9 Merge pull request #1115 from ispyisail/feat/qet-mcp-edit-folio-uuid
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m10s
Let qet_edit name a folio by its uuid
2026-09-28 20:54:04 +02:00
Laurent Trinques 4bf02cd936 Merge branch 'master' into feat/qet-mcp-edit-folio-uuid 2026-09-28 20:53:51 +02:00
Laurent Trinques 9fe30c9081 Merge pull request #1114 from ispyisail/feat/script-folio-uuid
Add scripting calls to find a folio by its uuid
2026-09-28 20:28:58 +02:00
Laurent Trinques 045d2edc27 Merge pull request #1108 from ispyisail/feat/qet-mcp-conductor-uuid
Let qet_edit name a wire by its uuid
2026-09-28 20:27:05 +02:00
Laurent Trinques c1332adce5 Merge pull request #1107 from ispyisail/feat/lasting-wire-uuid
Give wires in older projects a lasting uuid, from what they connect
2026-09-28 20:19:03 +02:00
Laurent Trinques 59f0b0b3f8 Merge pull request #1106 from ispyisail/feat/script-conductor-uuid
Add scripting calls to list wires by uuid and find their ends
2026-09-28 20:10:53 +02:00
ispyisail a4d8f19d9f Merge branch 'feat/script-conductor-uuid' into feat/qet-mcp-conductor-uuid 2026-09-29 06:34:16 +13:00
ispyisail 97510f9639 Merge remote-tracking branch 'upstream/master' into feat/lasting-wire-uuid
# Conflicts:
#	tests/qttest/CMakeLists.txt
2026-09-29 06:34:16 +13:00
ispyisail 604c3b1cd7 Merge remote-tracking branch 'upstream/master' into feat/script-conductor-uuid
# Conflicts:
#	tests/qttest/CMakeLists.txt
2026-09-29 06:34:16 +13:00
Laurent Trinques bc5ef180b6 Merge pull request #1105 from ispyisail/feat/stable-symbol-uuid
Give symbols in older projects the same uuid every time they open
2026-09-28 16:42:25 +02:00
Laurent Trinques cdcff93190 Update CMakeLists.txt 2026-09-28 16:42:05 +02:00
Laurent Trinques 69a468af63 Merge branch 'master' into feat/stable-symbol-uuid 2026-09-28 16:33:24 +02:00
Laurent Trinques f981ec7dc8 Merge pull request #1111 from ispyisail/feat/1070-rotate-whole-group
Rotate a selected group as one piece
2026-09-28 14:55:04 +02:00
Laurent Trinques 7456a48681 Merge pull request #1110 from ispyisail/feat/1070-pick-one-member
Click again on an item of a selected group to select it alone
2026-09-28 14:54:27 +02:00
Laurent Trinques 5a09d67f64 Merge pull request #1109 from ispyisail/fix/second-save-changes-nothing
Fix a just-saved project changing again on its next save
2026-09-28 14:52:54 +02:00
ispyisail c7e5c0c3b3 qet-mcp: qet_edit names a folio by its uuid
qet_edit takes a folio as its index, and the index shifts when an earlier
op in the same run adds, inserts or removes a folio: remove folio 0, then
edit "folio 2", and the edit lands on the folio that was 3. Every other
item qet_edit addresses (elements, texts, shapes, pictures, tables, symbol
text fields) can already be named by uuid; folios could not.

- "folio" and "to_folio" take the folio's uuid as well as its index,
  turned into the current index at run time by qet.folioIndex(). The
  lookup is required only when a uuid is used, so an index-only edit still
  runs on a build without it.
- qet_project_info lists each folio's uuid. A folio saved without one
  (132 of the 133 in the shipped examples) shows it empty until the
  project is saved once, and an empty "folio" says so.

Tests: the generated script for a uuid folio, on its own and inside a
table lookup and link_elements' to_folio; and a real run that removes
folio 0 and then retitles the old folio 2 by uuid, on a file saved without
folio uuids. The run fails against a build without qet.folioIndex().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 00:00:27 +13:00
ispyisail b99f5551d2 Scripting: look up a folio by its uuid
Every scripting call names a folio by its index, and the index shifts when
a folio is added, removed or moved: a script that removes folio 0 and then
edits "folio 2" edits the wrong one. Texts, shapes, pictures, tables and
symbol text fields already have a uuid lookup for the same reason; folios
had none.

- qet.folioUuid(index): the folio's uuid, or "".
- qet.folioIndex(uuid): the folio's current index, or -1.

A folio saved without a uuid (132 of the 133 in the shipped examples) is
given one on load, derived from the file, so it is the same on every load
and is written on the next save. No two folios share one: a clash is
renewed on load.

Tests in misc/qet-mcp's integration suite, which drives these through
--run: a folio followed across the removal of the one before it, on a file
saved without folio uuids, and a new folio's uuid found in the saved file.
Both fail against a build without this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:56:08 +13:00
ispyisail e25ae3cbfb Test the second save on every example, a single-space value and accents
Review of #1109 (scorpio810):

- tst_resaveunchanged now resaves every .qet in examples/ twice instead
  of two of them (24 rows, ~40 s). Dropping the load-time trim now also
  fails affuteuse_250h.qet, which the two-file version missed.
- New case singleSpaceValueKept: a title-block property set to one
  space survives two saves (#973), and an accented property comes back
  unchanged. Fails if qetproject.cpp stops parsing with
  PreserveSpacingOnlyNodes.
- New tst_diagramcontext: the QDom reader (projects) and the pugixml
  reader (element definitions in the collection) return the same value
  for plain, stray-spaced, accented and non-Latin text. Fails if the
  pugixml path decodes as Latin-1 or either reader stops trimming.

The pugixml reader still reads a single-space value as "": pugixml drops
whitespace-only text unless parse_ws_pcdata is set, as it did before
this PR. That reader only sees element definitions, never a project, so
#973's title-block values do not go through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:29:51 +13:00
ispyisail e7745afd18 qet-mcp: answer review on naming a conductor by uuid
- An end conductorEnds() reports as "?" is never picked.
- An empty "conductor" says why: an older project's conductors have no
  saved uuid, so qet_conductors reports it empty.
- The op description and README say set_conductor still changes the
  whole potential when given a uuid, and that older projects' conductors
  are named by element + terminal until #1103.
- test_conductor_by_uuid covers move_conductor_segment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:25:05 +13:00
ispyisail af6c2f0dde Merge branch 'feat/script-conductor-uuid' into feat/qet-mcp-conductor-uuid 2026-09-28 23:21:51 +13:00
ispyisail 3d85fc013d Scripting: keep conductors()'s doc comment on conductors()
The anonymous namespace sat between the comment and the function, so
Doxygen attached the comment to describeEnd().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:21:38 +13:00
ispyisail a8f940505d Rotate a selected group as one piece
Discussion #1070 proposed that rotate, like move, copy and delete, works on
the whole group once one of its items is clicked. Rotate (Space) turned
each member on its own spot instead, so rotating a group pulled it apart:
two grouped texts side by side ended up each turned in place, no longer
side by side.

When the selection is exactly one whole group -- wires aside, which follow
their symbols -- Rotate now turns it as one piece around its centre, as
"Pivoter le groupe" (Shift+Space) already does
(ItemGroups::soleWholeGroup()). Any other selection, including a single
member picked out of its group, rotates as before.

In the GUI, on two grouped texts selected by one click: Space on master
leaves both where they were, turned; here it gives exactly what
Shift+Space gives on both (both texts swung around the group's centre).
tst_itemgroups: 4 new checks; without the whole-group condition, a
picked member counts as a group and fails. ctest 24/24.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:39:03 +13:00
ispyisail 6ed41bbfd0 Click again on a member of a selected group to pick it on its own
Clicking an item of a group selects the whole group (#1070). Clicking
again on one of its items is meant to select just that item, to edit it
on its own -- what discussion #1070 proposed -- but the second click
selected the whole group again: Qt left only the clicked item selected on
release, and the group completion pulled the others back in.

A press on a member of a group that is selected whole now notes that
member (ItemGroups::memberToPick(), which also finds the member when the
click lands on a symbol's own text). If the click ends without a drag and
Qt has left only that member selected, the selection stays so. A drag
still moves the whole group; Ctrl+click keeps its meaning; a group of one
is not picked from.

In the GUI, on two grouped texts: one click then Delete removes both
(master and this); click, click again, Delete removes only the clicked
text here, both on master; dragging after one click moves both texts by
the same amount on both. tst_itemgroups: 5 new checks; removing the
whole-group or the group-of-one condition fails one each. ctest 24/24.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:33:28 +13:00
ispyisail 4dcb54a3a2 Merge branch 'feat/script-conductor-uuid' into feat/qet-mcp-conductor-uuid 2026-09-28 22:04:33 +13:00
ispyisail 9db6084387 Build tst_scriptconductoruuid only where --run exists
The test runs a script through --run, which a build without the Qt Qml
module does not have (QET_HAS_SCRIPTING). Linux CI installs no Qml package,
so the binary took the script for a project to open and the test waited
out its 60 s. The test is now built only when scripting is: a configure
with Qt6Qml disabled lists 24 tests, without it; with Qml it runs and
passes as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:04:30 +13:00