Compare commits

...

621 Commits

Author SHA1 Message Date
Laurent Trinques 85342f4b56 Merge pull request #1318 from ispyisail/feature/bom-skip-junctions-slaves
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m7s
Add options to leave junctions and contact blocks out of the parts list
2026-10-05 17:59:05 +02:00
Laurent Trinques 2e4b761602 macOS: fix macdeployqt call, it has no -extra-plugins option
The previous commit passed -extra-plugins to macdeployqt, which does
not know it: it aborted with "Unknown argument" and deployed nothing,
so the following steps failed on an empty Frameworks/ directory.

Call macdeployqt without options and stop the script if it fails. The
offscreen plugin is copied by the existing fallback block.
2026-10-05 16:27:57 +02:00
Laurent Trinques 4cc8d62c5f macOS: bundle the offscreen Qt platform plugin in the DMG
macdeployqt only deploys libqcocoa, so running QElectroTech with
QT_QPA_PLATFORM=offscreen aborts on the signed macOS bundle. This
prevents headless use (e.g. qet_mcp.py) without opening a Cocoa app.

Deploy platforms/libqoffscreen.dylib with macdeployqt -extra-plugins.
If it is not deployed, copy it from the Homebrew Qt and point its Qt
framework references to the bundle. The build stops if the plugin is
still missing. The existing signing loops already sign it with the
other plugins.
2026-10-05 16:10:36 +02:00
ispyisail b1247d4bb0 Merge pull request #1317 from ispyisail/fix/pdf-trailer-startxref
Fix repeatable PDF changing with the output folder (#1178)
2026-10-05 22:15:39 +13:00
ispyisail 16173c2070 Merge pull request #1316 from ispyisail/perf/live-timing
Add an optional timing log for live mode
2026-10-05 22:14:50 +13:00
ispyisail 97e81f7ebf Merge pull request #1314 from ispyisail/feature/live-remember
Remember live mode's start warning and "Always" across restarts
2026-10-05 22:14:31 +13:00
ispyisail e7180cd3d2 English for the parts-list options (#1178)
Slave contacts, Leave out junctions and its tooltip; the locations of the
entries after them in the two .ui files moved down accordingly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:14:18 +13:00
ispyisail 8434b88eb8 Add options to leave junctions and contact blocks out of the parts list (#1178)
Since #849 the parts list has a row for every contact block (slave) and
terminal-type element. Users who draw the dots and bends where wires
branch as terminal-type symbols (114_connections) get one empty row per
junction: 109 of 151 rows on one reported cabinet.

Two options, both off by default so existing exports are unchanged:
- leave out the contact blocks: --no-slaves, "no_slaves" in qet_export,
  or uncheck the new "Contacts esclaves" element type in the dialog;
- leave out the junctions: terminal-type elements with no label,
  designation, manufacturer or manufacturer reference. --no-junctions,
  "no_junctions", or "Laisser de côté les jonctions" in the dialog.
  A terminal block with a label or a part number stays.

The element type filter had no box for slaves, so every query it built
left them out: the export dialog never listed contact blocks, although
the command line has since #849. The dialog now checks the new box by
default and gives the same rows as --export-bom. The box is unchecked by
default elsewhere, so nomenclature tables keep their rows.

docs/smart-device-bom.md still said slaves and terminals were excluded;
corrected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:08:44 +13:00
ispyisail 082b55b759 Merge pull request #1315 from ispyisail/feature/assistant-house-style
Add a house style that every AI assistant reads
2026-10-05 22:02:05 +13:00
ispyisail 527b0f3b2d Fix repeatable PDF changing with the output folder (#1178)
Rewriting the links copied the old trailer up to %%EOF, so the old
startxref stayed in the file before the new one. Its offset is the size
the file had before the rewrite, and the links carry the output path,
so the same project exported to two folders gave different bytes, and
a different document id. Both rewrites now copy the trailer dictionary
only, through one helper; the print window's rewrite no longer leaves a
stray %%EOF either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 21:43:28 +13:00
ispyisail db9ba6c1ca Merge remote-tracking branch 'upstream/master' into perf/live-timing
# Conflicts:
#	sources/scripting/liveserver.cpp
2026-10-05 21:36:17 +13:00
ispyisail 7ddf2ca14f Merge remote-tracking branch 'upstream/master' into feature/assistant-house-style
# Conflicts:
#	misc/qet-mcp/README.md
#	misc/qet-mcp/qet_mcp.py
2026-10-05 21:36:08 +13:00
ispyisail 33f7d27388 Merge remote-tracking branch 'upstream/master' into feature/live-remember
# Conflicts:
#	lang/qet_en.ts
2026-10-05 21:35:52 +13:00
ispyisail 32f7b94594 Merge pull request #1313 from ispyisail/feature/live-tools
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m50s
Add printing, save/close and a layout check to live mode
2026-10-05 21:33:18 +13:00
ispyisail dccde2b8c8 Merge pull request #1312 from ispyisail/feature/live-new-project
Add live-mode commands to create, open and switch projects
2026-10-05 21:32:59 +13:00
ispyisail 9b775baf6e qet-mcp README: the live timing log
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:35:19 +13:00
ispyisail a4f12917bc qet-mcp README: the house style
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:34:57 +13:00
ispyisail 36d574179b Assistant house style: rewrite qet-assistant.json when the style changes
qet.setHouseStyle() only saved the setting, so an assistant reading
qet-assistant.json saw the old text until QElectroTech restarted.
AssistantInfo::refresh() writes the file again, but only in a
QElectroTech that writes it at all: a headless --run would otherwise
replace the open QElectroTech's live channel with its own empty one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:34:57 +13:00
ispyisail 44dfb9d8d5 Assistant house style: a drawing-convention text every AI assistant reads
QetSettings::houseStyle()/setHouseStyle() (assistant/house_style),
qet.houseStyle()/qet.setHouseStyle() in the script API, a house_style
field in qet-assistant.json, and qet_about in the qet MCP server
showing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:34:56 +13:00
ispyisail 193d177d95 qet-mcp README: remembered live-mode choices
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:34:44 +13:00
ispyisail 8b130497e0 qet-mcp README: print, save/close, changes, live layout check, index cache, standard symbols
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:34:26 +13:00
ispyisail 4ceeeed302 qet-mcp README: the live project tools
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:34:14 +13:00
ispyisail 76d9ab2911 Live mode: English for the print question
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:34:14 +13:00
ispyisail 0bb0ea225f Live mode print: draw at 96 dpi, as the PDF export does
At the printer's own resolution (600-1200 dpi) symbols and texts were
replayed many times too big next to the wires and grid. Seen on a test
print to PDF; the same page now matches qet_export's PDF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:34:14 +13:00
ispyisail 0df8ca5b3c Live mode: print, save/close, changes, snapshot; house-style layout rules; saved symbol index
QElectroTech (LiveServer):
- print: folios to the default (or a named) printer with no print dialog,
  always after asking the user (paper cannot be undone); or to a new PDF
  file. Rendered as the PDF export draws a folio (no grid, no terminals).
- save_project (own file, or save as a new file, never over one) and
  close_project (only without unsaved changes: never discards work).
- changes: the undo history, each step marked assistant or user, with
  "since" to ask what changed after a point.
- snapshot: a copy of the open project as on screen, for a check to read,
  without saving it or changing its file.
- qet.elementTextGeometry(): where a symbol's text is drawn on the folio.

qet MCP server:
- qet_live_print, qet_live_save_project, qet_live_close_project,
  qet_live_changes, qet_live_layout_check (snapshot + qet_layout_check, no
  "run this script?" window); write paths never overwrite.
- Layout rules from the house style, for file and live checks alike:
  label_on_wire, four_way_junction, misaligned_branch.
- The element index is saved under ~/.cache/qet-mcp: a new server process
  loads 8839 symbols in 0.4 s instead of parsing them for 6.9 s; the
  change check is a third faster.
- qet_about lists the installation's standard symbols
  (standard-symbols.json in QElectroTech's data folder) when there are any.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:34:14 +13:00
ispyisail 85492c8f75 Live mode: English for the remembered-choice texts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:22:38 +13:00
ispyisail 2fb48e791b Live mode: open_project never stops on the backup question
openAndAddProject() asked "Créer une copie de sauvegarde ?" even when
called non-interactively, so open_project waited for an answer from the
user in front of the window. Only live mode opens projects that way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:15:19 +13:00
ispyisail 550dcdda71 Live mode: remember the start warning and "always" across restarts
Live mode asked at every start (the "Mode direct" warning) and forgot
"always" for scripts when QElectroTech closed, so a user who works
with an assistant every day re-enabled it every day. Both are now
opt-in and remembered: the warning has a "Ne plus demander au
démarrage" checkbox (scripting/live_skip_warning), and the ask-first
choice is saved (scripting/live_ask_first; the Assistant panel's
checkbox and "Toujours" in the script window). The status bar still
shows live mode whenever it is open. Switching live mode off in the
settings clears "don't ask again", so turning it back on warns again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:11:47 +13:00
ispyisail ad20eb94ba Live mode: open a saved project, switch between open projects
open_project opens a .qet and makes it current, as File > Open does but
with no file dialog and no error windows: a missing, unreadable or
foreign file is refused in the answer. A project already open is only
made current. switch_project makes another open project current, by its
index or its file. status now lists every open project (index, title,
file, folios, unsaved changes, current), so the assistant sees what it
can switch to. MCP: qet_live_open_project, qet_live_switch_project; both
paths go through the workspace policy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:00:13 +13:00
ispyisail 42b1e43e4a Live mode: create a new project (new_project, qet_live_new_project)
An assistant in live mode could only work on the project already open.
new_project makes one as File > New does -- this installation's
new-folio defaults, optional title and number of folios -- and makes it
the current project, so the next request works on it. An optional
absolute path saves it at once, never over an existing file: QElectroTech
refuses, and the MCP server refuses before asking (a new never-overwrite
class in its path policy, so an "overwrite" flag cannot get round it).

Pages and links across pages already worked through scripts
(qet.addFolio, setFolioTitle, linkElements across folios); the tool's
description now says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:48:38 +13:00
ispyisail e77a175fc9 Live mode: optional timing of each request (QET_LIVE_PERF_LOG)
A request with "timing": true gets queue, confirm and exec times in its
answer; with QET_LIVE_PERF_LOG set, QElectroTech also appends one JSON
line per request, with the time until the folio is next painted. The
qet MCP server logs its side of each live call to the same file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:32:42 +13:00
Laurent Trinques a5cd68ced5 Merge pull request #1311 from ispyisail/feature/xref-stack-option
Add a setting for stacking cross references at the folio bottom
2026-10-05 06:34:49 +02:00
ispyisail b8eb5c90a1 Cross references at the folio bottom: stack only when texts overlap, add a checkbox (#1287 follow-up)
The stacking added in #1287 tested the crosses' bounding rects, so
crosses whose frames or empty margins touched were raised although no
text was hidden (the list display case found in review). The texts each
cross really draws are now recorded while its geometry is built, and a
cross is raised only when one of its texts would be drawn over a text of
another one.

A new "Empiler les références dont les textes se chevauchent" checkbox
in the cross reference properties, per type, turns the stacking off;
the crosses of that type are then only centred, as before #1287. It is
saved in the project (xref attribute "stackoverlapping") and in the
settings, and is on when absent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:24:11 +13:00
Laurent Trinques ecf995174e Merge pull request #1309 from plc-user/master
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m54s
FIX copyright-date in cmake-files
2026-10-04 21:26:39 +02:00
plc-user b8be28782f FIX copyright-date in cmake-files 2026-10-04 20:59:17 +02:00
Laurent Trinques 1882486785 Merge pull request #1308 from jp2images/svg-icons-d
Sort the icon folders by source and redraw the convert-shape icons (#1269)
2026-10-04 20:02:44 +02:00
Jeff Patterson 1dc9c5cc3d Served the app icon from the icon theme in the new element wizard
The qet theme now lists Nuri's app icon PNGs under the name
qelectrotech, and the wizard loads its logo by that name instead of
the 256 px file path. The files stay in ico/breeze-icons/, where the
packaging scripts install them from.

Part of #1269.
2026-10-04 10:16:34 -05:00
Jeff Patterson f46c63bd42 Redrew Andre's three convert-shape icons for the light and dark themes
ellipse-to-bezier, rect-to-bezier and rect-to-polyline are now drawn
pixel by pixel at 16 and 22 px, with the 1 px lines and hollow handles
of the line tool, and follow the theme's text color. The polyline is
simpler: at menu size the three-segment line with a handle on every
corner was too much detail to read, so it is now two segments rising
to a peak with a handle at each foot. Andre agreed to the redraw under
GPL-2.0-or-later.

Part of #1269.
2026-10-04 10:16:29 -05:00
Laurent Trinques 534d3b4a49 Merge pull request #1277 from saschbe/docs/improve-testing-guide
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m5s
docs: improve testing and build instructions
2026-10-04 16:17:56 +02:00
Jeff Patterson 20ee3383d3 Sorted the icon folders by source and added ico/README.md
Each folder now holds icons from one source: qet/ for QET's own SVGs,
breeze/ for KDE Breeze (with breeze/added/), flags/, colors/, and
themes/ for script output (with themes/generated/). The unused
128x128/terminalstrip.svg is removed. The generator, qrc files,
qeticons.cpp and ico/copyright follow the new paths. ico/README.md
lists each folder's source, script and license.

Part of #1269.
2026-10-04 07:28:56 -05:00
Laurent Trinques dafd8e8024 Update translations files 2026-10-04 12:55:47 +02:00
Laurent Trinques da39ec1c86 Update translations files 2026-10-04 12:42:50 +02:00
Laurent Trinques 49b145bd4f Merge pull request #1307 from arummler/fix-qtests
Fix qtest regarding hash stability
2026-10-04 11:53:43 +02:00
Laurent Trinques 1c09c4e391 Merge pull request #1282 from arummler/formula-improvements
Auto numeration: a lot of changes for elements, condutors, sheets
2026-10-04 11:52:56 +02:00
Andre Rummler 86be2b303e Fix qtest regarding hash stability as requested in PR #1282. 2026-10-04 10:28:15 +02:00
Laurent Trinques bbb8e5d2e2 Tested in list display on a real project: neighbouring references on the same row were raised alternately (980 / 927 / 980...) although nothing visibly overlapped.
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m52s
Cause: in list mode `drawAsContacts()` adds a margin (`adjust(-30, -4, 4, 4)`) to `boundingRect()`, and the horizontal test in `stackAtBottom()` uses that rect. Two lists overlapping only inside the empty margin (1.4 units in my log) count as overlapping. `gap` only changes how far the raised ones move, not which ones.

Fix: test the content only in list mode (`rect.adjust(30, 0, -4, 0)`), with the two margins as constants shared with `drawAsContacts()`.

Result: all references stay at the bottom (980 -> 980), none raised by mistake, also when moving an element sideways.

Checked: [to complete: same-column coils in list mode still stack, cross mode unchanged]
2026-10-04 09:32:12 +02:00
Laurent Trinques ac2f360910 Merge pull request #1302 from ispyisail/fix/1252-parttext-align-while-typing
Fix #1252: aligned static text drifts while typing in the element editor
2026-10-04 07:38:51 +02:00
Laurent Trinques 7bd9979777 Merge pull request #1303 from ispyisail/fix/text-group-dangling-xref-connection
Fix a crash in Project properties after a text group is deleted
2026-10-04 07:37:27 +02:00
Laurent Trinques faa0ca92a5 Merge pull request #1301 from ispyisail/fix/zoom-content-hidden-items
Fix #1281: Zoom to content includes hidden wire texts
2026-10-04 07:35:52 +02:00
Laurent Trinques 8a275be6da Merge pull request #1299 from ispyisail/fix/1280-junction-dot-potential
Fix #1280: missing junction dot where wires share a line
2026-10-04 07:34:46 +02:00
ispyisail e055438f30 Fix a crash when cross-reference settings change after a text group is deleted
A text group kept at the bottom of the page, on a master element,
connects to its project's XRefPropertiesChanged with a lambda that has
no context object. The connection belongs to the project, so it
outlives the group: once the group is deleted (paste a coil, undo,
then make any other edit), changing a cross-reference setting in the
project properties calls QTimer::singleShot() on the freed group and
QElectroTech crashes.

Pass the group as the context of that connection and of the
linkedElementChanged one beside it, so Qt removes both when the group
goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:59:10 +13:00
ispyisail 9d7fac1f53 Element editor: keep a static text's alignment while typing (#1252)
startEdition() records the bounding rect, but the aligned point was only
restored in endEdition(), so a right/centre/bottom-aligned text grew from
its old left/top edge while being typed into and jumped into place when
the edition ended. Re-anchor after every content change during inline
edition.

endEdition() now always clears previous_text, which marks an edition in
progress. It used to stay set after an edition that left the text
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:06:55 +13:00
Andre Rummler 8eb2024bb1 Fix jump to element search. 2026-10-04 01:04:44 +02:00
ispyisail cbacd2bb62 Merge pull request #1283 from jp2images/svg-icons-c
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m39s
Finish the SVG icons and fix the icon licenses in ico/copyright (#1063, #1269)
2026-10-04 11:45:07 +13:00
ispyisail 2ff0f0d860 Merge pull request #1300 from arummler/fix-qobject
Fix missing Q_OBJECT
2026-10-04 11:44:13 +13:00
ispyisail 6f5db879c1 Zoom to content: count only visible items
itemsBoundingRect() also counts hidden items. The text of a single-line
wire, and the wire texts hidden by "one text per potential", are never
positioned again and can sit far outside the drawing, so Zoom to content
zoomed out to include them. On photovoltaique.qet 14 such texts reach
x = -729 and the zoom covered 2421x924 instead of 1135x648.

Add Diagram::visibleItemsBoundingRect() and use it for Zoom to content,
the scene rect and the borderless export. Zoom to content now goes through
zoomToRect(), which re-centres after the scroll bars appear; before, the
view could stay scrolled to the top-left with part of the content cut off.

Fixes #1281

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:23:26 +13:00
Andre Rummler 30281032e4 Fix missing Q_OBJECT. Required to pull declaration into the header. 2026-10-04 00:11:38 +02:00
ispyisail bec0f1c7b4 Fix #1280: junction dot missing where a wire's corner meets a wire it is not wired to
A conductor draws a junction dot at each of its bends that lies on
another conductor. It only looked at the conductors on its own two
terminals, so a bend lying on a conductor of the same potential that
shares no terminal with it got no dot. Dragging the horizontal parts of
a chain of conductors onto one line, as in the report, does exactly that.

Look at every conductor of the potential on the folio instead
(relatedPotentialConductors(false)). The folio scene uses NoIndex, so a
scene lookup at each bend scans every item and made export of the
366-conductor Polonez example 60% slower; walking the potential costs
nothing measurable there (2.08 s vs 2.11 s) or on a 153-conductor
potential (0.97 s both).

The test for one other conductor moves into bendMakesJunction(). It
also stops a point being appended once per matching segment: master drew
the dot above K2 in the fixture ten times.

Across the 23 examples (133 folios) exported to SVG, 6 dots are added,
all at T-joints that had none, and none removed.

tst_junctiondots exports fixtures/junction_dot_shared_potential.qet and
checks for both dots; it fails on master with the dot above K3 missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:57:27 +13:00
ispyisail 69aed0cf84 Merge pull request #1287 from ispyisail/fix/xref-bottom-stack
Fix cross-references printing over each other at the folio bottom (#1178)
2026-10-04 10:55:24 +13:00
ispyisail 05de3b754c Merge pull request #1286 from ispyisail/fix/pdf-document-id-from-content
Fix repeatable PDF changing when a project is regenerated (#1178)
2026-10-04 10:54:52 +13:00
ispyisail 519f61e2cf Merge pull request #1285 from ispyisail/fix/element-search-ranking
Fix symbol search offering coils for "NC contact" (#1178)
2026-10-04 10:54:38 +13:00
ispyisail 30d29b933a Stack cross-references snapped to the bottom of a folio instead of overlapping (#1178)
With "snap to bottom", each master's cross was centred under it at the
bottom of the folio on its own, so two coils in one column drew their
crosses on the same spot and the references printed over each other.

The crosses of a folio are now placed together: when two would overlap,
the one of the higher element goes above the other, so a column of
coils gets its crosses in the same order. Crosses that do not overlap
stay where they were. Moving an element sideways now re-places them
too, since it can move a cross into or out of another's column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 08:54:13 +13:00
ispyisail ef96b7d948 Give a repeatable PDF the same document id however the project was made (#1178)
With SOURCE_DATE_EPOCH set, the document id came from the project's uuid
and a hash of the .qet file. A project generated again from the same
data has new uuids, so its PDF differed in the id alone (XMP DocumentID
and trailer /ID, 120 bytes) although every page was the same.

Qt now writes a fixed placeholder id, and once the file is written the
placeholder is replaced with an id derived from the bytes of the PDF:
the same pages give the same id. Same length, so no offset changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 08:54:13 +13:00
plc-user 7e8510cea7 Merge pull request #1284 from ispyisail/translate/en-wires-per-terminal
Update the English translation (qet_en.ts)

Thank you ispyisail!
2026-10-03 21:51:40 +02:00
ispyisail a13cab0945 English: translate the 48 new entries in qet_en.ts
lupdate (run on qet_en.ts only) found strings added since the last
update that the English interface showed in French:

- wires per terminal (#1272, #1273): the rules in Settings > General
  and Project properties, the experimental-feature warning, and the
  tooltip on a terminal that refuses a conductor (both plural forms);
- Display > Show (#1268): the item kinds and the "%n kinds of items
  hidden" status message;
- the 3D mouse settings page.

Terms follow the existing English: conductor (conducteur), terminal
(borne), sheet (folio), sheet reference (renvoi de folio). The menu
"Affichage" is "Display", so its "Afficher" submenu is "Show" rather
than lupdate's guess "Display". lrelease reports 3573 finished,
0 unfinished. Other languages are untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 08:36:32 +13:00
ispyisail c49561a76c qet-mcp: better symbol search for NO/NC and schematic symbols (#1178)
qet_element_search matched every word as a substring of the names and
the path, so "NC contact" offered a remanence coil ("nc" in "remanence",
"contact" in its folder) and an outlet before any auxiliary contact,
"normally closed contact" found nothing, and "emergency stop" ranked a
maker's part and two assembly-plan drawings above the push button.

- A word of one or two letters must now be a whole word. Longer words,
  and short ones with a digit, still match inside a word, so German
  compounds ("schutz", "klemme") and "3p" find what they found before;
  on 37 sample queries no result is lost.
- NO, N/O, "normally open", "normalement ouvert" are one word, and NC,
  N/C, NF, "normally closed", "normalement fermé" another.
- Symbols under *_graphics, *_manufacturers_articles and
  *_miscellaneous_unsorted come after every other match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 08:34:29 +13:00
plc-user f09be8007f Add German part to "CREDIT" 2026-10-03 21:27:33 +02:00
Jeff Patterson 4fe31dfefa Gave the circle, ellipse and arc icons the line tool's handles, and kept crop a dashed box at 16 px
Circle and ellipse had a blue square handle and the arc none; all three
now have hollow 3 px handles and 1 px lines, like draw-line. Breeze draws
transform-crop as a crop mark at 16 px and a dashed box at 22, so a 16 px
dashed box in ico/breeze-added/16/ replaces Breeze's 16 and 32 px art.
2026-10-03 12:20:38 -05:00
Jeff Patterson 379cb72941 Fixed tst_importcategory on macOS
The test set the elements folder in a settings file under XDG_CONFIG_HOME,
which macOS does not read, so common:// paths did not resolve and the
first placement failed. It now passes --common-elements-dir.
2026-10-03 12:20:38 -05:00
Jeff Patterson e4edbe53e1 Split the icon licenses in ico/copyright into one paragraph per license (#1269)
The icon section was one Breeze (LGPL) paragraph that listed GPL and
CC BY-ND icons in its text, so a DEP-5 reader saw every icon as LGPL.
Each license now has its own Files paragraph and License text.
2026-10-03 12:20:38 -05:00
Jeff Patterson da90cb8452 Served the remaining Breeze icons from ico/breeze and dropped unused icon files (#1269)
The search panel's close button and the five KDE Breeze icons that sat in
ico/breeze-icons/ now come from ico/breeze/, with 16, 22 and 32 px art.
André's three SVGs moved from ico/generated/ to ico/scalable/. Removed the
18 .svgz files and the QETIcon and QETOxygenLogo icons nothing used.
2026-10-03 12:20:38 -05:00
Andre Rummler d76f3528ae AutoNum: a lot of changes
Schemas now get saved with a UUID and do not have to be looked up according to the form. They can be renamed and it gets reflected in existing elements. The GUI for
editing them is now much easier, e.g. forms can be reshuffled. A formula can be applied. Many options now also for conductors and sheets.
2026-10-03 18:09:33 +02:00
Laurent Trinques 3bf1a7b86b Merge pull request #1273 from ispyisail/feature/wires-per-terminal-chains
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m48s
Wire terminals in a chain, not a star, under a wire limit
2026-10-03 16:56:02 +02:00
Laurent Trinques 389fd454da Merge pull request #1278 from ispyisail/fix/301-dxf-picture-warning
Fix DXF export warning about pictures hidden by View > Show (#301)
2026-10-03 16:38:42 +02:00
Laurent Trinques dedd3b6d03 Merge pull request #1272 from ispyisail/feature/wires-per-terminal
Add an optional limit on wires per terminal (discussion #1158)
2026-10-03 16:35:10 +02:00
ispyisail 9e3376919c Merge branch 'feature/wires-per-terminal' into feature/wires-per-terminal-chains
# Conflicts:
#	sources/wiringrules.cpp
2026-10-04 01:43:37 +13:00
ispyisail 3150b42087 Warn with a pop-up, not a box, when a wires-per-terminal rule is turned on
Review of #1272: scorpio810 asked for a warning box for users; the inline
box added in 9a55939bb is replaced by a pop-up, as the element editor
gives for unnamed terminals. When OK in Settings > General or in Project
properties turns a rule on (a limit where there was none, or one wire
per folio report), a warning says the rules are experimental, may change
with settings to redo if wires and conductors become separate objects,
and can all be turned off in Settings > General. OK goes on. Changing a
limit that was already on, or turning rules off, shows nothing.

tst_wiringrules: when the warning is due (WiringRules::turnsRuleOn()).
Checked in the GUI from both pages, and that a plain OK shows nothing.

English for translators: "Warning"; "The wires-per-terminal rules are an
experimental feature. They count wires as QElectroTech represents them
today. They could change, and your settings need redoing, if wires and
conductors become separate objects in a future version. All these rules
can be turned off in Configure QElectroTech > General."

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-04 01:43:21 +13:00
ispyisail fc4e589d60 View > Show: no DXF picture warning when pictures are hidden
The DXF export warns that pictures become outlines. Pictures hidden by
View > Show are left out of the export, so they no longer trigger it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:32:03 +13:00
ispyisail 79b595d66d Merge branch 'feature/wires-per-terminal' into feature/wires-per-terminal-chains 2026-10-04 01:13:09 +13:00
ispyisail 9a55939bb4 Warn that the wires-per-terminal rules are experimental
Review of #1272 (scorpio810, after arummler's note that the rules would
need redesigning if wires and conductors become separate objects): a
warning box, with the standard warning icon, at the top of the rules in
Settings > General and in Project properties. It says the feature is
experimental, counts wires as QElectroTech models them today, and that
the rules, and the user's settings, could change if wires and conductors
are separated.

English for translators: "Experimental feature. These rules count wires
as QElectroTech represents them today; they could change, and your
settings need redoing, if wires and conductors become separate objects
in a future version."

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-04 01:13:09 +13:00
saschbe 7c963bf8e4 docs: document building and running the test suites 2026-10-03 14:06:05 +02:00
Laurent Trinques 5fe9cc76e3 Merge pull request #1271 from ispyisail/feature/qet-mcp-wires-per-terminal
Add qet_check rules for crowded terminals and folio reports
2026-10-03 13:55:16 +02:00
Laurent Trinques 5044d3d235 Merge pull request #1270 from ispyisail/fix/script-delete-element-wires
Fix qet.deleteElement() leaving the symbol's wires in the project
2026-10-03 13:54:21 +02:00
Laurent Trinques cf7be1bebf Merge pull request #1268 from ispyisail/feat/301-show-by-kind
Add View > Show to hide texts, shapes and pictures (bugtracker #301)
2026-10-03 13:53:50 +02:00
ispyisail 09380f0ff7 tst_wiringrules: skip deletingASymbolChainsTheWires on a build without scripting
It drives QElectroTech with --run, which such a build does not have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-04 00:24:29 +13:00
ispyisail 5db92384a5 tst_wiringrules: skip the --run checks on a build without scripting
The project's CI builds QElectroTech without scripting, where --run is
not an option and the process never exits, so the test waited out its
timeout and failed (#1272's stack, CI run 37118548599). The --run checks
now skip there, as the script tests are left out of such builds; the
rules and the save round trip still run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-04 00:24:20 +13:00
ispyisail 4372051a51 Merge branch 'feature/wires-per-terminal' into feature/wires-per-terminal-chains 2026-10-04 00:24:20 +13:00
ispyisail 17e9f53b81 View > Show: keep the wire text of a potential when its wire is deleted
With "one text per potential", deleting the wire that carries the text
moves it to another wire of the potential, but only if the text
isVisible(). While wire texts were hidden by View > Show none was, so the
text was never moved and the potential came back with no text.

ShownKinds::wantsVisible() also counts a text hidden only by its kind.
Also drop the always-true/false enum range checks clang warned about.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:07:16 +13:00
ispyisail efd37538e0 Settings > General: widen the wire limit box so "Sans limite" fits
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 23:51:14 +13:00
ispyisail b5845b44a3 Merge branch 'feature/wires-per-terminal' into feature/wires-per-terminal-chains 2026-10-03 23:51:14 +13:00
plc-user 538d4967cd sync part of ico/copyright with English part of ELEMENTS.LICENSE 2026-10-03 12:21:58 +02:00
ispyisail 3a04a42da8 Merge branch 'feature/wires-per-terminal' into feature/wires-per-terminal-chains
# Conflicts:
#	tests/qttest/tst_wiringrules.cpp
2026-10-03 23:17:49 +13:00
ispyisail 7ba820f8f0 Set the wires-per-terminal limits for the application, a project can override
The limits were per project only. They are now set once in Settings >
General, under the master switch: the most wires per terminal and "one
wire per folio report". Every project follows them.

Project properties > "Conducteurs par borne" gets "Utiliser les réglages
de l'application", ticked by default, showing the application's values
greyed. Unticked, the project keeps its own values, saved in the project
as before, so a drawing that needs its own limit carries it to any
computer. A project's own "no limit" overrides an application limit too.

QETProject::wiringRules() now returns the rules in force (the project's
own, or the application's), so the code that checks a wire is unchanged;
projectWiringRules() returns what the project itself stores. A project
that follows the application still writes nothing, so it saves exactly as
before.

tst_wiringrules: which rules win, the save round trip of a project's own
"no rule", and through the real binary that the application's limit
refuses a wire unless the project overrides it or the master switch is
off (checked to fail with the application's rules ignored). ctest 55/55.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 23:16:43 +13:00
Laurent Trinques e5785594bc Resize splash file 2026-10-03 11:58:12 +02:00
ispyisail 2e2801b617 Fix qet.deleteElement() leaving the element's wires on the folio
qet.deleteElement() built its DeleteQGraphicsItemCommand from the element
alone. The Delete key's selection also carries the wires on the element's
terminals (DiagramContent's conductors to update), and the command removes
those with it. From a script they stayed: still listed by
qet.conductorUuids(), still saved, attached to an element that was gone.

The wires on the element's terminals now go into the command, as for the
Delete key. tst_scriptconductoruuid checks that no wire is left with an
end on the deleted element (it failed before this change: 1 left).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 22:26:51 +13:00
ispyisail 173346c712 qet-mcp: qet_check finds crowded terminals and folio reports with several wires
Discussion #1158 asks for a limit on the wires per terminal. Before any
limit, two read-only checks let anyone count their own projects:

- crowded_terminals: terminals with more than four wires (two double
  ferrules, one either side of the screw), folio reports left out;
- reports_with_several_wires: folio report arrows with more than one
  wire, the rule plc-user proposes for reports.

Both are info, not errors: cable, busbar and single-line symbols carry
more wires on purpose, and a third of the shipped examples' reports have
several. Over the 24 examples: 23 crowded terminals in 10 projects
(the worst, 19, is a cable symbol in affuteuse_250h.qet) and 130 reports
in 4 projects. Plain SQL over the conductor table, so they work on any
current QElectroTech.

test_qet_mcp: the checks are read-only, the exact-answer test lists them,
and a coil wired to five others is reported once with its count (checked
to fail with the threshold raised). 398 tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 22:26:42 +13:00
ispyisail 5d27ed8156 Wire terminals one after another under a wires-per-terminal limit
Discussion #1158, stacked on the limit itself. Two of QElectroTech's own
tools wire several terminals as a star, every terminal to one of them:
"create wires in a drawn polygon" (also qet.addConductor()), and deleting
a symbol, which rewires the far ends to keep the potential. Six terminals
give the hub five wires, which the project's limit forbids.

When the project sets a limit (and the master switch is on), both now
wire the terminals one after another instead, from the top left one to
the nearest not yet wired (WiringRules::chainOrder()). The polygon tool
also skips a wire whose terminal is already full. Without a limit, both
build the star as before.

tst_wiringrules: the chain order, and through the real binary that
deleting a symbol wired to four others leaves them with at most two
wires each under a limit, three on one of them without (checked to fail
with chaining turned off).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 21:49:06 +13:00
ispyisail ee8e7c3450 Fix qet.deleteElement() leaving the element's wires on the folio
qet.deleteElement() built its DeleteQGraphicsItemCommand from the element
alone. The Delete key's selection also carries the wires on the element's
terminals (DiagramContent's conductors to update), and the command removes
those with it. From a script they stayed: still listed by
qet.conductorUuids(), still saved, attached to an element that was gone.

The wires on the element's terminals now go into the command, as for the
Delete key. tst_scriptconductoruuid checks that no wire is left with an
end on the deleted element (it failed before this change: 1 left).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 21:49:06 +13:00
ispyisail 20e134f2bd Add a wires-per-terminal limit, with an application master switch
Discussion #1158: a real terminal takes one or two wires (four with
double ferrules), a folio report is a virtual point that carries one, but
QElectroTech lets any number of wires be connected anywhere.

Project properties > General gets a "Conducteurs par borne" group:
- the most wires a terminal may take (0, the default, is no limit);
- "one wire per folio report".
When set, a new wire that would go past the limit is refused: the
terminal shows red while dragging, as for any refused link, and a tooltip
says why on release. Auto-connect on placing, moving or multi-pasting a
symbol skips full terminals, and qet.addConductor() refuses with its own
message. Wires already drawn, opening, pasting and undo are never
refused.

Settings > General gets a master switch, on by default, that turns every
rule of this feature off in every project; the project group is greyed
with a note while it is off.

The rules live in wiringrules.cpp. The project setting is saved as
<wiring_rules .../> only when a rule is on, so a project that never used
it saves exactly as before; an older version drops it, which means "off".

tst_wiringrules covers the rules, the save round trip and, through the
real binary, that a wire past the limit is refused unless the master
switch is off (checked to fail with the limit check removed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 21:42:12 +13:00
ispyisail 649f06b3b8 Add View > Show to hide texts, shapes, pictures by kind (bugtracker #301)
A submenu under View (Affichage > Afficher) with one tick per kind:
symbol texts, wire texts, free texts, shapes, pictures, tables and
cross-references. Unticking one hides it on every folio of every open
project; the status bar says how many kinds are hidden. Session only,
not saved in the project.

Each item is tagged with its kind by its constructor (ShownKinds::tag),
so an item created while its kind is hidden starts hidden whatever
created it. A tag rather than type(): the cross-reference under a
contact's label is a plain QGraphicsTextItem.

Items that already hide themselves (wire text switched off, one text per
potential, a cross-reference snapped elsewhere) now go through
ShownKinds::setVisible(): the kind state can only veto a show, and
apply() re-shows only what it hid itself (hidden_key), never what an
item hid. Conductor::updateTextVisibility() replaces the same visibility
rule pasted in diagrampropertiesdialog.cpp and qetscriptapi.cpp.

Hidden items are not selectable (Qt), so Select All, copy and delete
skip them. Print, PDF and image export render the scene and leave them
out; the DXF export walks items itself and skips them through
ShownKinds::isHidden(). CrossRefItem::linkedChanged() keeps following
its slaves while hidden only by kind, so its label is right when shown.

Known limitation: QGraphicsScene::itemsBoundingRect() still counts hidden
items, so Zoom to fit and borderless image export keep their space.

Verified: tst_shownkinds (12 cases, two mutation checks) and ctest 55/55;
GUI hide/show of symbol texts, wire texts (incl. one text per potential),
free texts, shapes and cross-references returns pixel-identical folios;
with nothing hidden, --export-dxf of the 24 examples is byte-identical to
master (133 files) and --export-pdf renders identically (133 pages).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 19:44:12 +13:00
ispyisail 6144da0f91 Merge pull request #1266 from ispyisail/feature/qet-mcp-layout-ops
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m57s
Add qet_edit operations that place symbols so wires come out straight
2026-10-03 18:35:52 +13:00
ispyisail b899d46453 Merge master (#1264, #1265) into feature/qet-mcp-layout-ops
README and tests: both PRs added text at the same places; kept both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 15:56:00 +13:00
ispyisail 78111063fb Merge pull request #1265 from ispyisail/feature/qet-mcp-layout-check
Add qet_layout_check: score a drawing and fix wires that jog
2026-10-03 15:52:16 +13:00
ispyisail 41ba7d267f Merge pull request #1264 from ispyisail/feature/script-terminal-position
Add terminalPosition() and conductorPath() to the scripting API
2026-10-03 15:51:43 +13:00
ispyisail d952fb9a0d qet-mcp: place_element takes an angle, for symbols turned along a rung
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 13:41:25 +13:00
ispyisail 372e541a00 qet-mcp: ops that line symbols up so wires come out straight
A wire is straight only when its two terminals are exactly in line, and
qet_edit placed a symbol only by its origin, so an assistant had to guess
where the terminals would land -- and usually missed by a few pixels.

- place_element adds a symbol with one of its terminals in line with
  another symbol's, gap pixels away on the side that terminal faces, and
  notes when the new symbol's terminal faces the wrong way to meet it.
- align_terminal moves a placed symbol across so two terminals line up.
- align_elements lines up symbols' boxes (left/center/right/top/middle/
  bottom), as Edit > Align does.
- distribute_elements spaces symbols' origins evenly, or a pitch apart,
  keeping symbols on the grid on it.

place_element and align_terminal need qet.terminalPosition(); on a build
without it the edit stops before running, naming the missing call, and
every other op still works there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 13:37:00 +13:00
ispyisail cc23cf820f Scripting: add terminalPosition() and conductorPath()
A script laying out a drawing could read where a symbol is
(elementGeometry) but not where its terminals are, so it could not place
one symbol with a terminal exactly in line with another's -- the one
thing that makes the wire between them straight. And it could read a
wire's drawn path only through conductorSegments(), which names the wire
by a terminal and so refuses any terminal carrying two wires: 282 of the
3120 wires in the shipped examples.

terminalPosition(folio, element, terminal) returns where a wire docks on
the terminal, in folio coordinates, and which way it leaves (n/e/s/w,
the element's rotation included). conductorPath(folio, uuid) returns any
wire's drawn path as points, by its uuid.

tst_scriptlayoutreads checks the two against each other on every wire of
a fixture: each path starts and ends where its terminals' positions say,
and leaves each the way it faces; where conductorSegments() can name a
wire, both give the same points; and a quarter turn turns the facing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 13:26:34 +13:00
ispyisail 5f2d31bff7 qet-mcp: add qet_layout_check, a score and fixes for how a drawing reads
A wire is straight only when its two terminals are exactly in line, and a
symbol is placed by its origin with its terminals at an offset from it, so
symbols an assistant places "under each other" by eye land a few pixels
apart and the wire jogs. Nothing told it so.

qet_layout_check reads every symbol and every wire's drawn path through
the scripting API (the default path is not saved in the file) and reports
a 0-100 score with: wires that jog where moving one symbol would make them
straight, extra bends, wires through symbols, overlaps, symbols off the
grid and crossings. "fixes" is one move_element per symbol, planned
together so they can be applied in one qet_edit call: a straight wire pins
its symbols, a move never lands a symbol on another or across a wire, and
symbols lined up with each other go onto the grid together. Styles: iec
(columns), nfpa (rungs) or auto. Read-only.

Over the 23 shipped examples, applying the fixes never lowers a score and
a second check proposes no further moves on 21 of them; a sloppy
assistant-style drawing goes from 40 to 100 in one round, in both styles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
2026-10-03 13:17:09 +13:00
ispyisail 18568dfd53 Merge pull request #1263 from ispyisail/fix/1238-empty-xrefpos
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m59s
Fix #1238: cross-reference drawn over the contact's label
2026-10-03 11:01:58 +13:00
ispyisail 9749985089 Fix #1238: cross-reference drawn over the label in older projects
XRefProperties read the stored cross-reference position with
QMetaEnum::keyToValue() and cast the result straight to
Qt::AlignmentFlag. An empty value gives -1. Earlier versions saved
xrefpos="" into projects and the settings file (18 of the 29 shipped
examples that carry cross-reference settings have it), and -1 matches
no branch of DynamicElementTextItem::setXref_item(), so the
cross-reference stayed at (0,0): the top-left corner of the label,
on top of it.

fromXml() fell back to AlignBottom only when the attribute was
missing, and fromSettings() only when the key was missing (#296), so
an empty value kept producing the bad position and was saved back
empty on every save.

Both now go through one helper that returns AlignBottom for an empty,
unknown, or not-offered value. The next save writes "AlignBottom", so
affected projects heal once resaved.

tst_xrefpos covers fromXml(), fromSettings() and the rewrite on save;
it fails 5 of 14 cases without this change. Exporting
examples/2612_ats_singlephase.qet to SVG before and after shows every
slave cross-reference moving from over its label to below it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ELKpbGxqJd7EFiTUypBtVe
2026-10-03 10:18:06 +13:00
Laurent Trinques e1f14e4b2f Merge pull request #1258 from ispyisail/fix/route-close-symbols
Fix wire routing looping between close symbols (#1178)
2026-10-02 23:15:33 +02:00
Laurent Trinques d8add2b8a0 Merge pull request #1257 from ispyisail/feature/pdf-reproducible
Add repeatable PDF export when SOURCE_DATE_EPOCH is set
2026-10-02 23:15:05 +02:00
Laurent Trinques ff64fdcb00 Merge pull request #1256 from ispyisail/fix/route-review-followups
Fix the follow-ups from the review of wire routing (#1245)
2026-10-02 23:14:51 +02:00
Laurent Trinques e42d09da45 Merge pull request #1255 from ispyisail/fix/pdf-link-order
Fix PDF cross-reference links coming out in a different order each run
2026-10-02 23:14:31 +02:00
Laurent Trinques 12ca26420b Merge pull request #1262 from ispyisail/fix/cli-common-elements-dir
Fix --common-elements-dir being ignored by --run and the export
2026-10-02 23:06:15 +02:00
Laurent Trinques 04b7659180 Merge pull request #1261 from ispyisail/fix/english-untranslated
Fix 175 strings showing in French in the English interface
2026-10-02 23:04:14 +02:00
Laurent Trinques 7d61acebd9 Merge pull request #1260 from ispyisail/fix/live-mode-polish
Fix live mode's English text, settings path and screenshot (#1178)
2026-10-02 23:02:54 +02:00
Laurent Trinques bb005f16f4 Merge pull request #1259 from neitri/master
Russian translate new dialog text
2026-10-02 23:01:07 +02:00
Laurent Trinques 69a71d775f Merge pull request #1254 from ispyisail/fix/settings-dir-macos
Fix #1246: QET_SETTINGS_DIR reads the wrong folder on macOS
2026-10-02 22:58:14 +02:00
Laurent Trinques 069e7d815d Merge pull request #1250 from ispyisail/feature/mcp-folio-presets
Add paper size presets for folios to the scripting API and MCP
2026-10-02 22:57:04 +02:00
ispyisail 0559087617 Move tst_pdfreproducible so it does not clash with #1255
Both added their test right after tst_pdfpagesize in
tests/qttest/CMakeLists.txt, so whichever merged second conflicted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJw3u5AdKQgQVPug8CzuMQ
2026-10-03 09:49:54 +13:00
ispyisail 5ed5eb82ee Honour --common-elements-dir and the other folder options in headless runs
Reported on #1178. Two things kept --common-elements-dir from working for
--run and the command-line export:

- main() runs both before QETApp exists, and only QETApp::parseArguments()
  applied the folder options, so they were ignored. The application of
  those options is now QETApp::applyDirectoryArguments(), static, and the
  two headless branches call it too. They also drop the options from the
  arguments they pass on: the export reads its project and output by
  position, so "--export-pdf --common-elements-dir=X p.qet out.pdf" took
  the option for the project ("Project not found").
- commonElementsDir() read the saved collection path before the
  command-line one, so a user's own setting beat a folder given for this
  run. The command line now comes first, in the GUI too.

Tested with a --run script adding a symbol that exists only in a given
folder: master fails with or without a saved collection path; this
resolves it, before or after --run, and over a saved path to another
folder. Without the option it still fails, and a saved path to the
folder still works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ELKpbGxqJd7EFiTUypBtVe
2026-10-03 09:33:24 +13:00
ispyisail 8361a1efce Build and work on Qt before 6.8
QPdfWriter::setDocumentId() is new in Qt 6.8, QTimeZone::UTC in 6.5 and
QHashSeed in 6.2, so the build failed on Qt 6.4 (Ubuntu 24.04). Qt before
6.8 also writes no /ModDate and no XMP metadata, and setDocumentDate()
then gave up and left the time of the export in the file. Those two are
now rewritten when present, and no document id is set where Qt writes
none.

tst_pdfreproducible also checks that the xref table still matches the
file after the dates are rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJw3u5AdKQgQVPug8CzuMQ
2026-10-03 09:30:11 +13:00
ispyisail ee62f2a4e8 English: translate the 175 entries still unfinished in qet_en.ts
lupdate had added them, but nobody filled them in, so the English
interface showed these strings in French: align, group and ungroup,
arc and fillet, centre on cursor, paste at the original position, wire
hops, the collection prefixes, the materials list, saving and loading
settings, the terminal-name check in the symbol editor, recovery files,
the macro recorder's messages and a few more.

About 30 of them were pre-filled by lupdate from an identical string
elsewhere and left unfinished; some of those guesses were wrong (a
dialog's "Annuler" offered as "Undo"). All are reviewed here. Plural
messages get both English forms. lrelease now reports 3487 finished,
0 unfinished. Other languages are untouched.

Terms follow the existing English: sheet (folio), terminal (borne),
conductor (conducteur), parts list (nomenclature); "liste de matériaux"
is "materials list" so it is not confused with the parts list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ELKpbGxqJd7EFiTUypBtVe
2026-10-03 09:19:08 +13:00
ispyisail 5859bc3353 Live mode: English text, the right settings tab, folio-only screenshot
Reported on #1178 after a first try of live mode on macOS:

- The English interface showed the live-mode setting in French. The
  strings were in lang/qet_en.ts but untranslated. This fills in English
  for live mode, the script buttons, the macro recorder and the Connect
  an AI assistant dialog (101 entries); other features' untranslated
  English entries are left for their own change.
- The setting is on Configurer QElectroTech > Général > Projets, but the
  start-up warning, the MCP server's error and its README all said
  "Configurer > Général". All three now name the Projects tab, and the
  README and the server name the English menus and buttons.
- qet_live_screenshot grabbed the whole viewport, so a tall window around
  a landscape folio came back mostly empty. It now crops to the folio's
  frame and title block where they are on screen (the whole viewport if
  the folio is scrolled out of sight), and says so in cropped_to_folio.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 09:14:57 +13:00
Mikhail d8b687530d Russian translate new dialog text 2026-10-03 00:45:20 +05:00
ispyisail 84cda1f0b0 Routing: no loops between close symbols, and route inside frames
Reported on #1178: with "route": "avoid", two contacts one above the
other, wired bottom terminal to top terminal 30 px apart or closer, got
a five-segment loop (at 20 px: v 80, h -20, v -140, h 20, v 80) that ran
down through the lower contact and back up past the upper one.

The cause was exitPoint(): it walked out of a terminal until clear of
every obstacle, so with another symbol in front it walked through that
symbol, and the search then had to come back. The same walk made a wire
between two symbols inside a frame (a cabinet drawn as one element)
leave through the frame's side, go round, and cross back in.

The router now knows each terminal's own symbol (Request::start_symbol,
end_symbol; applyRoute() fills them):
- two terminals facing each other on one line with nothing between
  them are joined straight, however close;
- the exit walks through the margin around other symbols but never
  through one; a terminal pointing straight into another symbol gets
  "no-route" instead of a route through it;
- an obstacle drawn around either end's own symbol is left out.
Without the symbols (the old Request), routes are as before.

Rerouting every wire of four shipped examples (perceuse, affuteuse_250h,
Polonez, industrial; 1331 wires): master routes 121 of them through
another symbol, this none (10 pass through a second symbol lying
exactly on an end symbol's rectangle, which no route can avoid). 99
wires that master routed through a symbol now get "no-route" and keep
their path. The 1232 wires both route are 6 % shorter in total
(436,144 -> 410,608 units) with 11 % fewer bends (2256 -> 2010).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:43:27 +13:00
ispyisail b0d73080ce Routing: follow up scorpio810's review of #1245
- Rename the `near` lambda in Conductor::setPathPoints() to `isNear`:
  `near` and `far` are empty macros in the Windows headers (minwindef.h).
- Reword the log when setPathPoints() refuses a route. "does not join the
  two terminals" named only one of its reasons; the router always returns
  the two terminals and at least one exit point, so a refusal means the
  route is not a run of horizontal and vertical segments between them.
- README: running along another wire is penalised, not forbidden, so two
  wires can end up drawn on top of each other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:13:31 +13:00
ispyisail 8bbdafe2e4 qet-mcp README: the preset note says frame, not PDF page
The note the op returns reads "...; frame 1223.25 x 791.25 pt"; the README
quoted it as "PDF page".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 07:51:44 +13:00
ispyisail 51d20abca4 Write the same PDF for the same project when SOURCE_DATE_EPOCH is set
Two exports of one project differed in the creation and modification
dates (the time of the export, in local time) and in the document id
(random). With SOURCE_DATE_EPOCH set (reproducible-builds.org),
--export-pdf now uses the time it names, in UTC, and a document id
derived from the project: version 5 of the project uuid with the
SHA-256 of the project file, so it is the same for the same file and
changes when the file does. Qt has no setter for the dates, so
PdfLinks::setDocumentDate() rewrites them in the document information
and the XMP metadata after the file is written, fixing the XMP /Length
and shifting the xref table. Without the variable nothing changes.

Only --export-pdf reads it; the print window's PDF export does not, as
a person exporting by hand wants the real date.

qet_export (misc/qet-mcp) gains "reproducible" and "source_date_epoch".
It reports whether the build honoured the variable, since an older one
ignores it.

tst_pdfreproducible exports examples/741.qet four times and requires
identical bytes and the epoch's date; it fails with either this change
or the hash seed fix reverted. Projects with cross-reference links also
need their links in a fixed order, which is a separate fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 07:50:28 +13:00
ispyisail 580cbb1cb1 Merge remote-tracking branch 'upstream/master' into feature/mcp-folio-presets
# Conflicts:
#	misc/qet-mcp/README.md
#	misc/qet-mcp/qet_mcp.py
2026-10-03 07:44:54 +13:00
ispyisail 6e478e0e3f Fix the hash seed main() asks for having no effect
main() set QT_HASH_SEED=0 so that hashes, and so the XML a project is
saved as, come out the same in every run. Qt reads that variable once,
when the first hash is made, and that happens before main(): printed
right after the qputenv(), QHashSeed::globalSeed() was a different
random number in every run. The setting never took effect.

QHashSeed::setDeterministicGlobalSeed() does what the variable was
meant to, for every hash made from then on. The variable is kept for
the processes QElectroTech starts.

One visible effect: the PDF engine writes its fonts in QHash order, so
--export-pdf of the same project gave different files from run to run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 07:40:32 +13:00
ispyisail 76cad625af Read QET_SETTINGS_DIR/QElectroTech/ on macOS too (#1246)
On macOS, Qt names the settings subfolder after the organization domain
when one is set, so QElectroTech read <folder>/qelectrotech.org/ while
main.cpp, tst_settingsdir and qet_mcp.py all use <folder>/QElectroTech/.
Clear the domain when QET_SETTINGS_DIR is set, on macOS only. Nothing in
QElectroTech reads it; SingleApplication hashes it into its key, so these
runs no longer hand off to an open QElectroTech.

test_qet_mcp.py: find true on PATH (/usr/bin/true on macOS), and compare
the found binary with samefile() on case-insensitive file systems.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 07:30:07 +13:00
ispyisail 8e6dc1a849 Write a PDF's cross-reference links in the same order in every run
injectCrossRefLinks() drew each link as it found it: the contacts of a
cross-reference come from a QMultiMap keyed by Element pointer, and the
scene's items in stacking order. Both change from run to run, so the same
project exported twice gave two different PDFs whose only difference was
the order of the link annotations.

The links are now collected and drawn sorted by their rectangle on the
page (top, left, bottom, right), with the target as the tie-break. The
links themselves are unchanged. The print window's PDF export goes
through the same function.

tst_pdflinkorder exports examples/industrial.qet five times in separate
processes and requires the same links in the same order. It fails on
the old code (lists differ at index 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 07:24:46 +13:00
Laurent Trinques 25084a44da Update splash file
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m40s
2026-10-02 15:55:51 +02:00
Laurent Trinques 30cd96e240 Update translations files 2026-10-02 15:49:50 +02:00
Laurent Trinques 8e164df351 Merge pull request #1249 from ispyisail/fix/pdf-landscape-paper-size
Fix landscape folios exporting slightly off the paper size in PDF
2026-10-02 15:06:12 +02:00
Laurent Trinques 2a72e77b3e Merge branch 'master' into fix/pdf-landscape-paper-size 2026-10-02 15:06:00 +02:00
Laurent Trinques 8fc43aa30f Merge pull request #1245 from ispyisail/feature/mcp-route-around
Add wire routing around symbols to the scripting API and MCP
2026-10-02 14:05:13 +02:00
Laurent Trinques e413fde272 Merge pull request #1253 from jp2images/svg-icons-b
Convert the remaining theme icons to SVG and drop unused PNGs (#1063)
2026-10-02 13:56:56 +02:00
Laurent Trinques d05e396fb1 Merge pull request #1248 from ispyisail/feature/wiring-terminal-identity
Add terminal index and uuid to the wiring list export
2026-10-02 13:44:52 +02:00
Laurent Trinques 8a63a2d529 Merge pull request #1247 from ispyisail/fix/stable-potential-text
Fix wire number moving between tied wires from run to run
2026-10-02 13:43:37 +02:00
Laurent Trinques 8baeac16e9 Merge pull request #1244 from ispyisail/fix/windows-longpath-manifest
Add longPathAware to the Windows manifest
2026-10-02 13:22:32 +02:00
Laurent Trinques cdc8e9db13 Merge pull request #1243 from ispyisail/fix/element-long-paths
Fix symbols in deep folders on Windows (paths of 260+ characters)
2026-10-02 13:13:33 +02:00
Laurent Trinques 29527d11cb Merge pull request #1242 from ispyisail/fix/import-category-by-name
Fix placing a symbol twice in a project with an extra symbol folder
2026-10-02 13:06:09 +02:00
Laurent Trinques 8a27ae6cee Merge pull request #1241 from ispyisail/fix/editor-parts-list-drag
Fix dragging parts selected in the symbol editor's Parts list
2026-10-02 12:58:17 +02:00
Laurent Trinques e13023f916 Merge pull request #1240 from ispyisail/fix/project-properties-ok-marks-modified
Fix Project properties > OK marking the project modified with no edit
2026-10-02 12:54:46 +02:00
Laurent Trinques f4869f6c0a Merge pull request #1239 from ispyisail/fix/project-properties-page-num-placeholder
Fix Project properties saving the Page Num placeholder as a numbering
2026-10-02 12:48:25 +02:00
Jeff Patterson 6753aa3ce7 Loaded icons by theme name and removed the PNGs nothing uses 2026-10-02 05:47:40 -05:00
Jeff Patterson 0d473a3889 Imported the Breeze page icons and generated the light and dark icon themes 2026-10-02 05:47:40 -05:00
Jeff Patterson 15439cfc84 Traced the last PNG theme icons and took ispyisail's circle, ellipse and fit-window drawings 2026-10-02 05:47:40 -05:00
Jeff Patterson 708dd4ddd2 Served the settings page icons as SVG and kept draw-fillet in the generated themes 2026-10-02 05:47:39 -05:00
Laurent Trinques 7b991891be Merge pull request #1235 from ispyisail/fix/bugtracker-112-editor-snap-zoomed-out
Fix bugtracker #112: no snap in the symbol editor below 100 % zoom
2026-10-02 12:42:52 +02:00
ispyisail 487b67136b qet-mcp presets: include QPageSize outside the QtPdf guard
The preset code uses QPageSize and std::floor whether or not QtPdf was
found; it compiled only because other headers bring them in.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 23:28:58 +13:00
ispyisail ca42080ebe Route a conductor in milliseconds on a crowded folio
The router costed every grid edge against every symbol and every wire
segment on the folio. On the 191-symbol, 366-wire Polonez example that
is about 145 million tests and 700 ms per route, so a qet_edit routing
some 40 wires there ran into the scripting run's 30-second limit.

Each symbol and wire now visits only the grid edges it can touch, found
by binary search, and applies the same exact test to them as before, in
the same order. Rerouting every conductor of the 23 example projects
gives the same 11,018 saved segments as before, now 22 ms per route on
Polonez instead of 700.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 23:21:35 +13:00
ispyisail 371ea881e5 Keep longestConductorInPotential's doc comment on the function
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 23:08:32 +13:00
ispyisail 0b4fc8beb0 qet-mcp: size a folio for a sheet of paper (set_folio_border preset)
setFolioBorder(folio, "preset", "tabloid-landscape") picks the column and
row counts and whole-number sizes that fill the sheet best without going
over it, as one ChangeBorderCommand. The title block and headers are
measured from the folio, so it holds for any template on either edge.
Sizes stay whole because the folio properties panel edits them in whole
pixels. Among fills within 1.5 pt of the sheet, the size nearest the
current one wins: from a new folio tabloid landscape is 23 x 70 by 12 x 82,
not 9 x 179 by 5 x 197. A0-A5, letter, legal, tabloid/ledger, each
-portrait or -landscape; folioPresets() lists them, and folioBorder() reads
"width" and "height" of the frame and title block.

Every preset exports on its sheet; landscape ones rely on the previous
commit, which turns a wide page before matching it to a sheet.

The MCP server checks the name up front, probes for folioPresets(), and
the op's note says what was chosen and the frame's size in points.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 23:00:11 +13:00
ispyisail 797d502c0a Put a landscape folio on its standard sheet in --export-pdf
The PDF export sizes each page to its folio, and QPageSize rounds a size
within 3 pt of a standard sheet to the sheet. QPageSize knows the sheets
upright only (bar Ledger), so only portrait folios snapped: an A3 landscape
folio came out as a 1190 x 841 pt page, while an A3 portrait one was
842 x 1191, the sheet. A wide folio is now matched upright and turned.

tst_pdfpagesize exports a fixture with an A3 folio each way and checks both
pages are A3; it fails without the change (1190 x 841).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 22:53:13 +13:00
ispyisail e345d4e5d0 Give each end of the wiring export its terminal index and uuid
Most shipped symbols leave their terminals unnamed, so --export-wiring
wrote both terminal columns empty and a reader had to work each end out
from the .qet: terminal uuid, then its place in the definition, then the
index. The terminal table now keeps the index the scripting API's
addConductor() takes, from both fills of the database (the document and
the folios give the same value: Element::parseTerminal()'s sort, top to
bottom then left to right), and wiring_list_view and the CSV gain
from/to_terminal_index and from/to_terminal_uuid as their last columns.

The index is left empty for two terminals at one point, where the sort
is not stable and the index is not defined; the uuid tells them apart.
The wiring list dialog reads named columns and is unaffected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 22:30:49 +13:00
ispyisail 5e28e015e0 Scripting API and MCP: route a conductor around the symbols in its way
A new conductor gets the default two or three straight segments, which
run through whatever symbol or wire lies between its terminals; scripts
then fix it segment by segment with moveConductorSegment().

ConductorRouter finds an orthogonal path on the folio grid that leaves
and enters each terminal in its own direction, keeps clear of every
element's rectangle, stays inside the border, and charges for bends and
for running along or crossing other wires. qet.routeConductor() and
qet.routeConductorBetween() apply it through Conductor::setPathPoints(),
which pushes the same ChangeConductorCommand a handle drag does, so the
path is saved, survives a reload, and one undo restores the default.
Where no route exists the wire keeps its path and the call says so.

qet-mcp: add_conductor takes "route": "avoid", and a route_conductor op
reroutes an existing conductor (by terminal or by uuid). The router
methods are required only by an edit that routes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 22:28:11 +13:00
ispyisail 4c89f37fd8 Windows: embed a manifest that declares longPathAware
With LongPathsEnabled set in the registry, Windows lets a program open
paths of 260 characters or more only if its manifest says longPathAware.
QElectroTech.exe so far carried the toolchain's default manifest. This one
repeats what the default declares (supported Windows versions, asInvoker)
and adds longPathAware. MSVC takes the .manifest as a source; MinGW gets it
through a small resource file.

Follow-up to #1178 (symbols in deep folders).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:25:46 +13:00
ispyisail e94c6d82c9 qet.addElement: report an unreadable symbol instead of a collision
A symbol file that exists but cannot be read has a null uuid, which the
check against the copy already embedded reported as "would collide with a
different element". Say that the file could not be read, with its full
path and length, since a long path is the usual cause on Windows.

tst_unreadableelement makes the file unreadable through its permissions
and fails on master with the old message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:22:18 +13:00
ispyisail af184ed986 Read symbol files through QFile so paths past 260 characters work on Windows
ElementsLocation::pugiXml() and the qet_directory name lookup opened files
with pugixml's load_file(), which on Windows fails once the full path
reaches MAX_PATH. QFile handles long paths, so exist() and the import
succeeded while uuid(), the name, the informations and the thumbnail of
the same symbol came back empty. Read the bytes with QFile and hand them
to load_buffer().

Reported on #1178 as a false collision from qet.addElement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:22:18 +13:00
ispyisail eb2367e400 Put a potential's one text on the same conductor in every run
With "one text per potential" on, the number is drawn on the
potential's longest conductor. When two conductors tie for longest,
longestConductorInPotential() kept whichever it met first, iterating a
QSet<Conductor *> in pointer order -- which changes from run to run. The
same file, exported twice, could put the number on either conductor:
same numbers, different PDF.

A tie is now broken by where the conductors' ends are on the folio, then
by uuid. Not by uuid first: a file with no conductor uuids gets fresh
ones on every load, and a project generated again by a script gets new
ones each time, while the drawing is the same.

tst_potentialtextcarrier exports a fixture with such a tie eight times;
before the fix it failed in each of three tries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 22:19:39 +13:00
ispyisail ec262d1af9 Fix placing a symbol a second time in a project with an extra category
XmlElementCollection::importCategory() returned the project collection's
first category, taken to be "import". A project can hold other
top-level categories before it: examples/lmdg.qet has "k_elem" first.
Imported symbols were then filed under k_elem/, while every lookup of an
imported symbol is under import/ (QETProject::importElement(),
XmlElementCollection::addElement()). The first placement worked; the
second could not find the copy, tried to import it again and failed.
In the editor the drop silently placed nothing; a script's
qet.addElement() returned "".

Look the category up by name, falling back to the first one as before
when a project has none called "import".

Test: tst_importcategory places the same symbol twice in a copy of
lmdg.qet through --run. It fails without the fix (the second placement
returns "") and passes with it. ctest 45/45.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 21:49:42 +13:00
Laurent Trinques 808d6a3c5b git submodule update --remote elements 2026-10-02 10:39:09 +02:00
ispyisail b4c0e2a52a Fix dragging parts selected in the symbol editor's Parts list
Selecting several parts by clicking them in the Parts list, then dragging
one on the drawing, moved only the part under the cursor; the others
stayed where they were. No selection handles were shown either.

updateSelectionFromPartsList() blocks the scene's signals while it copies
the list selection to the drawing, so selectionChanged never reached
ElementScene::managePrimitivesGroups(), which sets up the decorator that
moves a multi-part selection. Call it once the signals are unblocked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 21:31:59 +13:00
ispyisail a16bac8f84 Snap a moved multi-part selection to the editor grid too (bugtracker #112)
A selection of several parts is moved by ElementPrimitiveDecorator, which
snapped to a fixed 1-unit step at every zoom. That is the "selection is
big" half of #112: below 100 % one part landed on the 10 grid but a
multi-part selection did not, and at 100-400 % it ignored the grid too.

It now snaps to the editor's current grid, as a single part does; Ctrl
still gives the fine 1-unit step. Resizing and arrow-key moves are
unchanged.

Also from review: add rows just below each zoom threshold to
tst_elementviewgrid (each catches a moved threshold the old rows missed),
and use one grid_step in drawBackground() instead of identical x/y copies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 21:09:24 +13:00
ispyisail 82fc56c5e6 Fix Project properties > OK marking the project modified with no edit
Pressing OK in Projet > Propriétés du projet without changing anything
marked the project modified and rewrote its defaults for new folios.
Besides the Page Num placeholder (fixed separately), four causes:

- Title block custom fields: since #271 the dialog offers the
  template's variables for filling in; those left empty came back as
  part of the context. Drop the ones not in the stored context.
- DiagramContext::remove() left the key's show flag behind, so a
  context with a key removed never equalled one without it.
- Guides: applyConf() marked the project modified whether or not the
  guides changed. Compare them like every other setting.
- Wire dash size and cross-reference offsets: the spin boxes cannot
  show the stored default (dash size 1 under a minimum of 2; offset 0
  under a minimum of 40 shown as "Default"), so the clamped value came
  back. Keep the stored value unless the box shows something else, as
  #1204 did for the wire panel.

Checked in the GUI (with the Page Num fix as well) on grafcet,
schema_indus and Projet_vierge: OK with no edit then Ctrl+S saves the
same file as Ctrl+S alone; master writes dash-size 1 -> 2, a
cross-reference offset 0 -> 40, and on Projet_vierge 22 empty custom
fields. New test for DiagramContext::remove() fails without the fix.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 21:04:54 +13:00
ispyisail 003eda2cb8 Fix Project properties saving the Page Num placeholder as a numbering name
In Projet > Propriétés du projet > Nouveau folio, the Page Num box shows
a "Créer un Folio Numérotation Auto" entry when the project has no folio
auto-numbering. Pressing OK, even without touching anything, returned
that entry's text as the numbering's name: it was saved in the project
as auto_page_num (in the interface's language), and since it differed
from the stored empty value the project was marked modified.

When the project has numberings, an empty stored value cannot be shown
at all, so the first numbering came back instead.

Return the stored value unless the user picks an entry (activated() is
only emitted for a user's choice), and never return the placeholder.

Checked in the GUI on grafcet, schema_indus and Projet_vierge: after OK
with no edit and Ctrl+S, master saves auto_page_num="Créer un Folio
Numérotation Auto"; this branch keeps auto_page_num="". An edited field
on the same page (default author) is still saved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 21:04:53 +13:00
Laurent Trinques b485258424 Merge pull request #1237 from ispyisail/fix/bugtracker-344-locked-show-text
Fix bugtracker #344: say why a wire's "show text" box is locked
2026-10-02 09:26:56 +02:00
Laurent Trinques bf0a8bd8d5 Merge pull request #1236 from ispyisail/fix/aux-machine-manufacturer-reference-variables
Fix %{machine_manufacturer_reference_auxiliary1..4} never resolving
2026-10-02 09:24:55 +02:00
ispyisail 59b25b5f18 Fix bugtracker #344: say why a wire's "show text" box is locked
The conductor properties dialog disables the "Texte visible" checkbox
when the folio has "Afficher un texte de potentiel par folio" on, but it
also hides that option, so nothing told the user why the box could not
be ticked. A wire saved with its text off then looked impossible to fix.

The locked box's tooltip now names the folio option and where to change
it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:23:16 +13:00
ispyisail fe8498a26c Fix %{machine_manufacturer_reference_auxiliary1..4} never resolving
AssignVariables::replaceVariable() is a fixed list of replacements. Every
auxiliary family is listed for blocks 1 to 4 except the machine
manufacturer reference, which stopped at the plain
%{machine_manufacturer_reference}. A text using one of the four
auxiliary forms printed the variable itself on the folio and in exports.

Add the four, next to their manufacturer-reference neighbours.

Test: tst_resaveunchanged resaves a project with one terminal whose
texts combine each machine reference variable with its neighbour, and
checks the saved texts carry the values. It fails without the fix (the
variable is saved as typed) and passes with it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 19:40:31 +13:00
Laurent Trinques 5a9c280ff4 Merge pull request #1234 from ispyisail/fix/bugtracker-345-unlinked-contact-label
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 5m2s
Fix bugtracker #345: crash opening a project with an unlinked contact
2026-10-02 08:19:49 +02:00
ispyisail 86da56b866 Fix bugtracker #112: no snap in the symbol editor below 100 % zoom
Below 100 % ElementView::drawBackground() hid the grid and also set the
snap step to 1 unit, so anything dragged while zoomed out landed off the
grid with nothing on screen to show it. A large symbol opens fitted to
the window below 100 %, which is why the report also says "when the
selection is big".

Keep hiding the grid below 100 % (its dots would merge into a grey
wash), but snap every 10 units, as at 100 %. The zoom-to-grid rule moves
into a header-only ElementViewGrid so it can be tested without an
editor window, like textgrid.h.

Checked in the editor: a one-rectangle symbol zoomed out to about 0.6x,
its edge dragged by (15, 10) px and saved: master moves it by (28, 18),
this branch by (30, 20). tst_elementviewgrid fails with the old step 1
and passes now; ctest 45/45.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 19:13:01 +13:00
Laurent Trinques 8d2243586e Merge pull request #1205 from ispyisail/feature/multi-wire-properties-panel
Edit several wires at once in the Selection properties panel
2026-10-02 07:56:25 +02:00
Laurent Trinques bbb6f502e7 Merge pull request #1216 from ispyisail/fix/935-folio-to-sheet
Rename "folio" to "sheet" in the English interface
2026-10-02 07:55:18 +02:00
Laurent Trinques ee4051daee Merge pull request #1212 from ispyisail/feature/sketch-fillet
Add a fillet tool to round the corner between two drawn lines
2026-10-02 07:54:35 +02:00
Laurent Trinques e35a5e3d66 Merge pull request #1215 from neitri/master
Russian translate
2026-10-02 07:52:33 +02:00
ispyisail c0b8472665 Fix bugtracker #345: crash opening a project with an unlinked contact
A contact (slave) not linked to a coil has no element to take its label
from: DynamicElementTextItem::elementUseForInfo() returns null. Since
8985babfe (#978) the composite-text branch of updateLabel() called
element->actualLabel() without checking, so a text built from %{label}
on such a contact crashed QElectroTech while the project was loading
(QETProject::refresh -> Diagram::refreshContents). The project could not
be opened again.

Check the element as the ElementInfo branch above already does; with no
element, %{label} shows empty, as it did before 8985babfe.

Test: tst_resaveunchanged opens a blank project holding one unlinked
contact with a %{label} text. It fails without the fix (the --resave
crashes) and passes with it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 18:48:48 +13:00
ispyisail 6c8fb720c1 Merge pull request #1232 from ispyisail/fix/symbol-preview-size-limit
Fix a huge symbol size crashing QElectroTech while drawing its icon
2026-10-02 17:00:21 +13:00
ispyisail c3774e4037 Merge pull request #1231 from ispyisail/fix/projects-panel-close-use-after-free
Fix a crash when closing QElectroTech without saving an edited project
2026-10-02 16:59:45 +13:00
ispyisail 1002b269ac Fix a symbol with a huge size aborting QElectroTech in its preview
ElementPictureFactory::pixmap() made a pixmap of whatever width and
height the symbol file declares. A symbol whose parts span 280 000 px
(made by the GUI fuzzer in the symbol editor) asked for a ~315 GB
pixmap. Under AddressSanitizer that aborts QElectroTech; the symbol then
sat in the user collection and every later start died loading its icon.

The preview is now drawn scaled down to fit 4096 px on its longer side.
The largest symbols in the shipped collection are 3160 px, so none of
them changes. The size is also bounded before it is rounded up to a
multiple of 10, so a crafted value near INT_MAX cannot overflow.

Checked with a user collection holding one symbol declared 280000 x
280000, on ASan builds: master aborts (out of memory, exit 1) 3/3 when
the collection is expanded; with this change QElectroTech keeps running
and lists the symbol, 3/3.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 16:17:58 +13:00
ispyisail 924b87447f Fix a use-after-free when closing QElectroTech without saving
Closing QElectroTech with an edited project and choosing "Close without
saving" read freed memory. Destroying the project clears each folio's
undo stack, which signals the Projects panel to refresh that project.
The refresh reached GenericPanel::updateItem(), whose only statement was
QApplication::processEvents(). That ran the editor window's pending
deleteLater(), destroying the panel, and GenericPanel::addProject() then
carried on using it (genericpanel.cpp:142).

The call was added in 2013 (70b7cd7d1) to keep the window responsive
while the panel reloaded, when it also listed the whole element
collection. The collection has its own panel now; this one lists
projects, folios and title block templates. updateItem() is again what
its comment says it is: a hook that does nothing.

Found by the GUI fuzzer under AddressSanitizer. Reproduced 3/3 on master
51b122993 (open a project, move everything on a folio, Ctrl+Q, Close
without Saving: heap-use-after-free, exit 1); 0/3 with this change
(exit 0, no report), with the save prompt confirmed on screen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 16:15:51 +13:00
ispyisail 51b1229935 Merge pull request #1228 from ispyisail/feature/mcp-macro-recorder
Add MCP tools to read macro recordings and check scripts against them
2026-10-02 15:14:16 +13:00
ispyisail cddc4e53f0 Merge pull request #1227 from ispyisail/feature/macro-recorder
Add a macro recorder: record a task by hand, for an assistant to script
2026-10-02 15:13:54 +13:00
ispyisail f6867a0f4e Merge pull request #1225 from ispyisail/feature/mcp-live-tools
Add MCP live tools to act on the project open in QElectroTech
2026-10-02 15:13:29 +13:00
ispyisail 0f909e1246 Merge pull request #1224 from ispyisail/feature/mcp-script-tools
Add MCP tools to write, test and store script buttons
2026-10-02 15:13:08 +13:00
ispyisail cc07bb0e96 Merge pull request #1223 from ispyisail/feature/script-live-ask-first
Add ask-first, an Assistant panel and safe commands to live mode
2026-10-02 15:12:45 +13:00
ispyisail b18b6388d3 Merge pull request #1222 from ispyisail/feature/script-live
Add live mode: an AI assistant works on the open project as you watch
2026-10-02 15:12:17 +13:00
ispyisail 5fbc129671 Merge pull request #1221 from ispyisail/feature/script-manager
Add a script manager: write, try, give an icon to and delete scripts
2026-10-02 15:11:53 +13:00
ispyisail bec51d6fd1 Merge pull request #1220 from ispyisail/feature/script-buttons
Add script buttons: stored scripts with an icon in menu and toolbar
2026-10-02 15:11:26 +13:00
ispyisail 43f41eb044 Merge pull request #1219 from ispyisail/feature/script-current-folio-undo
Make a script run one undo step, and let it find the folio on screen
2026-10-02 15:11:03 +13:00
ispyisail 3e5e41b63f Merge pull request #1218 from ispyisail/fix/1210-report-menu-both-arrows
Fix #1210: keep both coming and going arrows in the right-click menu
2026-10-02 15:10:39 +13:00
ispyisail ed54271c23 Merge pull request #1214 from ispyisail/feature/mcp-conductor-defaults
Add a qet_edit op for a folio's wire defaults (one number per potential)
2026-10-02 15:09:56 +13:00
ispyisail 1e7dce4bd5 Merge pull request #1211 from ispyisail/fix/arc-middle-handle-overlap
Fix the arc's middle handle covering a resize or skew handle
2026-10-02 15:09:27 +13:00
ispyisail c22fd5fbbc Macro recorder: name the folder with the platform's separators in the copied request
On Windows the request read C:/users/...; the box above it already showed
C:\users\... Found testing the Windows package under Wine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:51:30 +13:00
ispyisail e23fc2aaad Add a macro recorder: record a task by hand, for an assistant to script
Projet > Scripts > Enregistrer une macro (also in command search and the
shortcut bar) records what is done on the current project until clicked
again. Under <data folder>/recordings/<id>/ it saves the whole project
before and after, the folio and selection at the start, and each step from
the undo history -- its name, the commands inside it, the folio, what was
selected, and the folio after it. No editing command is taught to the
recorder, and nothing new walks the scene: the files are QElectroTech's
own serialisation.

While recording, the status bar shows "● Enregistrement : N étapes" with
Arrêter. At the end a box says where it went and offers "Copier la demande
pour l'assistant": a ready-made request naming the recording, to paste
into the assistant's chat, since QElectroTech cannot send it anything
itself. qet-assistant.json lists the recordings, and live status says
whether one is under way and which was last.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:10:50 +13:00
ispyisail f4660f19ec qet-mcp: macro recordings -- read what a person did, check a script against it
qet_recording_list and qet_recording_read fetch what QElectroTech's macro
recorder saved: each step from the undo history as structured changes
(consecutive folio states diffed with qet_diff), and the overall change
from before.qet to after.qet. qet_recording_check runs a script on a copy
of before.qet from where the person started -- the folio on screen (a
proxy answers qet.currentFolio(), which a Qt object will not let a script
replace) and the selection -- and says whether the result matches
after.qet, or what differs. qet_recording_remove deletes one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:06:51 +13:00
ispyisail 7694f4833d Merge branch 'feature/script-live' into feature/script-live-ask-first 2026-10-02 13:03:34 +13:00
ispyisail a99c58487d qet-mcp: find the live channel in qet-assistant.json
QElectroTech now advertises its live channel in the "live" part of
qet-assistant.json instead of a live-session.json of its own. When nothing
is listening, the error now tells apart QElectroTech not running, live
mode switched off, and the start-up warning not accepted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:03:33 +13:00
ispyisail 124173514d Live mode: advertise the channel in qet-assistant.json
The socket name and token now go in the "live" part of the one file the
qet MCP server reads about this QElectroTech, rather than in a
live-session.json of their own, and are cleared when the channel closes.
The file also says whether the live setting is on.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:03:33 +13:00
ispyisail 15c114edd5 qet-mcp: read qet-assistant.json; qet_about; a note at first contact
QElectroTech now writes qet-assistant.json with its folders, features,
script calls and stored scripts. The server reads it to find the scripts
folder -- right even when QElectroTech runs with --data-dir, where the
per-platform guess was wrong -- and qet_about shows it, never the live
token. The initialize reply now carries instructions: what QElectroTech
is, headless and live, the usual order of tools, start with qet_about.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:02:26 +13:00
ispyisail 3a5b72c40f Merge branch 'feature/script-manager' into feature/script-live
# Conflicts:
#	cmake/qet_compilation_vars.cmake
#	sources/qetdiagrameditor.cpp
#	sources/ui/configpage/generalconfigurationpage.cpp
2026-10-02 12:58:54 +13:00
ispyisail 3cc857c754 Merge branch 'feature/script-buttons' into feature/script-manager
# Conflicts:
#	cmake/qet_compilation_vars.cmake
#	sources/qetdiagrameditor.cpp
2026-10-02 12:52:43 +13:00
ispyisail 5473ed7cdb Script buttons: qet-assistant.json, what an assistant needs to know in one file
QElectroTech now writes qet-assistant.json in the standard data folder
when an editor opens, and again whenever the stored scripts or the
scripting setting change: its version and program, every folder (data,
settings, scripts, the element and title block collections), which
features are on, every call a script can make (QetScriptApi::signatures(),
apiSignatures() without a running script), and the stored scripts with
their ids, files, icons and shortcuts, plus those refused and why. On
quit it says nothing is running any more.

The qet MCP server reads it rather than guessing each folder per
platform, which is wrong as soon as QElectroTech runs with --data-dir:
the file stays where the server can find it and names the folders
actually in use. Readable by the user only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 12:49:01 +13:00
ispyisail da312fa467 Merge branch 'feature/script-live' into feature/script-live-ask-first 2026-10-02 11:57:53 +13:00
ispyisail 7d91e8fdf1 Live mode: find the open project while QElectroTech is not the active window
While the user types to the assistant, QElectroTech is not the active
application, and QMdiArea then reports no active sub-window: status said
no project was open and every run was refused. Make the sub-window it
remembers active again before handling a request; the focus stays where
the user put it. Found with the Windows package under Wine; reproduced on
Linux with another window focused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:57:53 +13:00
ispyisail 78ee9ccbaa Merge branch 'feature/script-live' into feature/script-live-ask-first 2026-10-02 11:15:15 +13:00
ispyisail e7b699b59a Live mode: close each connection once it is answered
The qet MCP server connects once per request. On Windows a named pipe's
disconnection reaches QLocalSocket late, so the second of two quick calls
was turned away as "another assistant is already connected". Found
testing the Windows package under Wine with a Windows-side client.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 11:15:15 +13:00
ispyisail 3083654ad3 Live mode: ask first, an Assistant log, commands, folios, screenshot
A script the assistant writes on the spot is shown to the user before it
runs: Exécuter, Refuser, or Toujours pour cette session. Stored scripts
run as a click would. The choice is per session and never saved.

An "Assistant" dock lists every action with the time, ✓ or ✗, and its
undo step; the script, its log or its error show on hover and on a
double-click. It opens with live mode and holds the ask-first box.

The assistant may also:
- trigger an editor command from an allow-list of ones that open no
  dialog and that undo can take back (selection, zoom, rotate, snap,
  group, reset wires); saving, deleting, exporting and the rest are
  refused;
- show another folio;
- undo the newest step only if it made it ("Assistant : …");
- take a picture of the folio on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:43:19 +13:00
ispyisail 2fb14c5bd0 Live mode: an AI assistant acts on the open project while the user watches
A new setting, Configurer QElectroTech > Général > "Autoriser un assistant
IA à agir sur le projet ouvert", off by default. Off, nothing changes:
no channel is opened. On, every start shows a warning first -- Continuer,
Pas pour cette session, or Désactiver -- and nothing can connect until
the user answers Continuer. It waits for any other start-up question to
be answered, so the two never stack.

Accepted, LiveServer opens a local socket only the user's account can
use, with a random name and token written to live-session.json in the
data folder for the qet MCP server, and removed when the channel closes.
One JSON request per line: status (project, folio on screen, selection,
last undo step, stored scripts), run_script and run_stored. Requests are
queued out of the socket handler before they run (the lesson of PR #861).

Each run is one undo step named "Assistant : <name>". The status bar
shows the mode and the assistant's last action with a ✓ or ✗ and the
time, and an Arrêter button that closes the channel for the session.
Unticking the setting closes it at once.

QetScripting::runSource() runs script text and, for a live run, returns
what qet.log() wrote, the error with its line and the undo step instead
of showing boxes; qet.showMessage() is logged rather than opening a box
nobody asked for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:41:29 +13:00
ispyisail ae06377f1a qet-mcp: live commands, folios, undo of the assistant's own step, screenshot
qet_live_screenshot returns an MCP image the assistant can look at;
--call prints an image part as a data: URI instead of failing on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:40:29 +13:00
ispyisail 3d4f546dbc Keep both folio report arrows in the folio's context menu
Fixes #1210. The "Renvoi de folio" submenu offered the common coming and
going arrows only while the project had no folio report element yet.
Placing one copies it into the embedded collection, so from then on the
menu listed just that one and the other arrow could not be inserted until
the project was closed without saving.

The two common arrows are now always listed, after the project's own
report elements, each name once.

Checked in the GUI on a blank one-folio project: on master the submenu
drops from two entries to one after a coming arrow is placed and the
second pick places another coming arrow; with this change it keeps two
entries and the second pick places a going arrow (both in the saved file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:34:03 +13:00
ispyisail bf74468c40 qet-mcp: live mode tools, to act on the project open in QElectroTech
qet_live_status, qet_live_run_script and qet_live_run_stored talk to a
running QElectroTech through the channel its LiveServer opens, only when
live mode is switched on in its settings and accepted at this start. They
find it through live-session.json in QElectroTech's data folder and send
its token on every request; with no session, the error says which of the
three switches is missing. Running needs QET_ENABLE_SCRIPTING=1 as editing
does; asking for the status does not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:27:25 +13:00
ispyisail cd51f39d7b qet-mcp: the scripts folder rule as a pure function, tested for Windows and macOS
Checked against a real Windows package under Wine: QElectroTech keeps its
data in %APPDATA%\QElectroTech\QElectroTech, next to which the scripts
folder goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:05:24 +13:00
ispyisail 4f47093cb2 Script buttons: a manager to write, try and delete stored scripts
Projet > Scripts > Gérer les scripts… lists the stored scripts with their
icons, and the files that get no button with the reason. For each one it
edits the name, icon (a file copied next to the script, a theme icon, or
the initials), tooltip, shortcut, when it is enabled, and the script
itself; "Tester" saves it and runs it on the current project, one Ctrl+Z
to undo; "Supprimer" deletes it with its icon if no other script uses it.

It only reads and writes the files in the scripts folder, so a script
written here, by hand or by an assistant over the qet MCP server is the
same thing, and the folder's watcher turns each into a button.

ScriptHeader gains compose(), bodyOf() and idFor(), header-only and
tested: what the manager writes reads back as what was typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:22:10 +13:00
ispyisail 55eac848df qet-mcp: tools to write, test and store scripts that QElectroTech shows as buttons
qet_script_api lists every qet.* call, asked of the QElectroTech that will
run it (qet.apiSignatures() where the build has it, names otherwise), with
the header format that makes a script a button.

qet_script_test runs a script's text on a copy of a project and returns the
qet_diff of what it changed, its log and its errors with their line.

qet_script_install stores a script, and optionally an SVG icon, in the
scripts folder QElectroTech reads; with test_project it tests first and
stores nothing if the test fails. qet_script_list, qet_script_read and
qet_script_remove complete the set.

The folder is the server's choice, never the call's: ids are checked as
file names, and storing or removing needs QET_ENABLE_SCRIPTING=1 like an
edit, because a stored script runs when the user clicks it. A header is
checked with the same rules as QElectroTech's own, so nothing is stored
that would get no button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:13:05 +13:00
ispyisail b7ce89944e Scripting: qet.apiSignatures(), the calls a script can make
Read from the meta-object, so the list is the one the running build has:
for someone writing a script, and for an assistant that has to write one
without the source at hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:04:06 +13:00
ispyisail e03e523069 Script buttons: stored scripts become commands with an icon
Every .js file in the "scripts" folder of the user's data folder that
starts with a // ==QETScript== header becomes a command: in Projet >
Scripts, as a button on a new Scripts toolbar, and, because it is
registered with ShortcutManager as diagrameditor.script.<file name>, in
the shortcut settings, the shortcut bar (S) and command search. The
header gives its name, icon (a file next to the script or a theme icon;
a tile with its initials otherwise), tooltip, default shortcut and when
it is enabled (always, with a selection, with a conductor selected).

The folder is watched, so a script added, edited or deleted while QET is
open appears, changes or goes without a restart. A file with a header
that cannot be used gets no button; the Scripts menu lists it with the
reason. The menu also opens the folder, and holds "Exécuter un script…".

A click runs the script on the current project as one undo step named
after it, and asks to switch scripting on first, like "Exécuter un
script…" does: scripting stays off by default.

ShortcutManager::unregisterAction() takes a command out of the lists
when its script is deleted, and lets it come back under a new name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:04:06 +13:00
ispyisail 1c1f9db3eb Scripting: qet.currentFolio(), and one undo step per script run in the editor
A script started from the editor had no way to know which folio is on
screen, and each qet.* call was its own undo step, so a script that adds
twenty items needed twenty Ctrl+Z to take back.

qet.currentFolio() returns the folio shown in the editor; through --run,
which has no view, the first folio, or -1 when there is none.

A run from the editor is now one undo macro, named after the script. A
run that changed nothing leaves no empty entry behind. qet.undo() and
qet.redo() inside a grouped run say why they cannot work (QUndoStack
ignores them inside a macro) instead of failing silently. --run is
unchanged: one step per call, so scripts that call qet.undo() keep
working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:47:27 +13:00
ispyisail 5b4bba96ae English: call a folio a sheet
Issue #935. Changes the English translation only: "folio" becomes "sheet"
in the 171 interface strings that use it, and the "Folio" label of the seven
shipped title blocks becomes "Sheet". Variable names (%folio, %{folio-id},
%{previous-folio-num}, ...) are unchanged, so existing title blocks and
labels keep working. French and the other languages are untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 07:50:15 +13:00
Mikhail 0817c7dd87 Rassian translate 2026-10-01 23:29:23 +05:00
ispyisail 7df9052a47 qet-mcp: set a folio's conductor defaults (set_conductor_default)
The "one text per potential" switch (onetextperfolio) lives in a folio's
conductor defaults, which no scripting call or qet_edit op could reach, so
callers patched the saved .qet afterwards (#1178).

qet.setConductorDefault(folio, property, value) sets onetextperfolio or any
setConductorProperty name on a folio's defaults, or with folio -1 on the
project's defaults that new folios copy. A change to onetextperfolio
re-shows or hides the conductor texts at once, as the Folio properties
dialog does. Not on the undo stack, like both dialogs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 07:16:44 +13:00
ispyisail 4b501521a5 Add a sketch fillet: round the corner between two drawn lines
"Ajouter un congé" on the drawing toolbar, in Édition > Ajouter and in
the command search. Click two lines one after the other, or once where
they meet, and a small dialog asks for the radius (remembered for the
next one); Enter makes the fillet. The tool stays on for the next
corner; Escape or a right-click ends it.

The two lines are trimmed -- or extended, when they did not reach each
other -- to the points where the arc touches them, and an arc is added
between them. For each line the part kept is the one on the side of
the corner where it was clicked; a click on the corner keeps the
longer part of each. Parallel or nearly aligned lines, and a radius
too big for the lines, are refused with a status-bar message.

The arc is an ordinary Ellipse shape with a start and end angle, as
the Arc tool and the ellipse's arc handles save it, so nothing new is
written to the file. It is added by its own small undo command rather
than AddGraphicsObjectCommand, whose redo() calls setPos(), and
QetShapeItem::setPos() snaps the outline's corner to the grid, which
would pull the arc off the lines. One undo step undoes the fillet.

New icon draw-fillet, light and dark, registered like the Align icons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 07:08:18 +13:00
ispyisail 0bc27f0023 Translations for the multi-wire panel strings (English, French)
The three new strings only. A full update_translations run rewrites 33
files (+87,518/-52,666 lines, 184 new strings from other merged work),
so that is left to the next translations refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 06:20:17 +13:00
ispyisail 1c5fd5a284 Multi-wire panel: stable first wire, mixed fields, locked text, one reload
Review of #1205:

- The scene gives no order for its selection, so the "first" wire whose
  values the panel shows could change between two selections of the
  same wires. The wires are now sorted top to bottom, then left to right.
- A text field the selected wires do not agree on (function, section,
  cable...) is shown blank with the hint "Plusieurs valeurs". Typing any
  value there, including the first wire's own, now differs from what was
  shown, so it reaches every wire: 1.5 mm² on a 1.5 and a 2.5 wire works.
- The conductor text is locked while several wires are selected: the same
  number on all of them would make duplicates. "Apply to all conductors
  of this potential" with one wire selected still sets the number on the
  whole potential.
- One edit of N wires emitted N signals, each reloading the panel. They
  are now gathered into one reload, which matters since a reload reads
  every selected wire to find the mixed fields. Selections and targets
  are de-duplicated with sets, and a wire reached through an earlier
  wire's potential is not walked again. An edit that changes nothing
  returns before any potential is walked.

The rules move to sources/conductormultiedit.h, templates over the wire
type, so tst_conductormultiedit can check them against a fake wire:
order, targets with and without potentials, one undo step and its redo
for three wires, and the mixed fields. tst_conductorapplychanges now
gives the other wire the opposite value in every boolean field, so
copying one by mistake fails the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 06:20:17 +13:00
ispyisail 4c0f51c476 Arc: show the middle handle only with the size handles, uncovering the one under it
The middle handle of a half arc sits exactly on the middle of the top
or bottom edge of the ellipse -- a resize handle in Size mode, a skew
handle in RotateSkew mode -- and, drawn last, it covered that handle
(arummler, discussion #1203). It is now shown in Size mode only, where
the resize handle under it is hidden instead: the middle handle already
changes that height, with the ends kept in place. RotateSkew mode shows
its skew handle again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 06:12:09 +13:00
ispyisail 0ed9e7eb59 Merge remote-tracking branch 'upstream/master' into feature/multi-wire-properties-panel 2026-10-02 05:46:57 +13:00
Laurent Trinques b6495b51a6 Merge pull request #1208 from Kellermorph/strg-shift-v
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 3m18s
Ctrl+Shift+V pastes at the origin point, Ctrl+V keeps pasting under the cursor
2026-10-01 18:24:03 +02:00
Laurent Trinques 1ca13ce62d Fix unreadable search field in the Search and Replace widget (dark theme)
The search field gets a light green or light red background to show
whether the text matches, but only QPalette::Base was set. With a dark
theme the text colour is light too, so the typed text was light on
light and could not be read.

Set QPalette::Text to black together with the light background.

Fixes #1209
2026-10-01 16:03:46 +02:00
Laurent Trinques c7d31c08e4 Merge pull request #1204 from ispyisail/fix/wire-panel-unedited-properties
Fix the wire properties panel switching wires to single-line
2026-10-01 15:46:53 +02:00
Kellermorph 87bd172c44 Ctrl+Shift+V pastes at the origin point, Ctrl+V keeps pasting under the cursor
Ctrl+V moves the pasted group under the pointer as before. The new
Ctrl+Shift+V ("Coller au point d'origine", diagrameditor.paste_origin)
leaves the group exactly where it was copied from and warps the OS
cursor to the group's grid-snapped origin, so the paste baseline and
the physical cursor agree and the group starts following the mouse from
its original place instead of jumping to the pointer.

The cursor warp is only trusted as the baseline when it verifiably
lands (QCursor::pos() agrees with the target): QCursor::setPos() needs
pointer focus and is a silent no-op on compositors without
wp_pointer_warp_v1. Otherwise m_baseline_captured stays false and
moveTo() re-baselines on the first real mouse move, so a refused warp
costs at most the first movement rather than flinging the group across
the folio. An origin outside the viewport is scrolled into view first,
since the compositor rejects warp targets outside the window.
2026-10-01 13:04:04 +02:00
Laurent Trinques 041ad33e02 Merge pull request #1207 from jp2images/svg-icons-trace
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 3m6s
Convert the toolbar and menu icons to SVG (#1063)
2026-10-01 12:54:11 +02:00
Jeff Patterson 947b067185 Tested that every icon size uses art drawn for it and reads on light and dark 2026-10-01 05:39:52 -05:00
Jeff Patterson 194708c0da Loaded the traced icons by theme name and removed the PNGs they replace 2026-10-01 05:39:52 -05:00
Jeff Patterson 5057817b6e Generated the light and dark icon themes 2026-10-01 05:39:51 -05:00
Jeff Patterson dba532996c Traced QET's own icons as SVGs, with 16 and 22 px drawings, and credited them in ico/copyright 2026-10-01 05:39:51 -05:00
Jeff Patterson cd786af926 Added scripts that import KDE Breeze icons and build the icon themes with a drawing for each size 2026-10-01 05:39:51 -05:00
Laurent Trinques 8e5da3c10b Merge pull request #1202 from ispyisail/feature/arc-tool
Add an Arc tool, with a handle to pull a half arc in or out
2026-10-01 12:37:37 +02:00
ispyisail a556204ba1 Edit several wires at once in the Selection properties panel
Selecting more than one wire left the Selection properties panel empty,
and so did a box selection over a single wire, because it also picks up
the wire's number label.

Now the panel opens for any number of selected wires. A selected wire
label counts as its wire, once, so a box drawn over wires works. The
fields show the first wire's values, a line above them says how many
wires are selected, and an edit is applied to each wire field by field:
setting the function on ten wires leaves each with its own number,
cable and section. With "apply to all conductors of this potential"
ticked, the potentials of all the selected wires follow the same rule.
Each edit is one undo step.

Fields whose values differ between the selected wires are not marked as
such; that can follow if wanted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:15:32 +13:00
ispyisail a8699f6f62 Fix the wire properties panel changing properties nobody edited
With "Show the properties of a selected conductor in the Selection
properties panel" switched on, editing a wire there could change things
the user never touched:

- Enter in a field (Function, Section...) is not used by the line edit,
  so it reaches the checkable "Multifilaire" group box around it, which
  takes it as a click. The wire was switched to single-line, gaining
  ground, neutral and phase symbols. The modal dialog never shows this
  because its OK button takes Enter first.
- Every edit wrote back the whole set of properties as the widget holds
  them, so a value the widget cannot show exactly was rewritten: a dash
  size of 1 became 2.

The panel now swallows Enter at its checkable group boxes, and writes
only the fields that differ from what it showed, through the new
ConductorProperties::applyChanges(). "Apply to all conductors of this
potential" uses the same rule, so the rest of the potential keeps its
own values too.

tst_conductorapplychanges checks each field on its own: change that one
field, and a wire whose every field differs takes it and keeps the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:10:15 +13:00
ispyisail ffa76a61c1 Add an Arc tool and a handle to pull a half arc in or out (#408)
The diagram toolbar gets an "Ajouter un arc" button next to the
ellipse. Click one end, then click the other end at the height the arc
should reach: the result is a half arc bulging up or down from the line
between the two clicks (Shift: a true half circle). It is not filled,
since a fill would close it into a half disc.

A selected half arc shows one more handle, in the middle of the curve.
Dragging it makes the arc deeper or flatter while both ends stay put;
dragging it across the line between the ends turns the arc over. The
handle is hidden whenever the arc is not a half arc on a horizontal or
vertical diameter, because only then is "keep the ends, move the
middle" one well-defined change.

Nothing new is stored: an arc stays an Ellipse with a start and end
angle, exactly as the existing arc handles of an ellipse save it, so
files read by older versions are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 22:49:35 +13:00
Laurent Trinques 7fdc314e41 Merge pull request #1200 from ispyisail/feature/wire-crossing-hops
Add optional hops where wires cross (#436)
2026-10-01 11:07:09 +02:00
ispyisail 79a8637251 Add optional hops where wires cross (#436)
Where two wires cross without being connected, a project can now draw a
small arc (a hop) on one of them, instead of the user inserting a jump
symbol and splitting the wire. It is a project setting, off by default:
Project properties > Général > "Croisements de conducteurs", with no
hops, hops on horizontal wires, or hops on vertical wires. Choosing the
orientation rather than following drawing order keeps a project
consistent.

Only the drawing changes: no element is added and no wire is split. A
wire that ends or bends on another one is a junction and never hops,
and a crossing too close to the end of a segment for the arc to fit is
drawn plainly. PDF, SVG and image export and printing show the hops,
since they paint through Conductor::paint(); DXF export, which writes
the segments itself, does not.

The setting is saved as <wire_crossings hop="..."/> under the project
root, next to <usage>, and only when hops are on, so a project that
never used them saves exactly as before.

The geometry is in wirehops.cpp, free of any graphics item, and tested
on its own. Conductor::paintedPath() feeds it the conductors of the
folio from a snapshot of their scene points, rebuilt only when a
conductor changes shape, moves, or enters or leaves a folio; each hop
path is cached the same way. Asking the scene for the conductors in a
rect instead strokes every candidate's shape, which made a 366-wire
folio render 0.6 s slower; with the snapshot the difference is within
measurement noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:18:12 +13:00
Laurent Trinques d777b8d44d Merge pull request #1199 from ispyisail/feature/terminal-strip-drag-drop
Add drag and drop of free terminals onto a terminal strip (#409)
2026-10-01 08:14:25 +02:00
Laurent Trinques 3f9ab22224 Merge pull request #1180 from Kellermorph/plc-place
Element editor: save the moved position of the PLC table
2026-10-01 08:13:11 +02:00
Laurent Trinques 75b95cd636 Merge pull request #1198 from ispyisail/fix/terminal-move-tooltip
Fix the terminal strip's move button giving the wrong reason (#409)
2026-10-01 08:01:00 +02:00
ispyisail 66ad129c20 Add drag and drop of free terminals onto a terminal strip (#409)
In the terminal strip manager, free terminals could only be moved into a
strip with the "move to" list and its button. Dragging one onto a strip,
which is what users try first (#409), did nothing.

Free terminals can now be dragged onto a strip in the tree on the left,
from either place they are listed:
- the free terminal table: every selected row is moved;
- the tree itself: the dragged free terminal is moved.

Dropping on a strip, or on a terminal already in that strip, moves them
with the same undo command as the button, so one undo puts them back.
The strip is then selected, showing the terminals just added. A drop
anywhere else is refused.

A drop is also refused while the free terminal table has edits that are
not applied yet: moving reloads that table and would lose them. The
button is disabled in the same case.

The drag carries the uuids of the terminal elements under its own mime
type, and the tree only moves terminals that are still free in the
project when the drop happens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 18:33:50 +13:00
Laurent Trinques 509d551964 Merge pull request #1197 from ispyisail/fix/wiring-list-cable-column
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m49s
Add a Cable column to the wiring list export
2026-10-01 07:14:00 +02:00
Laurent Trinques 14c217da34 Merge pull request #1194 from ispyisail/fix/element-nonfinite-coordinates
Fix bugtracker #266: symbol with a nan coordinate breaks the editor
2026-10-01 07:12:27 +02:00
Laurent Trinques 2ea570c8f2 Merge pull request #1192 from ispyisail/fix/editor-resize-zero-undo
Fix bugtracker #266: undo of a resize to zero width does nothing
2026-10-01 07:11:49 +02:00
ispyisail 97239657c7 Fix the terminal strip's move button saying the wrong thing (#409)
The button that moves free terminals into a strip is disabled when there
is nothing it can do, but its tooltip always gave the same reason: apply
or cancel the pending edits. With no row selected, which is what the
screenshot on #409 shows, that sends the user looking for edits they
never made.

It also stayed enabled when the project had no terminal strip at all.
The "move to" list is then empty and a click did nothing.

The button is now disabled in all three cases and the tooltip names the
one that applies, the most basic first: no strip yet (create one with
the + button), edits pending, or nothing selected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 18:11:45 +13:00
Laurent Trinques 99422fe56c Merge pull request #1191 from ispyisail/fix/344-one-text-per-potential
Fix bugtracker #344: "one text per potential" now applies at once
2026-10-01 07:09:23 +02:00
ispyisail c43f550639 Add a Cable column to the wiring list export
Each wire already carries a cable name (Conductor properties -> Cable),
saved in the project, but no export showed it. The wiring list CSV
(Project menu, and --export-cables) now writes it as a last column, so
the list can be sorted or filtered by cable in a spreadsheet.

It goes last so existing columns keep their positions for anyone reading
the file by column number. When the two halves of a wire drawn through a
folio report are joined into one row, their cable names are merged the
same way as colour and section: written once if equal, both if not.

Asked for in issue #405.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:50:56 +13:00
ispyisail 75a7e642e0 Fix bugtracker #266: skip shapes with a nan coordinate in the element editor
A symbol with a "nan" or "inf" coordinate (bugtracker #266 had a line
with x1="nan" x2="nan") opened with that shape invisible and the view
off-centre. Moving the selection then snapped NaN through qRound() to
INT_MIN, and saving wrote width="-2147483630", breaking the symbol.

The folio already leaves such a shape out (ElementPictureFactory reads it
with QET::attributeIsAReal()). Do the same in the element editor, and
have --check-elements warn about it. Only geometry attributes are read,
so a text whose content is "inf" is not affected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:21:51 +13:00
ispyisail 5381d05d5d Fix bugtracker #266: undoing a resize to zero width did nothing
Resizing a selection in the element editor down to zero width or height
could not be undone: ScalePartsCommand scales back from the new rect, and
a flat rect cannot be scaled from, so undo silently left the shapes
collapsed and the flattening was saved. The same flat rect made
deltaForRoundScaling() divide 0 by 0 and fill the resize rect with NaN.

Never commit a resize rect with no width or height, and do not resize a
selection that is already flat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:53:02 +13:00
ispyisail 0cf7975be3 Fix bugtracker #344: folio "one text per potential" applies at once
Ticking or unticking "Afficher un texte de potentiel par folio" in
Folio > Properties only assigned Diagram::defaultConductorProperties.
Nothing recalculated the conductor texts, so texts hidden by the option
stayed hidden after it was turned off (and duplicates stayed visible after
it was turned on) until each wire was edited or the project was reopened.
That made the option look broken, which is part of what #344 reports.

When the option changes, reset each conductor's text visibility from its
own properties, then run calculateTextItemPosition() on every conductor of
the folio so the option is applied again from scratch.

Checked in the GUI on examples/industrial.qet folio 4 (26 wires with text,
19 potentials), master vs this commit: unticking now shows the hidden
texts immediately, ticking hides the duplicates immediately; master's
drawing does not change in either direction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:03:18 +13:00
ispyisail 7f799bb425 Merge pull request #1190 from ispyisail/feature/center-on-cursor
Add a Centre on cursor command to move around a zoomed-in folio
2026-10-01 10:57:21 +13:00
ispyisail 18fe429254 Merge pull request #1186 from neitri/master
Russian Translation
2026-10-01 10:48:20 +13:00
ispyisail ec49c7d1ad Russian translation: review fixes
- fill all three Russian plural forms for the PDF page count and the two
  font-recovery messages (lrelease dropped them, so French showed)
- restore the trailing and embedded newlines 12 translations had lost
- translate the remaining French AutoNumberingDockWidget tooltip
- wording: Повернуть %1, Сочетание клавиш / Горячие клавиши,
  Перетаскивание: наклон, горизонтали, Вспомогательное текстовое
  описание 1, со значением, ни при какой ширине

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:47:09 +13:00
ispyisail 0a1ea451ed Merge pull request #1188 from ispyisail/fix/1187-pasted-contact-label
Fix #1187: a contact pasted with its coil loses its label
2026-10-01 10:32:48 +13:00
ispyisail 8587c1785e Merge pull request #1189 from ispyisail/i18n/pt-br-forum-3205
Update the Brazilian Portuguese translation from forum topic 3205
2026-10-01 10:32:27 +13:00
ispyisail 2580163e33 Add a "Centre on cursor" view command
Scrolls the folio so the point under the mouse becomes the centre of the
view, keeping the current zoom. Requested on the forum (topic 3199) for
moving around a zoomed-in folio the way KiCad and EasyEDA allow;
"Zoom sur le contenu" (Ctrl+8) was the closest existing command, but it
also refits the zoom.

The command is in the Affichage menu and in the shortcut editor. It has
no default key: F4 and the single letters are already taken or would
clash with typing. It does nothing when the cursor is not over the view,
so choosing it from the menu is harmless. Near the folio border the view
cannot scroll past the edge, so the point ends up as close to the centre
as the scroll bars allow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:29:34 +13:00
ispyisail ce2b33cd34 Update pt_BR translation from forum topic 3205
wanderson_rds posted a corrected qet_pt_BR.qm on the forum
(https://qelectrotech.org/forum/viewtopic.php?id=3205). The .qm was
decompiled with lconvert and merged into the current qet_pt_BR.ts,
changing only the <translation> of each message, so line locations and
the lupdate layout are untouched.

- 266 strings that had no translation now have one (all plurals too).
- 29 existing translations replaced: "forme" was translated as "form"
  (formulário) instead of "shape" (forma); zero-width spaces removed
  from several "variáveis"; smaller wording and grammar fixes.
- "TextLabel" kept as master had it; vanished entries not touched.

lrelease: 2949 translations and 266 untranslated before,
3215 and 0 after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:06:54 +13:00
ispyisail a3ba364c9d Keep the label of a contact pasted together with its coil (#1187)
A slave pasted with its master is relinked to the master's copy by
initLink() (#997), so the slave data wipe that follows is only needed
for a slave that comes out of it unlinked. Linked, it wiped the
contact's label text while the link stayed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:04:52 +13:00
ispyisail ee781f8dc3 Merge pull request #1183 from ispyisail/fix/headless-settings-dir
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 3m54s
Fix the MCP server's elements_dir on Windows (QET_SETTINGS_DIR)
2026-10-01 09:12:03 +13:00
ispyisail 74664c0bcd Merge pull request #1184 from ispyisail/fix/wiring-list-folio-arrows
Fix the wiring list export dropping wires at folio reports
2026-10-01 09:11:33 +13:00
Mikhail aa893d610d Russian Translation 2026-10-01 00:57:19 +05:00
ispyisail 50b52d50c8 Merge pull request #1182 from ispyisail/fix/element-editor-space-rotates-selection
Fix #1177: Space rotated the selection, not the part being added
2026-10-01 08:43:15 +13:00
ispyisail d66ab752a8 Merge pull request #1181 from ispyisail/fix/windows-crash-report-location
Fix Windows crash reports not saying what crashed or where
2026-10-01 08:42:53 +13:00
ispyisail 367d168c6f Fix #1177: Space rotated the selection, not the part being added
In the symbol editor, Space is the shortcut of the Rotate action, which
rotates the selected parts. The action is enabled whenever something is
selected, and a window shortcut is handled before the key reaches the
scene, so while a terminal or arc was being placed with something already
selected, Space rotated the selection and never reached ESEventAddTerminal /
ESEventAddArc. With nothing selected the action was disabled and the key
fell through, which is why it only happened with a selection.

The scene now emits behaviorChanged() when it enters or leaves AddPart,
and updateAction() keeps the selection actions (rotate, fine rotate, flip,
mirror, cut, delete, depth) disabled while a part is being added. They come
back when the tool ends. The selection itself is left alone.

ESEventInterface::init() also gives the view keyboard focus: a selection
made in the parts list left the focus there, so Space still never reached
the tool.

~ElementScene blocks its signals before deleting the event interface,
whose destructor resets the behavior while the editor is being destroyed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:17:38 +13:00
ispyisail 65a1080cd3 Keep headless runs' settings in a folder of their own (QET_SETTINGS_DIR)
The MCP server gives each QElectroTech run a private HOME and writes the
element collection path for it (elements_dir) into
~/.config/QElectroTech/QElectroTech.conf there. Only Linux reads that
file: Qt keeps settings in the registry on Windows and in the system
preferences on macOS. So on those systems elements_dir did nothing, and
every run read the user's own settings (#1178).

When QET_SETTINGS_DIR names a folder, QElectroTech now keeps its
settings in <folder>/QElectroTech/QElectroTech.ini on every system. It is
set in main() before the first setting is read. Without the variable
nothing changes.

The server sets it for each run and writes the collection path to the
.ini as well as the .conf, so an older QElectroTech keeps working on
Linux. The path is written with forward slashes: Qt reads a backslash in
these files as an escape. The README notes that elements_dir needs this
on Windows and macOS, and that long arguments go on stdin with "-"
(Windows refuses a command line over 32,767 characters).

tst_settingsdir: a script places a symbol that only the folder's settings
can resolve, while the usual settings file points at an empty collection.
It fails on master and passes here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:00:55 +13:00
ispyisail dad99fb08c Say what crashed and where in Windows crash reports
A Windows crash report held the header and the log and nothing else: no
exception, no address, no stack (issue #1179, two reports that could not
be traced). It now also has:

- the exception, with its name ("access violation", "stack overflow"...)
- where it happened, as module+offset, and for an access violation the
  address that was read or written
- a backtrace, one module+offset per frame, walked with the unwind
  tables every x64 image carries (RtlLookupFunctionEntry and
  RtlVirtualUnwind): no dbghelp, no symbols, no allocation. addr2line
  on the same build turns each offset into a function and line.

The dump is written by a reporter thread started at install(), not on
the crashing thread, which may have no stack left or hold the loader
lock. The crashing thread waits for it for at most ten seconds.

Two kinds of crash wrote no report at all before and now do:
- abort() (std::terminate(), a failed assert): no SEH exception, so
  SIGABRT is handled as well.
- qFatal(): Qt ends it with TerminateProcess() on Windows, so the
  message handler now asks for the dump once the message is logged.
  Elsewhere reportFatal() does nothing; SIGABRT follows there.

Tested with a cross-built RelWithDebInfo build under Wine 11, crashing
on purpose from inside the event loop: a null pointer, a call through a
null pointer, a stack overflow, abort() and qFatal(). Each report
resolved to the line that crashed. Linux reports are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 07:40:57 +13:00
Kellermorph 23f8e261e5 Element editor: save the moved position of the PLC table
Moving the PLC table in the element editor was lost on save: the table
came back at (0, 0) when the element was reopened, and it was drawn at
(0, 0) in the diagram editor as well.

PartPlcTable::toXml() wrote m_rect.x()/y(), which are the coordinates
inside the item, and the move of a part never touches m_rect - it lives
in pos() (CustomElementGraphicPart::mouseMoveEvent, or the decorator for
a multi-selection). m_rect starts at the item origin, so x="0" y="0"
was written whatever the user did, while fromXml() reads those two
attributes as scene coordinates and hands them to setPos(). Every other
part translates first, see PartRectangle::toXml() with sceneTopLeft().

The top-left corner is now mapped to scene coordinates before being
written, which also covers a group transformation, since that one moves
m_rect while pos() stays put.

Both readers of the attribute need no change: ElementScene::fromXml()
for the editor and Element() for the runtime drawing of the table
(Element::drawPlcTable) already expect scene coordinates. Elements that
still carry x="0" y="0" keep loading as before.

Full build clean. Test suite: the same three failures as before the
change (tst_menubarkeyboard, tst_databasefromdocument,
tst_terminaluuids), each of them also failing on a build without this
patch, so they are unrelated.
2026-09-30 20:20:52 +02:00
Laurent Trinques 2a6155e0c9 Merge pull request #1168 from Kellermorph/prefix-button
Settings: configure the folder prefixes of the user collection
2026-09-30 16:07:18 +02:00
ispyisail 972127aa05 Wiring list export: keep every wire at a folio report, print the folio number
A wire drawn to a folio report waited, keyed by its report, for the wire on
the linked report, in a QMap whose insert() replaces: a second wire on the
same report pushed the first out of the list, and a pair with several wires
on a side was joined by whichever came first. On the shipped examples,
re-saved as the export reads them, 130 of 3,189 wires were missing from the
list (industrial 86, m_000 41, affuteuse_250h 3).

All the report halves are now collected first, and two are joined only
when each report of a linked pair has exactly one wire. With several on a
side the diagram does not say which terminal is wired to which, so each
wire gets its own row ending at the report, as an unmatched half already
did. The result no longer depends on the order the folios are saved in.

The Page column printed each folio's number template ("%id/%total") and
the rows were sorted by it. It now prints the number the folio shows, and
the rows are sorted by the folio's position in the project.

tst_wiringlistexport checks a fixture with one report pair of each kind,
and the page column on every example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:59:49 +13:00
Kellermorph 48aac69391 Merge upstream/master into prefix-button
Only one conflict: the include block of
sources/ui/configpage/generalconfigurationpage.cpp, where upstream adds
editor/terminalnamecheck.h next to the two includes of the prefix
editor. All three are kept. The file list in
cmake/qet_compilation_vars.cmake merged itself this time.
2026-09-30 11:10:02 +02:00
Kellermorph 1d339a8b27 Settings: only write the prefix rows that were edited
Review findings on the prefix editor:

- OK without touching anything changed the file: every row was written
  back, so an explicit <prefix/>, which reads as an empty field and
  cancels the inheritance, was dropped and that folder started
  inheriting its parent's prefix again. A row is now written only when
  it was edited or no longer holds what the file has, and the two kinds
  of empty field look different: hasPrefix() tells an explicit
  <prefix/> from a missing one, which is what the field hint shows.
- load() copied a broken file aside before the user had chosen
  anything, so every "Corriger le fichier" left a .bak behind while the
  message said nothing had been modified. The copy is now made by
  save(), right before the file is replaced: repairing or cancelling
  leaves nothing behind, and a copy that cannot be made stops the write
  rather than destroying the only copy.
- The button followed the combo box only for "Parcourir...": put back
  on "Par defaut" it fell back to the previously saved path instead of
  the default one. The directory of the entry being displayed is now
  used, "Par defaut" being dataDir()/elements/.
2026-09-30 11:01:31 +02:00
ispyisail a1a0a81c1b Merge pull request #1176 from ispyisail/fix/1155-text-anchor-position
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m28s
Fix #1155: Position X/Y of a symbol text follows its alignment
2026-09-30 19:01:55 +13:00
ispyisail bdc1d5878d Position X/Y of a symbol text is the point chosen by its alignment
The text properties showed and edited the top-left corner of a text,
whatever its alignment, and changing the alignment did not move the
text. A right-aligned label next to a symbol therefore needed a
different X for every text length to keep the same gap (#1155).

Position X/Y is now the anchor point: the point of the text chosen by
the alignment (right edge for "right", middle for "centre"...). Typing a
position puts that point there, and changing the alignment moves the
text so that the new anchor point is on the same X/Y. It is the same
point that already stays fixed when the text changes.

Applies to the element text properties on a folio and to the text
field properties in the symbol editor. The saved x/y is still the
top-left corner, so projects and symbols load and save unchanged and
look the same; only the numbers shown for texts that are not top-left
aligned change.

The geometry is in textanchor.h, shared by DiagramTextItem and
PartDynamicTextField, and tested in tst_textanchor.

Not changed: static texts of the symbol editor (PartText), whose
position is tied to the font baseline; texts inside a text group, which
have no position or alignment of their own; rotation still pivots on
the top-left corner (or the centre), not on the anchor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:12:43 +13:00
ispyisail 784a663c19 Merge pull request #1159 from ispyisail/feature/terminal-name-check
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m44s
Add a terminal name check when saving a symbol (#1157)
2026-09-30 15:30:28 +13:00
ispyisail 696783d350 Merge master into feature/terminal-name-check
Resolve the tests/qttest/CMakeLists.txt conflict with tst_configprofile
by keeping both tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:04:37 +13:00
ispyisail b2f0216d04 Merge pull request #1171 from ispyisail/fix/template-placed-under-cursor
Fix templates landing far from the cursor
2026-09-30 14:57:13 +13:00
ispyisail 4a89b6db68 Merge pull request #1172 from ispyisail/feature/color-dialog-double-click
Add double-click to choose a wire colour
2026-09-30 14:56:42 +13:00
ispyisail cb5247e07c Place a template under the cursor, and list it as soon as it is saved
A template (.qetmak) was previewed and placed offset from the cursor by
the position its items had on the folio it was saved from: the preview
pixmap kept that offset, and addMacro() added it back before handing the
position to fromXml(). A template saved from the middle or the lower
right of a folio therefore landed far below and to the right of the
click, often off the folio, which reads as "the template does not drop"
(forum topic 3190). Anchor both the preview and the placement on the
template's own top-left corner instead.

fromXml() skips translating for a null position, so a click on the folio
origin is nudged by a fraction of a pixel that the grid snap removes.

Also:
- a template saved with "Create a template" only appeared in the
  templates tab after reloading the collections or restarting; it is
  now added to the tab of every open editor when it is saved;
- the placement status bar message ended in an untranslated
  "(Makro-Anker)"; it now reads "x : y" like element placement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 13:47:45 +13:00
ispyisail 8b82e348f2 Choose a conductor colour by double-clicking it
In the F2 conductor colour dialog and the toolbar's "Other colour..."
dialog, a double-click on a basic or custom colour swatch now chooses it
and closes the dialog, as OK does (forum topic 3190). Qt's dialog only
selects the swatch on a double-click.

ColorDialogDoubleClick filters the dialog's QWellArray swatch grids,
found by class name once the dialog is shown, because Qt builds them
only then when a native dialog was offered but not shown. Where the
platform's native colour dialog is used instead (macOS, some Linux
desktops) there are no swatches to watch and nothing changes. The
toolbar dialog is now an instance rather than QColorDialog::getColor(),
so the filter can be installed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 13:47:45 +13:00
ispyisail 63196bf4ac Merge pull request #1167 from ispyisail/revive/654-rotating-backups
Keep three crash-recovery snapshots instead of overwriting one
2026-09-30 12:18:26 +13:00
ispyisail a3f10b5959 Merge remote-tracking branch 'upstream/master' into revive/654-rotating-backups
# Conflicts:
#	sources/qetapp.cpp
2026-09-30 11:44:49 +13:00
ispyisail eda9012097 Merge pull request #1169 from ispyisail/revive/499-shift-text-hint
Spell out the Shift-drag hint on symbol texts
2026-09-30 11:37:48 +13:00
ispyisail d698d0366a Merge pull request #1170 from ispyisail/revive/641-cabinet-thumbnails
Add cabinet thumbnails generated from a device's manufacturer and reference
2026-09-30 11:37:23 +13:00
ispyisail 2086270a35 Merge pull request #1166 from ispyisail/revive/640-config-profiles
Add saving and loading the configuration to a file
2026-09-30 11:35:54 +13:00
ispyisail a70e27b4c4 Merge pull request #1165 from ispyisail/revive/691-dialog-geometry
Remember dialog sizes, and drop a light-grey box in dark themes
2026-09-30 11:35:25 +13:00
ispyisail ed1ee0feea Merge pull request #1164 from ispyisail/revive/643-editor-background-frame
Add an adjustable background frame to the symbol editor
2026-09-30 11:34:49 +13:00
ispyisail 327ee11963 Merge pull request #1163 from ispyisail/revive/516-thread-safe-setup-data
Fix the elements panel being updated from worker threads
2026-09-30 11:33:42 +13:00
ispyisail 77b2393d10 Merge pull request #1162 from ispyisail/fix/terminal-strip-dock-use-after-free
Fix a crash when closing a project after its terminal strip window is gone
2026-09-30 11:33:15 +13:00
ispyisail 46598c4ed2 Merge pull request #1161 from ispyisail/fix/wire-nonfinite-values
Reject non-finite values when loading a wire
2026-09-30 11:32:53 +13:00
Kellermorph 03bffa96c7 Merge remote-tracking branch 'upstream/master' into prefix-button
Conflicts in cmake/qet_compilation_vars.cmake only, where upstream added
terminaluuids.cpp/.h next to the qetlabelsfile.cpp/.h this branch adds :
both are kept, qetlabelsfile first to stay alphabetical.
2026-09-30 00:25:14 +02:00
ispyisail aa09d0eb2a Store the thumbnails folder's French name as French, not through tr()
tr() returns the interface language, so an English or German session
would have written its own text under the "fr" name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:15:11 +13:00
ispyisail 6c4ee10f44 Generate cabinet placement thumbnails from manufacturer/reference info
Discussion #602. Right-click a selection holding an element with both a
manufacturer and a manufacturer reference: "Générer une vignette
d'armoire" adds a thumbnail element for each such device to a
"Cabinet thumbnails" folder of the project's embedded collection. The
thumbnail is a 120x30 frame whose dynamic text shows
%{manufacturer} / %{manufacturer_reference}; both values are also in
the definition's element information, so it shows them when placed.
A device already filed there is skipped, so running it again, or on
several copies of one device, adds nothing.

Revives #641, closed 2026-09-10 only to clear a review backlog. Changed
from it: the frame and text are placed around the hotspot (they were
drawn half outside the element), the frame is 120 wide instead of 60
(device names wrapped out of it), the file name goes through
QET::stringToFileName() (a '/' in a reference made a folder), the
version and font come from QetVersion and QETApp, and the action is
left out of the menu when no selected element qualifies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:08:55 +13:00
ispyisail 0f259bafb5 Spell out the Shift-drag hint on symbol texts
Hovering a symbol's text shows its value and the hint "<Shift> to move"
("<Shift> pour déplacer" in French), which does not say what moves: a
plain drag moves the whole symbol, Shift+drag moves the text alone
(DynamicElementTextItem::mousePressEvent).

English: "Hold Shift and drag to move this text on its own".
French: "Maintenir Maj et faire glisser pour déplacer ce texte seul".

Translations only, as for #1124: the source string is unchanged, so the
other languages keep their current text.

Revives #499 (closed 2026-06-11 by its author to test locally first).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:03:52 +13:00
Kellermorph 6ea626fec7 Settings: configure the folder prefixes of the user collection
The prefixes of a collection's folders live in a qet_labels.xml that so
far could only be edited by hand, and a hand-edited file is easy to
break: one extra </category> and the file stops being well formed, which
the lookup answers with "no prefix at all". Labels then degrade
silently, without any error anywhere - the file is simply ignored. Add
a way to edit it from the settings.

- "Configurer les préfixes…" next to the user collection path opens
  PrefixConfigurationDialog, which lists every folder of that
  collection, subfolders included, one line edit each, with "Tout
  déplier"/"Tout replier" and OK/Abbrechen. It writes on OK only :
  cancelling leaves the collection exactly as it was, and an emptied
  field drops the <prefix> again so the folder goes back to inheriting
  its parent's.
- QetLabelsFile owns the reading, scanning, structure building and
  writing of the file. prefixFromLabelFile() moves there unchanged from
  assignvariables.cpp as prefixForPath(), so the lookup used by label
  assignment and the one used by the dialog are one piece of code.
- Entries whose folder no longer exists on disk are offered as
  Conserver or Supprimer and only applied on OK.
- An unparsable file is copied to qet_labels.xml.bak first, and the
  dialog then reports the line and column of the syntax error, with
  "Corriger le fichier" as the default choice - such a file may be one
  forgotten tag away from being valid - and rebuilds the whole
  structure only when "Reconstruire" is picked. A broken file that
  cannot be backed up is refused rather than overwritten.

The scan reads non-hidden directories recursively in name order,
without following symlinks. A collection without any subfolder has
nothing to configure and says so instead of opening an empty dialog.

Tests: a standalone harness kept outside the tree (84 checks :
structure, inheritance, explicit empty prefix, orphan handling, broken
file, wrong root element, broken file whose backup cannot be written,
reload after save) and the dialog driven offscreen (29 checks,
including "reject creates no file"). ctest 27/28, the failing
tst_menubarkeyboard being the headless F10 test, unrelated to this.
The new strings are French source strings like the rest of the code;
the .ts files are left to the translation update.
2026-09-29 23:52:22 +02:00
ispyisail 1dd56df048 Keep three crash-recovery snapshots instead of overwriting one
Discussion #598, reviving PR #654. The crash-recovery backup written
every 20 minutes went to a single file. If the project was already in a
bad state when a backup ran, that bad state replaced the only recovery
copy.

QETProject now writes the backups in turn to three KAutoSaveFile slots
(BackupGenerations), so one bad write only replaces the oldest
snapshot. Scope is crash recovery only; the opt-in autosave is
unchanged.

After a crash, the recovery prompt groups the snapshots by project and
offers one row per project with a list to pick the snapshot to reopen,
newest selected by default. The snapshots not picked are deleted.

Ported onto current master: writeBackup() keeps the "skip if nothing
changed" check (bugtracker #273) and offerBackupFiles() keeps its place
after the stale-file filter and before the crash report (#901).

Tested with the backup interval shortened to 4 s (test build only):
after three changes, master holds one recovery file, overwritten each
time; this branch holds three, with 4, 5 and 6 folios. After killing
QET, the prompt lists the project; picking the oldest snapshot reopens
4 folios, the default reopens 6. Twice each. ctest 34/34 with and
without KDE Frameworks; the carried-over KAutoSaveFile test passes in
the nokde build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:45:25 +13:00
ispyisail 473f792bac Add saving and loading the configuration to a file (discussion #610)
Revives #640, closed only to clear a review backlog. Two entries in
the Configuration menu: "Enregistrer la configuration sous..." saves
the settings to a .conf file, "Charger une configuration..." replaces
them with one. The file is always ini format, so a profile made on one
platform loads on the others.

Changes from #640:
- Window sizes, positions and dock layouts (".../geometry",
  ".../state") and the recent-files lists stay out of a profile and
  are kept on load: they describe this computer, not a preference.
- A saved file carries a format marker; loading refuses files that
  QElectroTech did not save.
- Loading closes QElectroTech and asks the user to start it again,
  instead of starting a second copy: a second copy started while this
  one is still running hands its arguments over and exits (main.cpp).
- The colour dialog's custom colours are reloaded from the profile
  before quitting, since ~QETApp() saves them over the loaded ones.
- Both actions are registered with ShortcutManager, so they appear in
  command search and can be given a shortcut.
- The copy rules live in the header-only utils/configprofile.h and
  are unit tested (tst_configprofile).

Loading still replaces rather than merges: a setting the profile does
not have is removed, or switching profiles would keep settings from the
previous one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:42:26 +13:00
ispyisail 79e87cb3b8 Set up the elements collection's items on the GUI thread
ElementsCollectionModel::loadCollections() runs setUpData() for every
item on worker threads (QtConcurrent::map), and setUpData() calls
setText(), setFlags(), setData() and setToolTip() on items that are
already in the model. Each of these changes the model and emits its
dataChanged() signal from a worker thread, which QAbstractItemModel
does not allow. Loading the shipped collection (8838 elements) emitted
dataChanged() 38958 times, all from worker threads.

The expensive part (reading every element file) stays on the worker
threads. Only the result is moved: ElementCollectionItem::setData()
keeps a value set from a worker thread on the item, data() returns it
to the same worker so setUpData() still reads back what it has set,
and the model applies the kept values on the GUI thread when the map
is finished, before emitting loadingFinished(). setUpData() called on
the GUI thread (macros collection, a single added or changed element)
is unchanged.

With this change the same load emits dataChanged() 38958 times, all on
the GUI thread.

Revives the still-needed part of #516, closed only to clear a review
backlog. Its other two changes are left out: the wait in
loadMacrosCollection() guarded a model shared with a running map,
which no longer happens (the macros always get a model of their own),
and qetinformation.h's static QString constants are a size clean-up,
not a bug.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:35:19 +13:00
ispyisail a28e834185 Remember dialog size and position across sessions
QETElementEditor, QETDiagramEditor and QETTemplateEditor already
persist their geometry via QSettings; none of the modal QDialog
subclasses did, so any dialog resized to see more of its content
(Search and Replace, Diagram properties, Export, ...) is back to its
default size the next time it's opened.

Adds QET::trackDialogGeometry(), one call at the end of each dialog
constructor (after any default resize()), following the same
restoreGeometry()/saveGeometry() pattern as the three editors above,
stored under [dialoggeometry] and keyed by class name by default. An
explicit key is used for PropertiesEditorDialog, a single class
templated over several unrelated wrapped editors, so they don't all
fight over one saved size.

Covers the 36 dialogs of the original change (#691) plus seven added
since: RenumberElementsDialog, MaterialEntryDialog, AiAssistantDialog,
DuplicateOffsetDialog, ImageTransparentColorDialog, PdfPagesDialog and
WiringListDialog.

Deliberately not touched: BackupDialog and ImageCropDialog, which
setFixedSize() themselves, and MaterialSelectionDialog, which already
restores its own size.

A size saved while the dialog sat partly off-screen is restored on
screen: restoreGeometry() moves it back inside the available screen
(checked: saved at 1502,972 on a 1600x1000 screen, reopened at 600,317).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:31:12 +13:00
ispyisail 47c70e8b53 Add an adjustable background frame to the element editor
Discussion #604. Revives #643, closed on 2026-09-10 only to clear the
review backlog, ported onto current master.

The element editor shows an infinite grid, with nothing to judge how
big a symbol will look on a real folio. Affichage > "Afficher le cadre
de fond" draws a dashed blue rectangle centred on the element's origin
(the hotspot), sized by default to the drawing area of a default folio
(BorderProperties: columns x column width by rows x row height,
1020 x 640). Affichage > "Taille du cadre de fond..." sets another
size. Both persist in QSettings (elementeditor/background_frame_*).

The frame is drawn in ElementView::drawBackground(), so it is not part
of the scene: it is never written to the .elmt file and never in the
SVG export (scene render).

Changed from #643: resolved against master's new-style connects and
the hotspot visibility switch; the toggle gets the unused
document-print-frame theme icon (light and dark), without which it
showed as a text button in the icon toolbar; the size action is
registered with ShortcutManager like the toggle.

(cherry picked from commit a245411f9f)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:30:08 +13:00
ispyisail 576f085ce8 Drop hardcoded light-gray background on disabled PLC-IO type combo
masterpropertieswidget.cpp set an inline "background-color: #f0f0f0"
stylesheet on the disabled type combo box in the PLC master I/O
table. Qt already renders disabled widgets correctly without help;
the hardcoded light color just breaks under a dark palette, showing
a bright box in an otherwise dark dialog. Dropping the stylesheet
lets it render like every other disabled widget in the app.
2026-09-30 09:44:56 +13:00
ispyisail fbb59ee1e7 Fix a crash when closing a project after its terminal strip window is gone
The terminal strip window's tree dock connects to its project's
destroyed() signal with a lambda capturing `this`, but without `this`
as the connection's context, and never disconnects it. When the dock is
deleted before the project, closing the project runs the lambda on
freed memory.

The terminal strip window is a single instance, parented to the editor
window it was first opened from. With two editor windows:

1. open the terminal strip manager from window 1 (project A);
2. open it from window 2 (project B): the same window switches to B,
   still a child of window 1;
3. close window 1: the terminal strip window and its dock are deleted,
   the connection to B stays;
4. close window 2, or project B: heap-use-after-free in the lambda
   (terminalstriptreedockwidget.cpp:66), reported by AddressSanitizer
   3 times out of 3. Freed by ~TerminalStripEditorWindow from
   ~QETDiagramEditor.

Passing `this` as the context removes the connection with the dock, as
FreeTerminalEditor, FreeTerminalModel and TerminalStripEditor already
do for the same signal. With the fix: no error, 3 times out of 3.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 09:22:05 +13:00
plc-user bae336ef5b Update define_definitions.cmake – fix only whitespace
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m27s
2026-09-29 21:57:43 +02:00
ispyisail 721bd155f5 Reject non-finite values when loading a wire
A wire read "nan" or "inf" for its position, its segment lengths and
its label position without checking them: QString::toDouble() accepts
all three as numbers. PR #792 and #996 closed this gap for elements,
terminals and texts; wires were left out.

The worst case is a segment length: a single "nan" passes the check
that the segments add up to the distance between the two terminals,
because every comparison with NaN is false. The whole path then turns
to NaN: on 2612_ats_singlephase.qet one bad length out of six was saved
back as six "nan" vertical segments, and the next save dropped them and
routed the wire automatically, so each save changed the file.

Now:
- a non-finite segment length is skipped like any unreadable one, so
  the path fails the coherence check and the wire is routed
  automatically at load, as already happens for "inf";
- a non-finite wire position is read as 0;
- a non-finite label position or rotation is ignored, as if the label
  had not been moved or rotated.

Found by the mutation sweep (python3 -m simulator sweep, mutators
inject_nan_coordinate / inject_inf_coordinate). The 24 example projects
resave byte-identical with and without this change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 08:42:12 +13:00
ispyisail 08271cb771 Merge pull request #1160 from ispyisail/fix/1155-multiline-text-alignment
Fix #1155: lines of a multi-line symbol text ignore its alignment
2026-09-30 08:14:52 +13:00
ispyisail bd4d942d71 Add a terminal name check when saving an element
Discussion #1157. IEC 61666 §4.1 requires each terminal to be
identified unambiguously within its object, so two terminals of one
element must not share a name.

On save, the element editor now:
- refuses to save when two terminals share a name, lists the names
  ("N ×3") and selects those terminals;
- warns, and still saves, when a terminal has no name. Folio reports,
  conductor definitions and thumbnails are skipped.

Names are compared after trimming surrounding spaces, case sensitive.

Settings > General > Editor has a new checkbox, on by default, that
turns both checks off (elementeditor/check-terminal-names).

--check-elements applies the same rule: repeated names are a FAIL,
missing names a WARN. It ignores the setting, since it is an explicit
check. On the shipped collection this reports 82 FAILs, the elements
that repeat a terminal name today.

The rule lives in the header-only editor/terminalnamecheck.h, shared
by both, and is unit tested by tst_terminalnamecheck.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 07:24:55 +13:00
ispyisail 0d869b7e50 Align the lines of a multi-line symbol text to its alignment
The lines of a wrapped or multi-line dynamic text were always centred,
whatever horizontal alignment the user picked, on the folio and in the
symbol editor alike. Follow the horizontal alignment instead.

The alignment still also sets the anchor point, as before, and saved
positions are unchanged. Existing multi-line texts set to the default
left alignment are now drawn left-aligned instead of centred.

Issue #1155

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 07:05:54 +13:00
plc-user f15736bff7 Improve German wording 2026-09-29 18:39:04 +02:00
Laurent Trinques 0592b661df Merge pull request #1156 from Kellermorph/ger-translation
German Translation
2026-09-29 16:20:34 +02:00
Kellermorph 10d7dae5d0 German Translation 2026-09-29 14:14:02 +02:00
Laurent Trinques 0f1717c636 Merge pull request #1153 from ispyisail/feat/1069-align-icons
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m39s
Add icons for the Align commands
2026-09-29 13:01:19 +02:00
Laurent Trinques a16bb938f5 Merge pull request #1154 from ispyisail/fix/1145-shape-drag-snap
Fix #1145: drag a shape onto the grid by its corner
2026-09-29 12:59:21 +02:00
ispyisail cc0521bfe5 Drag a shape onto the grid by its corner, not its hidden origin (#1145)
A dragged shape put its pos() on the grid. pos() cannot be seen, and it
is off the drawn corner whenever the shape was drawn or resized with
Ctrl held, or rotated, so such a shape stayed off the grid however it
was dragged. Worse, Snap to grid (previous commit) moves pos() off the
grid to put the corner on it, so the next drag undid the snap.

QetShapeItem now overrides setPos(), which only the drag calls through
the virtual: dragged alone or with other shapes only, the top-left
corner of the drawn outline goes on the grid. Dragged together with
anything else it snaps by pos() as before, because the rest of the
selection follows this shape's movement and a corner correction would
take the symbols off the grid. Ctrl still drags freely: the snap goes
through Diagram::snapToGrid(), which reads it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 23:39:23 +13:00
ispyisail 1fdb414006 Give the Align commands icons
The seven Edit > Align commands (Snap to grid and the six align
commands, discussion #1069) had no icons: the menu, the context menu,
command search and the shortcut bar showed them as bare text.

They are pixel-grid SVGs in ico/scalable/ drawn like the folio and
Add PDF icons: 24 pixel canvas, art on the inner 22 pixel grid, one
currentColor ink so misc/make_icon_themes.py writes the dark copies.
Each align icon is a guide line with two boxes of different lengths
against it; the centre ones show the guide only between the boxes.
Snap to grid is a faint grid with one box sitting exactly on a cell.
The boxes have a solid outline and a 35 % tinted inside, which keeps
the guide the strongest mark at 16 pixels.

Names follow the freedesktop icon names (align-horizontal-left, ...).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 23:37:40 +13:00
Laurent Trinques 367efb5589 Merge pull request #1152 from ispyisail/fix/1145-align-shapes
Fix #1145: let Align and Snap to grid move drawn shapes
2026-09-29 12:31:24 +02:00
Laurent Trinques a4f6dfe8b4 Merge pull request #1151 from ispyisail/fix/1146-locked-group-member
Fix a group splitting apart when one of its items is locked (#1146)
2026-09-29 12:27:05 +02:00
Laurent Trinques b094ff328c Merge pull request #1150 from ispyisail/fix/1144-group-menu-refresh
Fix right-click menu offering Group again after grouping (#1144)
2026-09-29 12:26:52 +02:00
ispyisail 795484ff4f Let Align and Snap to grid move drawn shapes (#1145)
Rectangles, ellipses, lines and polygons were left out of the Align
submenu: with only shapes selected every command was greyed out, and a
shape outside a group was ignored when aligning it with a symbol.

Shapes now take part like pictures. Their edges are the shape as drawn
(the new QetShapeItem::sceneOutlineRect(), without the pen, the 6 px
selection margin or the wider hover outline; the old code used
sceneBoundingRect() for grouped shapes and so aligned them 6 px off).
The point that goes on the grid is the top-left corner of that box: a
rectangle's corner, an ellipse's box. pos() is not used, because a
shape drawn with Ctrl held or rotated has its corners off the grid
while pos() is on it.

The menu's enable rule now counts what the command counts, a group as
one. Before, two symbols in one group enabled the six align commands,
which then did nothing and only said so in the status bar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 23:24:50 +13:00
ispyisail 2dbaa69186 Fix a group splitting apart when one of its items is locked (#1146)
Grouping two symbols and then locking one of them (Lock position in its
properties) left the group in a broken state: dragging the unlocked symbol
pulled it away while the locked one stayed, and dragging the locked one
did nothing. The move simply dropped locked items, so the rest of the
group went without them.

A group with a locked member now does not move at all, whichever member
is dragged, and the status bar says why. This is the rule the item-groups
proposal (discussion #1070) set out for this case. The same rule applies
to the arrow keys and to the Align commands, which share
DiagramContent::removeNonMovableItems().

Also fixed on the way, for a plain selection with a locked symbol: a wire
between the locked symbol and one being dragged kept its user-placed text
moving with the dragged end. Such a wire is now redrawn only, as a wire to
an unselected symbol already is.

Checked in the GUI on two symbols joined by a wire (grafcet example),
master against this branch, positions read from the saved file:
- drag the unlocked member: master moves it 190 px, this branch moves
  nothing and shows the message
- arrow keys on the selected group (3 runs each): master moves the
  unlocked member, this branch nothing
- the same two symbols ungrouped: both move the unlocked one, as before
- user-placed wire text: master shifts it 190 px, this branch keeps it
ctest: 34/34.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:23:32 +13:00
ispyisail 1d816f432c Fix the right-click menu offering Group again after grouping (#1144)
Group and Ungroup are enabled in slot_updateComplexActions(), which ran
only when the selection changed. Grouping, ungrouping and undoing either
leave the selection as it is, and so does right-clicking an item that is
already selected, so the actions kept the state from before. The
right-click menu hides disabled actions, so it went on showing Group on a
selection that was now one group (and Ungroup after ungrouping), and the
one that would work was not in the menu at all.

Diagram::setItemGroup() is the one place an item's group changes (group,
ungroup, undo, redo, paste), so it now emits itemGroupChanged() and the
editor refreshes its actions on it, beside the selectionChanged
connection.

Checked in the GUI on two free texts, saving after each step: right-click
> Group, right-click again, Ctrl+Z, right-click again. Before, the second
and third right-clicks offered Group again and did nothing (both saves
still grouped). After, they offered Ungroup and ungrouped (0 grouped
texts in both saves); Group, Ungroup, Group also round-trips.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 23:09:57 +13:00
ispyisail cbbb9fd0ac Merge pull request #1149 from ispyisail/fix/register-filetypes-no-admin
Fix .qet file association from the Windows ZIP without admin rights
2026-09-29 22:31:03 +13:00
ispyisail df442c81e3 Fix register_filetypes.bat for the portable ZIP and for non-admin users
The Windows portable ZIP ships "Lancer QET_qt6.bat", but the script only
looked for "Lancer QET.bat", so it stopped at its first check and never
registered anything. It now tries the ZIP's name first and falls back to
the installer's.

It also wrote to HKEY_CLASSES_ROOT, which a standard (non-admin) Windows
account cannot create keys in. It now writes to
HKEY_CURRENT_USER\Software\Classes: per-user, no elevation needed, and
Windows merges it into HKEY_CLASSES_ROOT for that user.

qet_uninstall_file_associations.reg removes the HKCU keys first, then the
old HKEY_CLASSES_ROOT ones as before.

Tested under Wine 10 on the git10275 nightly ZIP: the old script aborts
("Lancer QET.bat ... n'a pas ete trouve"), the new one registers .qet,
.elmt and .titleblock under HKCU with nothing under HKLM, the uninstall
file removes them, and the "Lancer QET.bat" fallback works. Wine does not
enforce admin rights, so the non-admin case itself is not proven there.

Reported in #1148.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 22:00:06 +13:00
ispyisail 48446e01e4 Merge pull request #1142 from ispyisail/feat/database-from-document
Fill the project database from the file, not from the built folios
2026-09-29 20:57:16 +13:00
ispyisail 8fdff95732 Merge remote-tracking branch 'upstream/master' into feat/database-from-document
# Conflicts:
#	sources/qetproject.cpp
2026-09-29 19:00:16 +13:00
ispyisail 33a4ef5bad Merge pull request #1140 from ispyisail/fix/project-filename-first-dot
Fix title blocks cutting project file names at the first dot
2026-09-29 18:53:54 +13:00
ispyisail 79717ccd61 Merge pull request #1138 from ispyisail/feature/connect-ai-assistant
Add Help → Connect an AI assistant
2026-09-29 18:53:29 +13:00
ispyisail fd7aec4071 Merge pull request #1137 from ispyisail/feature/qet-mcp-windows-python
Offer Python with the Windows installer, for the MCP server
2026-09-29 18:53:01 +13:00
ispyisail 2511566d8b Merge pull request #1136 from ispyisail/feature/qet-mcp-install
Install the MCP server with QElectroTech
2026-09-29 18:52:36 +13:00
ispyisail fb61e03bd7 Merge pull request #1135 from ispyisail/fix/qet-mcp-windows-launch
Fix the MCP server failing to start QElectroTech on Windows
2026-09-29 18:52:10 +13:00
ispyisail b1abab1241 Work out formula labels and wire texts from the file, with the folios' code
The first fill from the document stored the label and wire text saved in
the file. QElectroTech saves what it last worked out, not what the formula
gives now: K%total-%id saved as K1-1 on a 3-folio project is K3-1 on the
folios, and the same for a wire's formula. So the document fill disagreed
with the folio fill on any file whose folios had been added or moved.

AssignVariables now works from a FormulaContext: the folio's number,
index, total, plant, location, title-block and project variables, and the
element's grid cell and prefix or the wire's four properties. The Diagram
overload fills one from a built folio, the database fills one from the
file, and both call the same evaluation.

Also brought in line with Diagram::fromXml(): an element Element::valideXml()
rejects is skipped with its wires; a folio where two elements number their
terminals alike (Element::fromXml() refuses one, by geometry), a frozen
formula wire text, and the older sequential-number attributes are left to
the folios. Each folio's border now starts from the defaults a new Diagram
has, not the previous folio's values.

tst_databasefromdocument: formulasAreWorkedOut (every variable kind, stale
saved values), unbuiltElementIsLeftOut, clashingTerminalIdsFallBack. Each
fails with its fix removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 16:08:45 +13:00
ispyisail 42fab5ad78 Fix title blocks cutting project file names at the first dot
A project saved as "my.project.qet" showed "my" in every title block
cell using %{projectfilename}, and "my" again in %{savedfilename} after
a save. QETProject used QFileInfo::baseName(), which stops at the first
"."; completeBaseName() strips only the ".qet" suffix. Same fix as
#725 made for the PDF export's file name.

Verified headlessly on examples/industrial.qet copied to
my.project.qet, with a title block cell set to %{projectfilename}:
--export-svg shows "my" on master and "my.project" with this change,
on all 50 folios. For %{savedfilename}, a --run script calling
qet.save("") twice writes "my" on master and "my.project" with this
change. ctest 32/32.

Not changed: write() updates the saved* variables after the file is
written, so the values stored in a file are those of the save before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 15:56:32 +13:00
ispyisail a0d5c4fa9c Fill the project database from the file as it is read
The project database -- behind parts lists, summary tables and the wiring
list -- was filled by walking the built folios: every element and
conductor object of every scene. Filling it from the document the project
was just read from instead is what the database needs before a project
could be opened without building every folio (qelectrotech-docker
DB-FROM-XML-SCOPE.md).

updateDB(document), called by readProjectXml(), fills diagram,
diagram_info, element, element_info, terminal and conductor from the
document, through the code the folios use: a BorderTitleBlock read from
each folio's XML gives the title-block values and each element's grid
cell, the embedded collection gives each definition, and the binders are
shared with the live path. Shapes, texts and pictures still come from the
folios (their boxes need fonts and pens).

It falls back to the folios, saying why in the log, when the document
does not carry what that needs: an item without a saved uuid (older files
-- the folios derive them on load), a conductor naming its ends the older
way, a %autonum folio number, a terminal showing its master's label, a
missing or unbuildable definition. A symbol label computed from a formula
is the one saved in the file, which QElectroTech writes as it computes it
on every save. QET_DATABASE_FROM_FOLIOS=1 forces the folio path.

tst_databasefromdocument saves every example once, then fills both ways
and requires identical tables (24/24, filled from the document each
time), and checks that an older file falls back with its reason. Red when
the document path is made to write a wrong grid cell. Database phase of
loading unchanged: industrial.qet 0.146 s vs 0.160 s, Polonez 0.040 s vs
0.038 s (median of 5).

QETProject::projectWideProperties() is split out of
updateDiagramsFolioData() so both fills use the same title-block context.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 15:38:20 +13:00
ispyisail ddc645174f Connect an AI assistant: say when Python is missing
The configuration names "python" or "python3", or the Python the Windows
installer can add. When none of those can run, the assistant fails with
an error far from the cause, so the dialog now says so in bold, with what
to do: tick "Python pour l'assistant IA" in the installer or install
Python (Windows), or install Python 3 from the system's packages.

Windows 10 and 11 put a python.exe in WindowsApps that only opens the
Microsoft Store, so a PATH search alone reports Python where there is
none. A result there is reported as possibly that shortcut. Backslashes
are converted explicitly, not with QDir::fromNativeSeparators(), which
leaves them alone off Windows and so could not be tested here.

tst_aiassistantsetup: 17 cases (8 new: the bundled Python is taken
without looking on PATH; none, python.org, the Store shortcut with either
separator, and a Linux folder named WindowsApps). Each of the 4 new rules
was removed in turn and the test failed. ctest 32/32. In the GUI, the
same install layout shows the warning with no python3 on PATH and no
warning with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:17:16 +13:00
ispyisail 95914c7ee1 Merge remote-tracking branch 'upstream/master' into feature/connect-ai-assistant 2026-09-29 14:40:38 +13:00
ispyisail 9778dc0a86 Merge branch 'feature/qet-mcp-install' into feature/qet-mcp-windows-python 2026-09-29 14:37:53 +13:00
ispyisail c4b1e02810 Merge remote-tracking branch 'upstream/master' into feature/qet-mcp-install 2026-09-29 14:37:43 +13:00
ispyisail 7fa2643ac5 Merge remote-tracking branch 'upstream/master' into fix/qet-mcp-windows-launch
# Conflicts:
#	misc/qet-mcp/test_qet_mcp.py
2026-09-29 14:37:36 +13:00
ispyisail 2801d93afd Merge pull request #1134 from ispyisail/fix/database-folio-date-century
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m31s
Fix folio dates a century early in the project database
2026-09-29 14:35:41 +13:00
ispyisail 06fc5153c1 qet-mcp: make QElectroTech launches work on Windows
On Windows every tool that starts QElectroTech (qet_export, qet_edit,
qet_query, qet_check, qet_continuity, qet_project_new) failed, for two
reasons:

- The server ran a copy of the executable from a temporary folder, to get
  its own SingleApplication key. A Windows program loads its DLLs from its
  own folder, so the copy died before main() with 0xC0000135 (DLL not
  found). Every flag the server passes is a CLI export flag or --run, and
  main.cpp handles both before it constructs SingleApplication, so on
  Windows the original is now run. The copy stays elsewhere.
- It set QT_QPA_PLATFORM=offscreen. The Windows packages ship only the
  qwindows platform plugin, so Qt found none and stopped at a message box
  nobody could close: every call hung until its timeout. Windows now keeps
  its default platform; the export flags and --run open no window.

Checked under Wine (qet-wine-smoke) on the fork's CI Windows build, run
through python.org's embeddable Python: before, qet_export ended with
exit 3221225781; after, a PDF export, a qet_query (98 elements, as on
Linux) and a qet_edit placing a common:// element all succeed. The hang
was isolated by launching the same export from bash (works) and from
Python with one change at a time: only dropping QT_QPA_PLATFORM made it
work. Four unit tests pin both choices per platform; each fails with its
fix removed. Suite 262/262 none skipped on Linux.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 14:25:06 +13:00
ispyisail d0a439a448 Merge pull request #1133 from ispyisail/test/qet-mcp-audit-element-tools
Add exact-answer tests for qet_check, qet_continuity and the symbol-file tools
2026-09-29 14:23:13 +13:00
ispyisail 0392d74e15 Merge pull request #1131 from ispyisail/fix/qet-mcp-folio-ref-uuid
Fix qet_edit's folio "$id" naming the wrong folio after an insert
2026-09-29 14:22:54 +13:00
ispyisail 37eb7ce5ac Merge pull request #1130 from ispyisail/test/qet-mcp-audit-survivors
Add qet-mcp tests for what a second mutation audit found untested
2026-09-29 14:22:35 +13:00
ispyisail 2f655dfbc7 Merge pull request #1129 from ispyisail/fix/qet-mcp-pin-binary
Fix the MCP server running any program an assistant names
2026-09-29 14:22:15 +13:00
ispyisail d331a79e8b Merge pull request #1124 from ispyisail/fix/935-english-wording
Fix broken and untranslated English strings (#935, phases 1–2)
2026-09-29 14:21:56 +13:00
ispyisail 3139fa54f7 Fix folio dates a century early in the project database
diagram_info.date -- and project_summary_view.date, which the summary
table dialog queries -- held the folio date read back from the title
block's text: QLocale::system().toDate() of the string
updateDiagramContextForTitleBlock() writes in the locale's short format.
Where that format has a two-digit year (en_US "M/d/yy") Qt reads the year
back as 19xx, so 741.qet's 2010-09-21 became 1910-09-21, and so did every
dated folio of the 24 examples (109 of 109).

bindDiagramInfoValues() now binds the folio's own date,
BorderTitleBlock::date(), the value that text was made from. A folio set
to show the current date or none gives the same date as before.

tst_dbfoliodate runs --run on examples/741.qet under LC_ALL=en_US.UTF-8
and expects 2010-09-21 from project_summary_view: 1910-09-21 without this
change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 14:14:15 +13:00
ispyisail 0075ee208f qet-mcp: exact-answer tests for qet_check, qet_continuity and the .elmt tools
A mutation audit of the functions no audit had covered (tests needing no
QElectroTech): these 13 caught 153 of 337 planted bugs (45 %). With these
tests, 322 (96 %). qet_check was at 26/59 and qet_continuity at 24/37
even with the binary tests, since those look at a finding or two.

- qet_check / qet_continuity: _run_qet replaced by a stub returning chosen
  log lines, so the whole answer is compared -- summary counts, "ok",
  passed, check_failures, each finding's count, note and sampled rows,
  sorting by severity, folio_number, the launch hint carried through,
  the folio argument in the script, folio bounds, and lines that only
  look like ours.
- qet_element_build / qet_element_info: the written header, names, kind
  information and terminals, and the reported result, key for key; the
  refusals; geometry worked out by hand; every part kind's extent and
  written attributes; number formatting.
- qet_element_search and its index: the index entry key for key, the
  cache, ranking (exact name, then first word, then length), the default
  and given limits.

The 15 left are equivalent: timeouts and output limits, a ranking
constant that only has to exceed 0, and a containment check the 5-unit
margin keeps from ever failing. Tests only; 282/282.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 14:00:02 +13:00
ispyisail c76ef10e0b Add Help > Connect an AI assistant
A dialog showing the configuration an AI assistant needs to start
QElectroTech's MCP server (misc/qet-mcp), with this installation's paths
filled in: pick the assistant (Claude Desktop, Claude Code, GitHub
Copilot in VS Code, Cursor, Gemini CLI, Codex CLI, LM Studio), the
drawings folder the assistant may use, and whether it may edit (off by
default). It says where the text goes, warns that text inside a project
from someone else can steer an assistant, and copies the text. It writes
nothing and starts nothing.

AiAssistantSetup finds the server beside the running program, in the two
places the packages put it (<root>/mcp beside <root>/bin on Windows,
<prefix>/share/qelectrotech/mcp beside <prefix>/bin otherwise), and the
Python the Windows installer can add. When there is none, the dialog says
the server is not installed with this version and links the guide.
The configuration is written with QJsonDocument, so Windows paths are
escaped correctly; Codex CLI gets TOML.

tst_aiassistantsetup (9 cases): both layouts, no server, the bundled
Python on Windows only, valid JSON for every JSON client with paths that
parse back unchanged, the "servers" key for VS Code, "type" only where
wanted, editing off unless allowed, escaped TOML. Each of 8 rules was
removed in turn and the test failed. ctest 32/32.

Checked in the GUI (Xvfb): Help menu opens it; from a build tree it
reports the server missing and disables Copy; from an install layout it
fills the paths, and the text Copy put on the clipboard, used unchanged
as a Claude Code configuration, ran a real qet_project_info call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:29:50 +13:00
ispyisail 6b9c5189b1 Offer Python with the Windows installer, for the MCP server
Most Windows users have no Python, which the MCP server needs. The
installer now offers "Python for the AI assistant", unticked by default:
the official embeddable package from python.org (3.14.7, ~12 MB, no
registry, removed with QElectroTech), in <folder>\mcp\python. The
portable folder and the MSI, which pack all of files/, carry it.

The workflow downloads it pinned by version and by python.org's own
sha256 for the file (checked against python.org's download API), and
fails the build on a mismatch. The script stays plain text beside it.

Checked under Wine (64-bit, qet-wine-smoke image), on an installer built
with makensis 3.10 (0 warnings, strings in all 29 languages):
- a silent default install puts mcp\qet_mcp.py in place and no Python;
  the same installer with the section ticked by default installs it, so
  the check tells the two apart;
- the installed Python runs the installed server: 15 tools listed,
  qet_project_info on an example answers as on Linux, and the server
  finds bin\QElectroTech.exe and elements\ by itself.
Not checked: a real Windows machine; the CI download step (fork run).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:00:30 +13:00
ispyisail ac952d3df6 Install the MCP server with QElectroTech
The MCP server (misc/qet-mcp) was only reachable from a source checkout.
It now ships with every package, next to the program, and finds that
QElectroTech and its element collection from where it sits:

- make install (Linux distributions, snap, flatpak):
  <prefix>/share/qelectrotech/mcp/qet_mcp.py, executable, with its README.
- Windows installer: a new "AI assistant (MCP)" component (on by default,
  ~200 KB), installed to <folder>\mcp. The workflow stages files/mcp, so
  the portable folder and the MSI, which packs all of files/, carry it too.

The server learns the Windows layout (<root>/mcp beside <root>/bin, whose
program is QElectroTech.exe, and <root>/elements), in addition to
<prefix>/share/qelectrotech/mcp. A copy saved anywhere else, even beside
some bin/ folder, is not taken for an installation.

The installer strings are given in all 29 installer languages: French
translated, the others in English until translated, which is what NSIS
would fall back to anyway but without its warning 6040 per language.

Checked: make install into a scratch prefix, then from the installed
script with nothing configured and no qelectrotech on PATH, an SVG export
and a qet_edit placing a common:// element both succeeded. makensis 3.10
compiles the installer with 0 warnings before and after (removing the
French description gives warning 6040), and the installer contains
mcp/qet_mcp.py. Suite 274/274 none skipped; each layout check was
removed in turn and a test failed. Snap/flatpak: installed by the same
CMake rule; launching their QElectroTech from outside is untested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:55:10 +13:00
ispyisail 124b7e4f6e qet-mcp: stop a tool call choosing the program the server runs
qet_export, qet_edit, qet_query, qet_continuity, qet_check and
qet_project_new took the QElectroTech executable as a per-call argument
and ran whatever executable file it named, with the call's own paths as
arguments. The workspace policy exempted it as configuration, but it is
chosen by the model on every call, so text inside a project could steer
an assistant into starting another program.

The server now finds QElectroTech itself: QET_BINARY, then the install it
ships in (<prefix>/share/qelectrotech/mcp/), then PATH. "binary" becomes
optional; when given it must be that same file (after resolving
symlinks) or one listed in QET_MCP_BINARIES. QET_MCP_ALLOW_ANY_BINARY=1
restores the old behaviour, as QET_MCP_ALLOW_ANY_PATH does for paths.
"elements_dir" defaults to the installed collection and, when given, must
be in the workspace, that collection, or QET_MCP_ELEMENTS.

Both checks live in enforce_path_policy(), the one place tool arguments
enter. test_configuration_paths_are_exempt asserted the old exemption and
is replaced by BinaryPolicy (13 tests) and a stdio test of the original
reproduction. Each new check was removed in turn and the tests failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:11:03 +13:00
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
Laurent Trinques fdcdfd749e Merge pull request #1127 from ispyisail/fix/bugtracker-105-diagonal-wire
Fix bugtracker #105: straight wire drawn diagonally after a move
2026-09-29 01:01:36 +02:00
Laurent Trinques 041e0fc2a6 Merge pull request #1123 from arummler/cpp-standard-raise
Fix small depreciations in C++20
2026-09-29 01:01:14 +02:00
ispyisail 94b0d8d06d qet-mcp: a folio's "$id" keeps naming it after a later insert or removal
The "$id" of an add_folio or insert_folio held the index the folio had
when it was made, so a later insert_folio or remove_folio in the same
qet_edit run shifted it, and ops naming "$id" edited the wrong folio --
the case #1115 fixed for folios given by uuid, left open for these.

The script now also keeps such a folio's uuid (qet.folioUuid()) and
resolves "$id", where an op takes a folio, through qet.folioIndex() at
the moment it is used. On a build without folioUuid() it falls back to
the stored index, as before, and nothing new is required of the binary.
The op's reported result is still the index.

Test: add_folio "$f" (index 1), insert_folio at 0, set_folio_title
"$f": the title lands on the third folio -- on the second without this
change. Two script-generation tests updated for the new expression.
255/255.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:46:18 +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 2ea279cc2b Fix bugtracker #105: straight wire drawn diagonally after a move
updateConductorPath() fits a stored profile to the new terminal
positions by sharing the horizontal difference over the profile's
horizontal segments and the vertical one over its vertical segments.
When a profile has no segment of non-zero length along an axis, the
difference along that axis was dropped and the last point joined the
terminal diagonally. On save that diagonal was written as one
axis-aligned segment, so on reopen pathFromXml() found the lengths
incoherent and rerouted the wire.

Every straight wire with a stored path hits this after a reload, as a
zero-length segment is saved as horizontal. Moving one end at a right
angle to the wire, in the direction that keeps its path type, showed it.

Generate a new path in that case, as is already done when there is no
profile for the path type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:09:27 +13:00
ispyisail 5683a53536 English: fix broken and untranslated strings (#935, phases 1 and 2)
Translation text only; no source string, no other language touched.
"folio" is kept, as decided in #935, and the two "on the current sheet"
strings whose source says folio now say folio too.

- Broken wording: "N° scheme", "Export fully folio", "Title of folio",
  "Label folio", "N° of folio", "Dimensions of folio", "Folio Untitled",
  "Move up  this folio", "Title block informations", the table
  "lines are missing %1" warning.
- Folio report elements get one name, "folio reference", which
  DiagramView already used (was: folio referencing(s), reference folio
  following, previous reference folio, folio reports).
- Machine-translation damage: "</ b>" broke the folio-reference error
  message's HTML, and two variable help texts listed "% F", "% l" etc.,
  which are not variables.
- Plurals still showing French or wrong: conductor colour undo entry
  (empty, so French was shown), "%n erreur", "%n forme", "%n item placed"
  for both forms, "redesigned" for redrawn, and "bornes" rendered as
  "boundaries" in the element-reload message.
- Cross-reference spelled one way; "Réf." and "Numéro : %1" translated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:00:58 +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
Andre Rummler 91ef85f891 Unify Qt6:: usage. 2026-09-28 23:38:01 +02: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
Andre Rummler a72384850a Replace depreciated aAdConst with STL. 2026-09-28 23:19:46 +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
Andre Rummler 2b813a0d91 Fix Qt6 package name which was forgotten when removing the variable used to differentiate between Qt5 nd Qt6. 2026-09-28 23:03:11 +02:00
Andre Rummler 574d6dcd56 Fix captor operator [=] which needs to be written explciitly [this] in C++20. 2026-09-28 23:01:42 +02:00
Andre Rummler 7facb1cbf8 Fix cast which becomes a warning in C++20. 2026-09-28 23:00:58 +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
ispyisail 75450d7102 Make saving a just-saved project change nothing
Saving a project that had just been saved changed it again in 18 of the
24 example projects, so a project kept in version control showed changes
nobody made. Both causes were cleanup done on save but not on load:

- Symbol information whose values were all empty was written as an empty
  <elementInformations/> block (DiagramContext::toXml() skips empty
  values, Element::toXml() wrote the block anyway). The next load read it
  as no information and the next save dropped it. The block is now written
  only when something went into it.
- Information values were trimmed on save but not on load, so a label with
  stray spaces (" PRISE") kept them in memory and in its displayed copy
  until the project was opened again. The same rule, kept in one place,
  now applies when reading: stray whitespace around real content trimmed,
  a value that is only whitespace kept (#973).

All 24 examples now save identically a second time (master: 6), and each
one's first save is byte-for-byte what master wrote only on its second.
A title-block property set to a single space keeps it through two saves.

tst_resaveunchanged runs --resave twice on Projet_vierge.qet and
m_000.qet; both fail without this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:59:02 +13:00
ispyisail b3a4e30010 Give wires saved without a uuid a lasting one, from what they connect
A wire saved without a uuid got a random one on every load, never saved
(#754): it had no identity from one session to the next, so a script
could only name it as "the wire on terminal N of symbol X", and a
comparison of two versions could not tell a moved wire from a new one.

When a folio is loaded, such a wire now gets a UUID v5 derived from its
two ends -- the symbol and terminal at each, sorted so the direction it
was drawn in does not matter -- and it is written on save. Never its
place in the file or its folio's index: inserting or moving a folio, or
saving the wires in another order, does not change it. Once saved the
uuid no longer depends on the ends, so re-connecting the wire keeps it;
QETProject::derivedItemUuid() never hands out a uuid the file already
carries, so a wire later drawn on the ends it left gets another one.
Wires that have a uuid keep it; a paste still renews them.

The 24 example projects: 3,189 wires, none with a uuid before, all 3,189
after one save, none lost, no uuid used twice in any project; two saves
of the same file are identical, and a second save keeps every wire's
uuid. Discussion #1103 has the measurements behind the recipe.

tst_derivedwireuuid runs --resave on a fixture naming ends by uuid and on
examples/tremie_vibrante.qet (ends by terminal number): every wire gets a
distinct uuid, the same on every load, read back after a save, kept per
wire when a folio is inserted, the wires are reordered or a wire is drawn
the other way, and a newcomer on a re-connected wire's old ends gets
another uuid. Without this change 14 of the 18 fail; with the uuid taken
from folio index and file order instead, the folio-insert and reorder
tests fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:13:02 +13:00
ispyisail e12410e439 Never derive a uuid the file already carries
A symbol keeps its derived uuid once saved, even when it is moved. A
symbol saved without a uuid that later turns up on the spot it left -- a
hand edit, an older version, another tool writing the file -- derived the
same uuid, and the project had two symbols with one identity.

readDiagramsXml() now collects every symbol and wire uuid the file
carries, on any folio, before a folio loads; derivedItemUuid() moves to
the next counter value while a candidate is among them. The result still
depends on the file alone. Renamed from derivedUuid(), which QETProject
already has for the project's own uuid.

tst_derivedsymboluuid: newcomerOnAMovedSymbolsSpotGetsAnotherUuid fails
with the check switched off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:13:02 +13:00
ispyisail 0721b42e21 Give symbols saved without a uuid the same one on every load
A symbol saved without a uuid got a random one from Element::fromXml()
on every load, and the next save wrote it out: two loads of the same file
gave the same symbol two identities, and anything pointing at it by uuid
(a script, a comparison of two versions, a wire's identity) could not
follow it from one session to the next.

When a folio is loaded, such a symbol now gets a UUID v5 derived from
what it is and where it sits: its type, its position on the folio and its
orientation. Never the folio's index, so inserting or moving a folio does
not change it. Identical symbols stacked on one spot, or a copied folio,
are told apart by a counter kept per project (QETProject::derivedUuid()),
in load order among those symbols alone. A paste still renews uuids.

Symbols that have a uuid in the file keep it. All 24 example projects
already have one for every symbol, so they are unchanged; with the
symbols' uuids stripped, each saves byte-for-byte the same twice (master:
different every time).

tst_derivedsymboluuid runs --resave on a fixture with its uuids stripped:
same uuids on every load, same after a folio is inserted in front, saved
uuids kept, stacked copies differ. The first two fail without this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:13:02 +13:00
ispyisail 9b967707a0 qet-mcp: qet_edit names a conductor by its uuid
set_conductor, move_conductor_segment and delete_conductor named a
conductor by one of its terminals, which had to carry exactly one
conductor: where two meet at a terminal, neither could be named from it.

Each now also takes "conductor": "{uuid}" (as qet_conductors reports it)
in place of element + terminal. The generated script asks
qet.conductorEnds() for the conductor's two ends and passes the one whose
terminal carries only that conductor. Where both ends are shared, or no
conductor has that uuid, the op fails and its "note" says which. The
lookup is required only when a uuid is used; giving both forms is an
error.

Tests: the script generated for each form and the argument errors; on a
folio where one terminal carries two conductors, deleting either by uuid
leaves exactly the other; an unknown uuid is reported. With the end chosen
without checking its terminal carries only that conductor, the terminal
test fails. 248/248 with a build carrying qet.conductorEnds().

Stacked on the conductorUuids()/conductorEnds() scripting change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:12:55 +13:00
ispyisail 353dbd3317 Scripting: list a folio's conductors by uuid and find their ends
A script could name a conductor only by one of its ends, "the conductor on
terminal N of element X", which fails where two conductors meet at one
terminal and cannot follow a conductor that is re-connected.

qet.conductorUuids(folio) lists the folio's conductor uuids, in the order
qet.conductors() lists them. qet.conductorEnds(folio, uuid) returns that
conductor's two ends as "{element uuid} terminal N" -- the form
conductors() prints and the conductor calls take -- or an empty list if
the folio has no such conductor. The end formatting conductors() already
did is shared rather than copied.

Conductors of older projects have no saved uuid yet, so theirs change
from one load to the next until that is settled (discussion #1103); new
conductors keep theirs.

tst_scriptconductoruuid runs a script through --run on a fixture: every
conductor has a distinct uuid, and its ends match the conductors() line
at the same position; an unknown uuid, a malformed one and a folio that
does not exist give empty lists. It fails with the two ends swapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:07:16 +13:00
Laurent Trinques 5ca047a05d Merge pull request #1104 from Alfmat01/master
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m29s
Complete Spanish translations
2026-09-28 09:40:57 +02:00
Alfmat01 db3ca9eb6d Merge pull request #1 from Alfmat01/Alfmat01-Complete-Spanish-translations
Complete Spanish translations

Complete 1,383 pending entries in lang/qet_es.ts, using terminology appropriate for electrical schematics, wiring and control panels.

Translate missing entries and review unfinished drafts.

Preserve placeholders, plural forms, links and markup.

Leave previously completed entries unchanged.

Validation: XML structure checked and no pending translations remain in the submitted file. Not tested in the running application.
2026-09-28 09:33:08 +02:00
Alfmat01 257ec77136 Complete Spanish translations
Complete Spanish translations
2026-09-28 09:27:43 +02:00
ispyisail 1586aaec75 Merge pull request #1097 from ispyisail/fix/qet-mcp-free-texts-only
Fix qet_diff counting a symbol's own texts as free texts
2026-09-28 16:48:53 +13:00
ispyisail 4ad352126b Merge master into fix/qet-mcp-free-texts-only
Conflict in _extras(): keep master's tables and folio uuids and _angle(),
and read only the folio's own inputs/shapes/images as this branch does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:26:31 +13:00
ispyisail 2e56001c9d Merge pull request #1102 from ispyisail/fix/legacy-symbol-input-texts
Fix schema_indus.qet example showing no device labels
2026-09-28 16:21:27 +13:00
ispyisail 65f10f6c86 Merge pull request #1092 from ispyisail/fix/search-list-dark-icons
Fix element icons that are dark on a dark theme in search results
2026-09-28 16:20:42 +13:00
ispyisail 5585d87ea5 Merge pull request #1089 from ispyisail/fix/1069-align-lone-shape
Fix Align centre commands when a group is left with only a shape
2026-09-28 16:20:11 +13:00
ispyisail 83da997c83 Merge pull request #1100 from ispyisail/feat/qet-mcp-items
Add qet_items: list drawn items with their uuids
2026-09-28 16:15:03 +13:00
ispyisail 049a4ba673 Merge pull request #1101 from ispyisail/feat/qet-mcp-edit-table-text-uuid
Let qet_edit address tables and symbol text fields by uuid
2026-09-28 16:14:41 +13:00
ispyisail f14bc0de28 Merge pull request #1098 from ispyisail/feat/script-table-text-uuid-index
Add scripting lookups for tables and symbol text fields by uuid
2026-09-28 16:14:09 +13:00
ispyisail 6d62140445 Merge pull request #1096 from ispyisail/fix/qet-mcp-diff-rotation
Fix qet_diff not reporting a rotated symbol
2026-09-28 16:13:34 +13:00
ispyisail d04eee756a Merge pull request #1095 from ispyisail/test/qet-mcp-read-tool-contracts
Add exact-answer tests for the MCP read tools and qet_diff
2026-09-28 16:13:02 +13:00
ispyisail d8c309afce Merge pull request #1094 from ispyisail/fix/qet-mcp-autonum-not-a-wire
Fix the MCP server counting a wire-numbering rule as a wire
2026-09-28 16:12:37 +13:00
ispyisail b1689fb760 Merge pull request #1093 from ispyisail/fix/qet-mcp-continuity-folio
Fix qet_continuity reporting a missing folio as clean
2026-09-28 16:12:13 +13:00
ispyisail 2529b11d69 Convert examples/schema_indus.qet to the current format; its labels show again
schema_indus.qet was saved by QElectroTech 0.3, where each placed symbol
kept its own values for its definition's old text fields, as
<element><inputs><input text="T1" .../>. Loading those was deliberately
dropped in 1f53c3929 ("Remove retro compatibility of element text item
prior to qet 0.7"), so since 2021 this shipped example has shown none of
its per-symbol texts: 122 texts on 44 of its 48 symbols, including every
device label (Q1, KM1, KM2, T1, M1, S1-S5, H1, H2, the X terminals) and
ratings such as "24VAC" and "F0 am 0,5A".

This commit changes the example file only, not the loader. It was
converted by loading it once with a local build that had 1f53c3929
reverted (and the converted label kept when the symbol's own label was
empty, as that code intended), and saving it. That build is not proposed.

Checked with a build of current master: every one of the 44 symbols shows
exactly the texts its <inputs> held; the 48 symbols (type, position,
rotation), the 69 wires (ends and numbers) and the folio are unchanged; the
two free texts only gain their font written out, as any save does. Like any
save, the file also gains uuids and the current version number.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:01:31 +13:00
ispyisail 503c8c69b5 qet-mcp: qet_edit addresses tables and symbol text fields by uuid
set_table_position and delete_table took a table's index, and
set_element_text / delete_element_text a text field's index. An index
shifts when an earlier item is deleted, so a run that deletes table 0 and
then moves "table 1" moved the wrong table.

Each now also takes the item's uuid, resolved at run time through
qet.tableIndex() and qet.elementTextIndex() (the previous commit), as
texts, shapes and pictures already are through textIndex() and friends. A
text field's lookup is scoped to the op's element, since copies of a symbol
share their fields' uuids. An index still works, and a build without the
lookups is refused with the usual missing-methods hint only when a uuid is
actually used.

Tests: the generated script (no binary), and end to end: delete one table
then move the other, both by uuid; and edit one copy's shared field by uuid,
leaving the other copy's untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:49:36 +13:00
ispyisail 9232775a25 Merge branch 'feat/qet-mcp-diff-uuids' into feat/qet-mcp-items 2026-09-28 13:31:54 +13:00
ispyisail d92a0781b3 qet-mcp: test the text-field uuid guard on each side of a diff
A field with a uuid on a symbol with none cannot be keyed by
(symbol, field); the mutation audit showed either side's guard could be
dropped unnoticed. Now tested with the symbol uuid missing on one side,
then the other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:31:54 +13:00
ispyisail fd962619b5 Scripting: look up a table or a symbol text field by its uuid
qet.textIndex(), shapeIndex() and imageIndex() turn a free text's, shape's
or picture's uuid into the index the other calls take. Tables and symbol
text fields had no such lookup, so a script could only name them by index,
and an index shifts when an earlier item is deleted: a script that deletes
table 0 and then moves "table 1" moves the wrong table.

- qet.tableIndex(folio, uuid): the table's current index in tables(folio),
  or -1.
- qet.elementTextIndex(folio, elementUuid, textUuid): the field's current
  index in elementTexts(folio, elementUuid), or -1. The element is part of
  the address because a field's uuid is unique only within its element:
  copying an element keeps its fields' uuids (2612_ats_singlephase.qet has
  one field uuid on 20 copies).

Tests in misc/qet-mcp's integration suite, which drives these through
--run: a table followed across a deletion of the one before it, and the
same field uuid resolving on two copies to each copy's own field. Both
fail against a build without this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:29:24 +13:00
ispyisail fd8b816475 qet-mcp: qet_items test fixture wraps its table in <tables>, as saved files do
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:20:23 +13:00
ispyisail bbe1903ec8 qet-mcp: read a folio's tables from its <tables> block only (as #1097 does for texts)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:20:22 +13:00
ispyisail 56a23d85dc Merge branch 'feat/qet-mcp-diff-uuids' into feat/qet-mcp-items 2026-09-28 13:20:22 +13:00
ispyisail a89a57bb10 qet-mcp: a symbol's own texts are not free texts on the folio
qet_diff's texts, shapes and pictures sections collected every <input>,
<shape> and <image> under a folio with iter(). Symbols in older files carry
their own <inputs><input> texts, so those were counted as the folio's free
texts: schema_indus.qet folio 1 showed 124 where QElectroTech has 2, and
editing one of those symbol texts would have been reported as a free text
changing.

Only the folio's direct <inputs>, <shapes> and <images> children are read
now. Shapes and pictures have no nested copies in the shipped examples;
they are changed too so the three stay alike. Found by comparing the
tools' answers with QElectroTech's own lists over the shipped examples.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:19:54 +13:00
ispyisail 1de307c13c Merge branch 'feat/qet-mcp-diff-uuids' into feat/qet-mcp-items 2026-09-28 13:15:21 +13:00
ispyisail 279c98f10a qet-mcp: add qet_items, listing drawn items with their uuids
qet_edit addresses free texts, shapes, pictures and tables by uuid, and
qet_diff reports them by uuid, but no tool listed them: the only way to
learn an item's uuid was to read the .qet. qet_items lists every free
text, shape, picture, table and symbol text field per folio (counted from
1, as qet_elements), with its uuid and main fields; filter by folio and
kind; default limit 500.

Also a test that every tool argument holding a data path is in the
workspace policy (_DATA_PATHS). Nothing checked that direction: a new tool
left out of the policy would have read or written anywhere with every test
passing, as removing qet_items' entry showed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:15:21 +13:00
ispyisail 8fd692783c qet-mcp: key symbol text fields by symbol and field uuid; refuse shared uuids
A symbol text field's uuid is unique only within its symbol: copying a
symbol keeps them, so 7 of the 24 shipped examples repeat one, up to 20
times (2612_ats_singlephase.qet). Keyed on the field uuid alone, those
fields merged and an edit to one copy could be reported on another. They
are now keyed on (symbol uuid, field uuid).

More generally, uuids are used as keys only when present on every item and
unique on each side; otherwise the old position/ends matching is kept, for
texts, shapes, pictures, tables, conductors and folios alike. Found by the
seeded-edit invariants (an untouched symbol's field reported changed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:15:12 +13:00
ispyisail f5888e8b56 qet-mcp: qet_diff matches conductors, folios, text fields and tables by uuid
QElectroTech now saves a uuid on conductors (those created since, or
loaded with one), folios, symbol text fields and tables, but qet_diff still
matched them by position or by ends:

- conductors: a rewire read as one wire removed and another added; by
  uuid it is that conductor with changed "ends".
- folios: a reorder read as every later folio changing its fields; by uuid
  it is one "reordered" entry, plus "added"/"removed" folios.
- symbol text fields: deleting the first of two read as the second
  changing; by uuid it is that field removed.
- tables (<graphics_table>) were not compared at all; now a section of
  their own.

Each is matched by uuid only when every item of that kind on both sides
has one; otherwise the old matching is kept, and each section says which
in "keyed_by", since older files and their first re-save mix the two.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:03:06 +13:00
ispyisail e64ce0a6ba qet-mcp: qet_diff reports rotated symbols; equal angles compare equal
Rotating a symbol changes only its orientation attribute (quarter turns),
which qet_diff did not read, so a pure rotation reported no change in any
section. The elements section now has "rotated": each symbol whose
orientation changed, with the value before and after.

Rotating and undoing leaves QElectroTech writing text-field rotations as
"-270" where they were "90" (or "-90" for "270"); compared as strings that
read as a change. Rotations of texts, shapes, pictures and element text
fields are now compared reduced to [0, 360).

Found by a seeded-edit invariant run over the shipped examples: 51
rotations unreported across 20 projects, and 7 undo sequences reported as
changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:55:46 +13:00
ispyisail c1b93a8892 qet-mcp: pin the read tools' and qet_diff's exact answers in tests
A mutation audit -- planting one small bug at a time in the server's read
and diff code (a flipped comparison, a skipped branch, a dropped output
field, a list cap off by one) and running this suite -- caught 78 of 241
planted bugs with the tests that need no QElectroTech binary. qet_elements,
qet_conductors and their row builders caught none: a filter that never
applied, a missing field or a wrong default limit all passed.

Two new classes pin exact output on small hand-made projects:
ReadToolContracts (qet_project_info, qet_elements, qet_conductors: every
field, every filter, limit and truncation, legacy and current conductor
keys) and DiffContracts (every qet_diff section: moves, relabels, info,
conductors and the unstable-key warning, texts/shapes/pictures by uuid and
by position, element text fields, folio and project fields, terminal
strips, and every list cap). The same audit now catches 240 of 241; the
one left is equivalent (rsplit("/", 1) vs rsplit("/", 2) then [-1]).

Tests only; no change to the server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:42:01 +13:00
ispyisail 18e5c30bed qet-mcp: a folio's wire-numbering rule is not a wire
A folio's conductor auto-numbering rule is saved as
<autonum><conductor><part .../></conductor></autonum>. qet_project_info
and qet_conductors counted every <conductor> tag under the folio, so the
rule appeared as a wire with no ends and no number: schema_indus.qet
folio 1 reported 70 conductors where QElectroTech holds 69.

Only children of <conductors> are wires now. A new corpus test compares
each folio's element and conductor counts with QElectroTech's own
elementCount()/conductorCount() after loading the file, over every shipped
example; it failed only on schema_indus.qet before this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:09:58 +13:00
ispyisail a73e5c462d qet-mcp: qet_continuity no longer reports a folio that does not exist as clean
qet.checkContinuity() answers a folio index it has no folio for with an
empty list, so qet_continuity returned "0 findings" for it -- the same
answer as a clean folio. The index counts from 0 while qet_elements numbers
folios from 1, so passing the last folio's number checked nothing and said
so cleanly; any other folio's number checked the next folio instead.

An index with no folio is now refused before QElectroTech is launched, with
the valid range and the counting rule. Each finding also carries
folio_number (counted from 1) beside the existing folio index. The
qet_continuity and qet_conductors descriptions and the README say how each
tool counts. Nothing changes for a valid index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:58:05 +13:00
ispyisail f5b644918b Merge pull request #1090 from ispyisail/fix/qet-mcp-diff-empty-info
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m12s
Fix qet_diff reporting untouched symbols as changed after a re-save
2026-09-28 11:54:13 +13:00
ispyisail 228e696b65 Merge pull request #1091 from ispyisail/fix/qet-mcp-edit-folio-hint
Fix qet_edit giving no clue when a folio number is off by one
2026-09-28 11:53:42 +13:00
ispyisail 9b09469a53 Adapt only element icons in the shortcut bar editor, not its commands
The editor's bar row and command list mix element previews with command
icons. The command icons already follow the palette through the qet-dark
icon theme, and running them through ElementPreviewDelegate again
flattened them to one lightness (up to 16/255 per pixel off master; a
two-tone icon would have had its tones swapped).

Adapt the element icons once, where the items are built, instead of
installing the delegate on those lists. This also covers an element
dragged from the element list onto the bar, which copies the item's icon
and so previously kept the dark one until the editor was reopened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:53:13 +13:00
ispyisail c7f4a4f01d Show element icons light on a dark palette in search results and the picker
The collection tree draws element previews through ElementPreviewDelegate,
which inverts the black line art on a dark palette. The ranked search list
(#1051) and the Insert element picker (#1052) are separate views and never
installed it, so their icons stayed black on a dark background.

Install the delegate on the ranked list, the picker's list and the shortcut
bar editor's lists, and adapt the pinned-element buttons' icons directly.
Coloured icons and light palettes are unchanged.

Reported in #1083.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:30:08 +13:00
ispyisail 5da0b92001 qet-mcp: say which folio index to use when qet_edit is given the wrong one
qet_elements and qet_project_info number folios from 1, as the application
does; qet_edit passes "folio" straight to the scripting API, which counts
from 0. Using the number qet_elements showed addresses the next folio, and
the op fails with nothing but "returned False".

Nothing changes for a call that works. When an op fails and the element it
names is in the project on another folio, the hint now says which index to
use. The qet_edit description and the README say how folios are counted.
Counting from 1 instead was not done: it would silently move every existing
caller's edits to another folio.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:20:04 +13:00
ispyisail 40ed284b7a qet-mcp: qet_diff no longer reports untouched elements as changed
QElectroTech writes an empty label as an empty <elementInformation> and
drops it on the next save. qet_diff compared the raw information bags, so
re-saving grafcet.qet with no edit reported 9 of its 37 elements as
info_changed ({"label": ""} -> {}), and Projet_vierge.qet 4.

An empty field and a missing one now compare equal, and info_changed lists
only fields that hold a value. The resave test now also requires an empty
info_changed; it failed on grafcet.qet and Projet_vierge.qet before this
change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:12:25 +13:00
ispyisail 91804d5f5e Align: a group reduced to one shape lines up on its own centre
A group whose only unlocked member is a shape (its other members
locked) kept a centre of (0,0), so Centrer horizontalement and
Centrer verticalement pulled every item toward the folio origin and
the shape itself landed off the line. The rule that turns a group's
members into one item now lives in Alignment::combined(), where every
member, shapes included, brings its own centre, and tst_alignment
covers it.

Also comments why unitFor()'s returned reference is safe, as asked in
the review of #1087.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-28 09:19:59 +13:00
Laurent Trinques 37674d836f Merge pull request #1087 from ispyisail/feat/1069-align
Add Align left/centre/right/top/middle/bottom for the selection
2026-09-27 21:52:56 +02:00
Laurent Trinques 5cb97f1c89 Merge pull request #1085 from ispyisail/fix/1080-parseoption-qt65
Fix the build on Qt older than 6.5 (#1080)
2026-09-27 21:46:11 +02:00
Laurent Trinques 40f0235c71 Merge pull request #1088 from Kellermorph/materiallist
Add a material list lookup for the element article fields
2026-09-27 21:43:52 +02:00
Laurent Trinques b2f9ed952d Merge pull request #1086 from ispyisail/fix/nokde-kcolorbutton-include
Fix the build without KDE Frameworks (conductor properties panel)
2026-09-27 21:33:10 +02:00
Kellermorph d5ae15f568 Add a material list lookup for the element article fields
The element description dialog only offered typing, so every article of
a catalogue had to be copied in by hand into the Artikelbeschreibung
row of each block, over and over.

Settings > General now keeps the path of a CSV material list (Créer...
writes a template with the header lines), and the Artikelbeschreibung
row of every block of the Bauteil dialog shows a "..." button opening a
searchable listing of that file. "Appliquer" fills the fields of that
block only -- the main block gets everything but label and formula, an
auxiliary block gets description, number, manufacturer, references,
supplier, quantity, unit and additional info -- and a cell that is
empty in the file never erases a value already in the element. The
symbol editor is left alone.

- sources/materiallist/: CSV reading and writing (UTF-8 BOM, ';', every
  field quoted, atomic write through QSaveFile), two header lines
  (translated labels then the canonical English keys), the selection
  window and the new entry form.
- The selection window reads the file again every time it opens, keeps
  its size, column widths, column order and sort between openings,
  shows every column of the file, scrolls all four directions with the
  wheel (Shift + wheel moves the columns) and puts back the order of
  the file when the corner above the row numbers or a row number is
  clicked.
- German translations for the new strings in lang/qet_de.ts.
2026-09-27 21:18:54 +02:00
ispyisail ad88505f87 Fix the build without KDE Frameworks (conductor properties panel)
conductorpropertieseditorwidget.cpp, from the #500 work, included
<KColorButton> unconditionally, so a build with BUILD_WITH_KF=OFF stopped
at "KColorButton: No such file or directory". Include the nokde stand-in
there instead, as dynamictextfieldeditor.h and terminaleditor.h do. It
has the same changed() signal the file connects to.

Checked on Ubuntu 22.04 (Qt 6.2.4, BUILD_WITH_KF=OFF): the file fails on
master and compiles with this change; together with #1085 the whole tree
builds and links. Builds with KDE Frameworks compile the same line as
before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-28 07:59:41 +13:00
ispyisail c29d4aff24 Fix the build on Qt older than 6.5 (#1080)
QDomDocument::ParseOption::PreserveSpacingOnlyNodes, used since 84add7b3e
to keep a title-block variable set to a single space through a reload,
only exists from Qt 6.5. On older Qt the project file is parsed as it was
before that commit: the build works again, and such a value reloads as
empty there.

Checked on Ubuntu 22.04's Qt 6.2.4: qetproject.cpp fails on master with
"'QDomDocument::ParseOption' has not been declared" and compiles with
this change. On Qt 6.5 and later the code is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-28 07:57:21 +13:00
ispyisail 3c4d23bb4e Add Align left/centre/right/top/middle/bottom for the selected items
Edit > Aligner gains six commands, also in the selection's context menu
and the command search: Aligner à gauche, Centrer horizontalement,
Aligner à droite, Aligner en haut, Centrer verticalement, Aligner en bas.
One undo step; wires follow their symbols. Second stage of discussion
#1069, on top of "Aligner sur la grille".

Left/right/top/bottom line the edges up on the outermost one. The two
centre commands line the items up on the mean of their centres, and a
symbol's centre is its origin point, not the middle of its drawing: in
the collection, vertical two-terminal symbols almost always have their
terminals on the origin's axis, so this puts their wires on one line.
A picture is aligned by the picture itself, without its caption
(imageRect() becomes public for this).

A group (#1070) lines up as one piece: its edges are its members'
together, and every member moves by the same amount, so the group keeps
its shape; shapes inside a group come along.

Each item moves only across the line it is aligned on, and lands on the
grid its drag uses, so aligning never takes a symbol off the grid. Two
symbols whose edges sit at different distances from their origins
cannot both be exactly on the line and on the grid; they end up within
half a grid step of it.

The commands need two items (Aligner sur la grille still needs one).
Locked items stay put and the status bar says so. If nothing moves, no
undo step is pushed and the status bar says the selection is already
aligned as far as the grid allows. The geometry is in alignment.h,
tested by tst_alignment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 07:52:17 +13:00
Laurent Trinques 1331ed6677 Merge pull request #1079 from ispyisail/feat/1071-dxf-layers
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m1s
Put the DXF export on named layers
2026-09-27 20:25:43 +02:00
ispyisail ae400d4da6 Merge master into feat/1071-dxf-layers 2026-09-28 07:22:42 +13:00
Laurent Trinques 7fdd4417a4 Merge pull request #1074 from ispyisail/feat/1070-item-groups
Add Group and Ungroup for items on a folio
2026-09-27 20:15:37 +02:00
Laurent Trinques 6637127910 Merge branch 'master' into feat/1070-item-groups 2026-09-27 20:15:27 +02:00
Laurent Trinques 38d7ecd67a Merge pull request #1084 from ispyisail/fix/1082-text-rotation-dialog
Fix #1082: text angle dialog opens at 0, unclear shortcut-bar tooltip
2026-09-27 20:10:13 +02:00
Laurent Trinques 27b60ec30e Merge pull request #1077 from ispyisail/fix/nomenclature-old-type-names
Fix old parts lists losing rows and tables when a project opens
2026-09-27 19:55:55 +02:00
Laurent Trinques 4bd5a6e5ac Merge pull request #1078 from ispyisail/feat/1072-export-dxf-cli
Add DXF export from the command line (--export-dxf)
2026-09-27 19:53:23 +02:00
Laurent Trinques 58cf62c782 Merge pull request #1083 from ispyisail/feature/search-tree-option
Add a setting to show collection search results as a tree
2026-09-27 19:47:54 +02:00
ispyisail 83bb7e14e0 Add the status tip to the S shortcut bar's tooltips too
The previous commit changed only the bar that appears beside the
cursor after a click. The S shortcut bar builds its own tooltips, so
its "Orienter les textes" button still said only that. Its tooltips
now carry the status tip on a second line as well, after the keyboard
shortcut.

Checked in the GUI: the S bar's second button shows "Orienter les
textes (Ctrl+Space)" over "Pivote les textes sélectionnés à un angle
précis".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 06:23:33 +13:00
ispyisail 239065af7d Open the text orientation dialog at the texts' current angle
The "Orienter les textes" dialog always opened at 0, so turning a text
at 90 degrees by a little meant typing the angle in again. It now opens
at the angle every selected text and text group shares, and at 0 as
before when they differ.

The shortcut bar showed only a command's short name as its tooltip,
which for this one ("Choose texts orientation") does not say what it
does. It now adds the command's status tip on a second line: "Rotate
selected texts to a specific angle".

Checked in the GUI on a folio holding one text at 90 degrees: master
opens the dialog at 0.00, this at 90.00.

Fixes #1082

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 06:16:17 +13:00
ispyisail adcf11270c Upgrade old element type names in saved query presets too
A query saved to nomenclature.json (Load/Save in the query editor, since
2020) before June 2022 still says element_type = 'Simple'. Loading it
ticked none of the type boxes and kept the old query, so the new table
came out empty. Pass it through LegacyElementTypes::upgradeQuery() as
ProjectDBModel::fromXml() now does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 05:55:20 +13:00
ispyisail 539708da3c Merge remote-tracking branch 'upstream/master' into fix/nomenclature-old-type-names
# Conflicts:
#	tests/qttest/CMakeLists.txt
2026-09-28 05:52:31 +13:00
ispyisail f62a44827b Add a setting to show collection search results as a tree
#1051 replaced the filtered tree with a ranked flat list. A new checkbox
in Settings > General, "Afficher les résultats de recherche sous forme de
liste triée" (elementscollection/search-flat-list, default on), keeps the
ranked list; unticked restores the pre-#1051 filtered tree search.

The Insert picker and shortcut bar call rankedSearch() directly and are
unaffected. Down/Enter from the search field only apply to the flat list.

Requested by scorpio810 on #1051.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-28 05:43:11 +13:00
ispyisail b350a2dbaa Merge branch 'master' into feat/1072-export-dxf-cli
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 05:42:44 +13:00
ispyisail e1152ba570 Merge master to resolve conflicts with snap-to-grid (#1073)
Both branches added an undo command, an include and a test target at
the same spots; kept both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 05:40:05 +13:00
ispyisail 8184ee1a18 Merge branch 'master' into feat/1071-dxf-layers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 05:38:31 +13:00
Laurent Trinques d56e99702c Update EN translations files 2026-09-27 16:51:51 +02:00
Laurent Trinques 08325061fd Flatpak add Hidapi support for 3DMouse 2026-09-27 13:17:37 +02:00
Laurent Trinques e9610d3f96 Update translations files 2026-09-27 12:47:18 +02:00
Laurent Trinques d5027eadbb Merge pull request #1076 from ispyisail/fix/dxf-titleblock-linebreak
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m25s
Fix title blocks with a line break making the DXF unreadable
2026-09-27 12:03:02 +02:00
Laurent Trinques da09206826 Merge pull request #1075 from ispyisail/fix/dxf-entity-order
Fix DXF export writing entities in a different order every time
2026-09-27 12:00:33 +02:00
Laurent Trinques 9812166071 Merge pull request #1073 from ispyisail/feat/1069-snap-to-grid
Add "Snap to grid" for selected symbols, pictures and texts
2026-09-27 11:55:49 +02:00
ispyisail a399923575 DXF export: keep the wires' colours
Every wire was written in the default colour, whatever its colour on the
folio: 723 of the 3,190 wires in the example projects (23 %, in 10
projects) lost theirs. The wire outline now takes the wire's colour, and
its number the wire's text colour, mapped to the nearest DXF colour as
the free texts already are. Black still comes out as BYLAYER.

A two-colour wire gets its main colour, and dashed wires (46 in the
examples) stay continuous: DXF line types are not written yet.

Checked on the 24 example projects, 133 folios: exactly 723 wire
outlines now carry a colour of their own, the only change from the
previous commit; ezdxf reads all files with no audit errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 21:54:44 +13:00
ispyisail 1d980b164f DXF export: colour BYLAYER where QElectroTech sets no colour (#1071)
Most entities were written with colour 0, BYBLOCK. Outside a block that
falls back to the default colour, so it shows black on white or white on
black, and no layer colour could change it: recolouring QET_WIRES in a
CAD program left the wires as they were. Black itself maps to 0 too
(RGBcodeTable[0]).

Colour 0 is now written as 256, BYLAYER. The layers are colour 7, so a
file looks the same on opening, and recolouring a layer now recolours
its contents. Real colours (free texts, terminal markers) are kept.

Checked on the 24 example projects, 133 folios: the only change from
the previous commit is 110,283 colour codes 0 -> 256, no entity is left
on 0, and ezdxf reads all of them with no audit errors. Rendered with
QET_WIRES set to red and QET_SYMBOLS to blue: before, 0 red and 0 blue
pixels; after, the wires and symbols take the layer colours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 21:54:44 +13:00
ispyisail 20a3e4f636 Put the DXF export on named layers (#1071)
Everything in an exported DXF was on layer 0, and the file had no LAYER
table: in a CAD program the border, title block, symbols, wires and texts
could not be hidden, printed or recoloured separately. Each kind of
content now has its own layer, declared in a LAYER table (white/black,
continuous, so the file looks the same on opening):

  QET_BORDER, QET_TITLEBLOCK, QET_SYMBOLS, QET_SYMBOL_TEXTS,
  QET_TERMINALS, QET_WIRES, QET_WIRE_NUMBERS, QET_JUNCTIONS, QET_TEXTS,
  QET_XREFS, QET_SHAPES, QET_TABLES, QET_IMAGES

Fixed, untranslated names, so layer filters and scripts work in any
language. Createdxf gets a current layer beside its xScale/yScale;
DxfExport sets it before each kind of content, and BorderTitleBlock
switches to the title block layer itself. No DXF version change: layers
exist in R10.

Checked on the 24 example projects, 133 folios: with the layer values
set back to 0 and the LAYER table removed, every file is byte-identical
to before; no entity is on an undeclared layer or left on 0; ezdxf reads
all of them with no audit errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 21:54:44 +13:00
ispyisail 4278ef864e Add DXF export from the command line (--export-dxf)
qelectrotech --export-dxf <project.qet> <output_dir> [--show-terminals]
writes one DXF per folio, named <NN>_<title>.dxf like the PNG and SVG
exports. Discussion #1072.

The DXF code moves out of ExportDialog into DxfExport, unchanged except
that its options come from an ExportProperties argument instead of the
dialog. The dialog and the command line both call it, and the command
line uses the dialog's default options (the preferences' export
settings), so both write the same entities.

- Createdxf answers a file it cannot open with a message box and
  exit(0); the command line checks the file first and fails with a
  message and exit code 1 instead.
- A note is printed when pictures become outline boxes, as the dialog
  warns.
- Scripting: qet.exportDxf(outDir, showTerminals). MCP: format "dxf".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 21:54:36 +13:00
ispyisail f98d5d00e9 Merge branch 'fix/dxf-titleblock-linebreak' (#1076) into feat/1072-export-dxf-cli 2026-09-27 21:54:36 +13:00
ispyisail 166ee34f52 Fix parts lists saved before June 2022 losing rows and tables on open
A nomenclature table saved by an older version filters on the element
type names the project database used then: element_type = 'Simple',
'Terminale', 'Master'. Commit 2e70d2e59 (June 2022) changed the database
to "simple", "terminal", "master", and SQLite compares text
case-sensitively, so such a table silently lost every row of that type
on open. Its continuation tables were then empty, and
removeUselessNextTable() deleted them: opening and saving the example
industrial.qet removed seven of its ten parts-list tables (folios 44-50),
and the remaining three listed 76 of 258 parts.

ProjectDBModel::fromXml() now rewrites old names in element_type = '...'
comparisons to the current ones (LegacyElementTypes::upgradeQuery()),
which also lets the query editor tick the right boxes again. A query
saved by a current version is unchanged, and so is any other text that
happens to contain "Simple".

Checked in the GUI on industrial.qet, open then save: master keeps
tables on 3 of folios 41-50, this keeps all 10, with 258 rows (the last
table 24 of 26) and the query saved as 'simple'. tst_legacyelementtypes
fails when a name maps wrongly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 21:46:30 +13:00
ispyisail 4efe28ce03 Fix title blocks with a line break writing an unreadable DXF
A title block cell holding a line break (tableau_domestique.qet's
"DESSINE" + CR LF + "%{author}") was written into the DXF as one TEXT
value with the break inside it. A DXF value ends at the line break, so
every group code after it was off by one line: ezdxf refuses all five
folios of that example with DXFStructureError, and other readers take
values for codes.

- TitleBlockTemplate::renderTextCellDxf() writes each line of a cell as
  its own TEXT, stacked by the cell's vertical alignment as the screen
  shows them, 1.6 text heights apart as the diagram texts' DXF export
  does. A single-line cell is written exactly as before.
- Createdxf's text writers turn any line break still in a value into a
  space, so no other caller can break the file either.

Checked in the Export dialog: tableau_domestique.qet's five folios, which
ezdxf refused on master, now read with no audit errors; grafcet.qet, which
has no line break, keeps exactly the same entities. (Byte comparison waits
on the DXF entity-order fix: until then every export reorders its entities.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 21:33:00 +13:00
ispyisail 7c4355338b Fix DXF export writing entities in a different order every time
Exporting the same folio to DXF twice gave files with the same entities
in a different order, so two exports never compared equal and a DXF could
not be diffed to spot a change. The export walked diagram->items(), whose
order follows memory addresses; saving had the same fault until
bugtracker #343.

Items are now taken in stacking order (z, then insertion order, which a
load makes the file's order), from the same rect query Diagram::toXml()
uses. Anything the query misses keeps its items() place after the rest.

Checked in the Export dialog on grafcet.qet, three folios, two exports
per build: master's differ every time, these are byte-identical, and both
contain exactly the same entities, only reordered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 21:28:36 +13:00
ispyisail f7591e0c9d Add grouping of folio items: Group and Ungroup (#1070)
Symbols, free texts, shapes and pictures can be grouped from the Edit
menu, the selection's context menu or the command search. Clicking one
member selects the group, so moving, copying and deleting act on all of
it; Ctrl+click on a member deselects the group; a rubber band touching
part of a group selects all of it when released. No default shortcut:
Ctrl+G is "jump to element".

A group is not an object in the scene. Each member keeps its place and
carries the group's uuid (QGraphicsItem::data()), saved as a "group"
attribute written only when set: a project without groups saves exactly
as before, and older versions open a grouped one and ignore the groups.
Re-parenting under a QGraphicsItemGroup would have made every member's
position group-relative; ElementTextItemGroup already needs nine special
cases for that.

- Selection is completed on clicks and at the end of a rubber band, not
  on every selectionChanged(): export, search and Tab select items
  themselves and must not have groups pulled back in.
- Project database: group_uuid on element, shape, independent_text and
  image, kept in step by projectDataBase::itemGroupChanged().
- Paste and folio duplication give each source group one new uuid.
- Undo of Ungroup restores each item's exact group.

Builds on #1065 (uuids and database rows for texts, shapes and images).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 21:14:47 +13:00
ispyisail 4c3e86c07e Add "Snap to grid" for the selected symbols, pictures and texts
Edit > Aligner > Aligner sur la grille, also in the selection's
context menu and the command search, puts each selected item back where
dragging it would have left it: symbols and pictures on the folio grid,
free texts on the text grid. One undo step; wires follow their symbols.

Symbols leave the grid through the fine nudge (Alt+arrow, 1 px), and
nothing put them back: 508 of the 3,678 symbols in the example projects
are off the 10 px grid. First stage of discussion #1069.

The status bar says how many items moved, or that the selection was
already on the grid, and how many locked items were left in place.
Shapes are left out: they are made of several points and no single one
is the obvious one to snap.

The snap never reads the keyboard, unlike Diagram::snapToGrid(), so a
user who binds the action to a shortcut with Ctrl gets the grid, not
pixel rounding. The geometry is in alignment.h, tested by tst_alignment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 21:00:18 +13:00
Laurent Trinques 44cabbcfe4 Merge pull request #1065 from ispyisail/feat/uuid-shapes-texts-images
Add a permanent id to shapes, texts, pictures and symbol parts
2026-09-27 09:55:25 +02:00
ispyisail a2a30ef7c4 Merge branch 'master' into feat/uuid-shapes-texts-images
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:51:23 +13:00
Laurent Trinques 0b4f480dfe Merge pull request #502 from ispyisail/proto-conductor-selection-properties
Add wire properties to the Selection properties panel, as an option (#500)
2026-09-27 09:11:22 +02:00
Laurent Trinques 05260de87b Merge pull request #1060 from ispyisail/feature/shortcut-bar-symbols
Add pinned elements to the shortcut bar, with a width you can set
2026-09-27 09:09:44 +02:00
Laurent Trinques db154a8d06 Merge pull request #1059 from ispyisail/fix/ctrl-shift-pan-stuck
Fix the folio staying in pan mode after Ctrl+Shift+M
2026-09-27 09:08:02 +02:00
Laurent Trinques 7dc1d345b0 Merge pull request #1057 from ispyisail/feature/mouse-gestures
Add right-drag mouse gestures to run commands
2026-09-27 09:02:44 +02:00
Laurent Trinques ba1811a0cf Merge pull request #1056 from ispyisail/feature/repeat-command
Add repeating the last drawing or placing command with Enter
2026-09-27 09:02:02 +02:00
Laurent Trinques a3dc8d3ad4 Merge pull request #1055 from ispyisail/feature/context-toolbar
Add a command bar beside the selection after a click
2026-09-27 09:00:29 +02:00
Laurent Trinques b1fe1ef7a7 Merge pull request #1054 from ispyisail/feature/shortcut-bar-customise
Add customising the shortcut bar in place
2026-09-27 08:59:55 +02:00
Laurent Trinques 9584fc4b7c Merge pull request #1053 from ispyisail/feature/shortcut-bar
Add a shortcut bar that opens at the cursor with S
2026-09-27 08:59:32 +02:00
Laurent Trinques 25baf48153 Merge pull request #1052 from ispyisail/feature/element-picker
Add an element picker that opens at the cursor with Insert
2026-09-27 08:58:50 +02:00
Laurent Trinques 7f51a3e10a Merge pull request #1051 from ispyisail/feature/ranked-search
Show collection search results as a ranked list
2026-09-27 08:47:30 +02:00
Laurent Trinques 503823bfce Merge pull request #1050 from ispyisail/feature/context-menu-placement
Add placing the last element and folio references to the folio menu
2026-09-27 08:47:00 +02:00
Laurent Trinques 30c97612a2 Merge pull request #1042 from ispyisail/feature/place-without-drag
Add double-click placement from the collection, and A to repeat it
2026-09-27 08:40:42 +02:00
Laurent Trinques 8cdf51405a Merge pull request #1066 from ispyisail/fix/readonly-query-all-rows
Fix table and script queries returning only their first row
2026-09-27 08:06:08 +02:00
Laurent Trinques 4a5755e552 Merge pull request #1068 from ispyisail/feat/349-image-label
Add a label to pictures (#349)
2026-09-27 08:04:44 +02:00
Laurent Trinques ef8b3866ba Merge pull request #1067 from ispyisail/perf/image-save-png-cache
Speed up saving projects that contain pictures
2026-09-27 08:04:29 +02:00
ispyisail f6f53e3f52 Stop re-encoding unchanged pictures on every save
DiagramImageItem::toXml() PNG-encoded every picture on every save,
autosave and copy, whether or not it had changed. That is most of the
cost of pictures in a project: resaving one holding 60 of them took
6.2 s and now takes 3.8 s.

The PNG bytes are now kept and reused while QPixmap::cacheKey() still
matches, so any edit (replace, crop, mirror, transparency) re-encodes
without each of those functions having to invalidate anything. On load
the cache is filled with the file's own bytes, so the first save
encodes nothing either.

Output is byte-identical to before on the example projects. A picture
whose PNG came from another encoder now keeps its original bytes
instead of being re-encoded; the pixels are identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 16:43:11 +13:00
ispyisail 5120feb417 Add an optional label to pictures (#349)
A picture can now carry a caption, set from its properties panel
("Libellé"). It is drawn centred under the picture at the folio's
normal text size whatever the picture's scale, turns with it, and
moves, copies and prints with it because the picture itself paints it.
Clicking the caption selects the picture.

Saved as a "label" attribute on <image>, written only when non-empty:
a project without labels saves byte-for-byte as before, and older
versions open a labelled project and simply ignore the caption.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 16:12:51 +13:00
ispyisail cd38f6f2a2 qet-mcp: test the uuids given to drawn items in older files
A file written before drawn items carried a uuid gets them from the folio
uuid, the kind of item and its order in the file. Three integration tests
pin what that promises: opening the same file twice gives the same uuids,
the first save writes exactly those and a reopen keeps them, and a uuid
repeated in a hand-edited file ends up naming one item only.

Each fails when the derived uuid is replaced by a random one. They read
drawing_item_view unsorted, which is what found the first-row-only bug
in QETSql::execReadOnly() fixed by the previous commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 14:00:16 +13:00
ispyisail 29fe731728 Fix read-only queries stopping after the first row
Since #1046, QETSql::execReadOnly() runs a query with PRAGMA query_only
set and switches it off before returning. Switching it off aborts a
statement SQLite is still stepping through ("abort due to ROLLBACK"), and
QSQLITE has already stepped to the first row by then. A query that
produces its rows as it goes -- a UNION ALL without ORDER BY -- therefore
came back with its first row only and no error. A sorted query was not
affected, because SQLite has read every row before returning the first.

Every query from the SQL box of a table, a saved <graphics_table> query
and the scripting qet.query() goes through here.

The checked run is now finished before query_only is switched off, and a
query that passed is run again for the caller. SQLite refuses a write at
its first step, so passing that step is what proves a statement reads
only; the second run is of a statement already shown to be read-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 14:00:16 +13:00
ispyisail b740508b7f Fix read-only queries stopping after the first row
Since #1046, QETSql::execReadOnly() runs a query with PRAGMA query_only
set and switches it off before returning. Switching it off aborts a
statement SQLite is still stepping through ("abort due to ROLLBACK"), and
QSQLITE has already stepped to the first row by then. A query that
produces its rows as it goes -- a UNION ALL without ORDER BY -- therefore
came back with its first row only and no error. A sorted query was not
affected, because SQLite has read every row before returning the first.

Every query from the SQL box of a table, a saved <graphics_table> query
and the scripting qet.query() goes through here.

The checked run is now finished before query_only is switched off, and a
query that passed is run again for the caller. SQLite refuses a write at
its first step, so passing that step is what proves a statement reads
only; the second run is of a statement already shown to be read-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 14:00:07 +13:00
ispyisail 76eea09192 qet-mcp: address texts, shapes and images by uuid; part uuids in symbols
- qet_edit: every op taking a text, shape or image "index" also takes its
  uuid, resolved at run time with textIndex()/shapeIndex()/imageIndex().
  Those are only required when a uuid is used.
- qet_element_build writes a uuid on every part (the caller's, or a new
  one) and returns them in part_uuids; qet_element_info lists part and
  terminal uuids.
- README and tests: drawing_item_view, uuid addressing, part uuids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 11:16:37 +13:00
ispyisail 30e83d8c52 Element editor: give every primitive a uuid
Lines, rectangles, ellipses, arcs, polygons, PLC tables and static texts
now save a uuid, as terminals and dynamic texts already did, so a tool can
name one part of a symbol. The graphic parts keep it next to their styles,
since all of them save through stylesToXml(); a part read from a
definition that predates this gets one on the next save.

Paste renews them, as it already did for terminals -- and now for dynamic
texts too, which were pasted with their source's uuid. A placed element
gives its dynamic texts fresh uuids anyway, so only the definition's own
identity changes. PartTerminal::uuid() returns the terminal's saved uuid
rather than inheriting the graphic-part one it never writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 11:16:37 +13:00
ispyisail 3a50cd9b0e Project database: tables for shapes, independent texts and images
Now that they carry a uuid, the folio's drawing furniture gets rows of its
own: shape, independent_text and image, plus drawing_item_view, which finds
any of them by uuid without knowing its kind first and says which folio it
is on.

Rows follow edits, not just loads. The script API's query() and every
other reader go through newQuery() without a rebuild, so an item's row is
queued on each change (moves, restyles, text edits, uuid renewal) and the
queue is flushed by newQuery() and updateDB() -- a queued write is a set
insertion, which matters for a drag that moves hundreds of items per mouse
step.

A pasted copy joins its folio still carrying its source's uuid and is only
renewed afterwards, so a flush in between must not let the copy overwrite
its source's row. Each row remembers the item that wrote it; another item
with the same uuid waits until uuidChanged() says it has its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 11:16:37 +13:00
ispyisail acb770909e DiagramPosition: compile the letter filter once
The constructor built QRegularExpression("[^A-Z]") on every call, and
compiling it cost more than the rest of Diagram::convertPosition() put
together -- about 95k instructions a call under callgrind. Every element
row of the project database pays it on each rebuild, as does anything
else that turns a scene point into a folio cell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 11:16:37 +13:00
ispyisail a9ad6e2f34 Give shapes, independent texts and images a persisted uuid
Elements, conductors, terminals and tables already carry a uuid. The
drawing furniture beside them did not, so a script or the MCP server could
only name a line, a box or a note by its index in a position-sorted list,
which shifts whenever one is added or removed.

- QetShapeItem, IndependentTextItem and DiagramImageItem get uuid(),
  newUuid() and setUuid(), read from and written to a "uuid" attribute.
- A folio loaded from a file written before this (or carrying a duplicate
  uuid) derives one from the folio uuid, the item kind and its order in the
  file, so the same file gives the same uuids on every load and a re-save
  is stable -- the #754 lesson for conductors.
- Paste and folio duplication renew them, as they already do for elements
  and conductors.
- Scripting: texts(), shapes() and images() end each line with the uuid;
  textIndex(), shapeIndex() and imageIndex() turn one back into an index.
- misc/qet-mcp: qet_diff keys texts, shapes and images on uuid when both
  sides have one, so a move or edit reads as a change to that item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:54:54 +13:00
ispyisail ff795258c4 Conductor dock: turn it on in the preferences, off by default (#500)
Showing a selected conductor's properties in the selection-properties
dock is now a preference, on the General page under Appearance:
"Afficher les propriétés d'un conducteur sélectionné dans le panneau
Propriétés de la sélection". It is off by default, so selecting a
conductor does what it did before unless the user turns it on.

This replaces the View-menu toggle, which defaulted to on; that entry
is dropped in the merge with master. Same setting key
(diagrameditor/conductor_properties_panel).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:13:55 +13:00
ispyisail 0cb424bd06 Merge remote-tracking branch 'upstream/master' into proto-conductor-selection-properties
# Conflicts:
#	cmake/qet_compilation_vars.cmake
#	lang/qet_en.ts
#	sources/qetdiagrameditor.cpp
2026-09-27 09:07:41 +13:00
ispyisail 47e5a8072b Shortcut bar: reopen at the cursor, wrap to a width the user sets
After customising, the bar now opens at the cursor like every other
time, instead of where the customising window was.

A size grip in the bar's corner sets its width. The tiles are laid out
in rows that wrap at that width, so a bar with many commands and pinned
elements becomes a block rather than one long strip; the height follows
the rows, and the width is saved. Until the user sets one, the tiles
stay on one row, as before. The grip is driven by hand: QSizeGrip asks
the window manager to resize, and on X11 a popup is not managed, so
nothing would happen. The hint line wraps rather than being cut off at
narrow widths.

The customising window remembers the size it was left at.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 03:16:56 +13:00
ispyisail 5bd55801e9 Pin elements to the shortcut bar
The bar's customising window gets an element search next to the command
list. Type part of a name, then drag a hit onto the bar, double-click
it, or press Enter to pin the best one. A pinned element shows as its
icon among the commands, and clicking it places the element, as the
picker does.

Pinned elements are saved in the same list as the commands, by
collection path (common://, custom://, company://), so the row keeps
the user's order. Elements embedded in a project are not offered: their
path names the project as loaded now. Elements are offered for the
empty-folio bar only; with something selected the bar is for acting on
it. Once any element is pinned there, the palette folder grid under the
bar is hidden, and comes back while typing a search.

Dragging a pinned element off the bar onto the commands, or a double
click, removes it. The Preferences page shows pinned elements by name
and icon, and removing one there drops it instead of listing it as a
command. The customising window is now kept on screen, since it is
taller than before.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 03:16:56 +13:00
ispyisail 5ccb089cdd Merge repeat-command into mouse-gestures (search results drag fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 03:00:26 +13:00
ispyisail 416ababc41 Merge context-toolbar into repeat-command (search results drag fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:59:59 +12:00
ispyisail 63771a41b5 Merge shortcut-bar-customise into context-toolbar (search results drag fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:59:59 +12:00
ispyisail 57905a5789 Merge shortcut-bar into shortcut-bar-customise (search results drag fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:59:59 +12:00
ispyisail 451cccd615 Merge element-picker into shortcut-bar (search results drag fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:59:59 +12:00
ispyisail af28a49b72 Merge ranked-search into element-picker (search results drag fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:59:59 +12:00
ispyisail 926bbcf75f Let search results be dragged onto a folio like the tree
While a collection search is active the tree is replaced by the flat
ranked list, and that list had no drag support: dragging a result only
moved the selection. Anyone used to searching and then dragging lost the
drag as soon as they typed.

The tree's drag is moved into a static ElementsTreeView::execElementDrag()
taking the source widget, and the results list uses it from the path each
row already carries, so the drag content and pixmap are the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:56:46 +12:00
ispyisail 6bcdb87524 Fix the folio staying in pan mode after a Ctrl+Shift shortcut
Holding Ctrl+Shift over a folio pans it, and only a key release seen by
the view ends that. A Ctrl+Shift shortcut that opens a window -- the
command search, Ctrl+Shift+M -- takes the keyboard before Ctrl and Shift
are released, so the view never sees the release. On Windows, where the
Shift press itself already reports Ctrl+Shift, the folio then stayed in
pan mode after a command was chosen: a hand cursor, and clicks ignored,
so a drawing tool picked from the search did nothing. On Linux the Enter
key's release happened to reach the view and end it.

The view now remembers that it pans because of Ctrl+Shift (not because
the hand tool was chosen) and stops when it loses the focus, or at a
click made without Ctrl+Shift, instead of waiting for a release it may
never get.

Reproduced on Linux by holding back Enter's release, as Windows does:
the line drawn after "Ctrl+Shift+M, une ligne, Enter" is not saved on
master and is with this change. Ctrl+Shift+drag and the hand tool still
pan (a dragged step does not move), as on master.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 01:35:56 +12:00
ispyisail f850d0bee8 Keep mouse gestures working while a tool is running
Most commands on the empty-folio ring start a drawing tool, and the view
left the right button alone while a tool ran, so the gesture after one
that started a tool only cancelled the tool: every other gesture opened
the context menu instead of the ring.

Now a right drag always shows the ring. A running tool is ended when the
drag starts, as picking another tool from the toolbar would. A plain
right click still goes to the tool, which cancels or finishes it as
before.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 01:34:55 +12:00
Laurent Trinques d7036ad807 Merge pull request #1046 from ispyisail/fix/1045-sqlite-handle
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m15s
Fix #1045: crash on clicking an element with online-installer Qt
2026-09-26 14:20:43 +02:00
Laurent Trinques 09fb81b99a Merge branch 'master' into fix/1045-sqlite-handle 2026-09-26 14:07:32 +02:00
ispyisail 501379def2 Merge repeat-command (with master) into mouse-gestures
# Conflicts:
#	cmake/qet_compilation_vars.cmake
2026-09-26 22:44:21 +12:00
ispyisail 52a831e1fa Merge context-toolbar (with master) into repeat-command
# Conflicts:
#	sources/qetdiagrameditor.cpp
2026-09-26 22:44:11 +12:00
ispyisail c1bb3b33f4 Merge shortcut-bar-customise (with master) into context-toolbar
# Conflicts:
#	cmake/qet_compilation_vars.cmake
2026-09-26 22:44:01 +12:00
ispyisail f91ec74dde Merge shortcut-bar (with master) into shortcut-bar-customise 2026-09-26 22:43:50 +12:00
ispyisail b08d0692b7 Merge element-picker (with master) into shortcut-bar
# Conflicts:
#	cmake/qet_compilation_vars.cmake
#	sources/qetdiagrameditor.cpp
2026-09-26 22:43:40 +12:00
ispyisail 627b818a30 Merge ranked-search (with master) into element-picker
# Conflicts:
#	sources/qetdiagrameditor.cpp
#	sources/qetdiagrameditor.h
2026-09-26 22:43:27 +12:00
ispyisail 0d2befbc00 Merge place-without-drag (with master) into context-menu-placement 2026-09-26 22:43:06 +12:00
ispyisail db387525e2 Merge place-without-drag (with master) into ranked-search 2026-09-26 22:43:06 +12:00
ispyisail 6763103d57 Merge master into place-without-drag (#1042)
# Conflicts:
#	sources/diagramview.h
#	sources/qetdiagrameditor.cpp
2026-09-26 22:39:52 +12:00
Laurent Trinques 852c32fc07 Merge pull request #1048 from ispyisail/feature/cell-lines
Add an option to show the folio cell limits across the drawing
2026-09-26 12:26:58 +02:00
Laurent Trinques e82548bee9 Merge pull request #1047 from ispyisail/fix/cell-ruler-ghosting
Fix the header bars: ghost labels on Windows, shown twice zoomed out
2026-09-26 12:21:24 +02:00
Laurent Trinques 60ab39b0f6 Merge pull request #1049 from ispyisail/feature/command-search-standalone
Add a command search: type part of a command's name, press Enter
2026-09-26 12:19:20 +02:00
ispyisail b57c37ca62 Run commands with right-drag mouse gestures
Hold the right button on a folio and drag: a ring appears around the
point where the button went down, showing up to eight commands, and the
one in the mouse's direction is highlighted with its name underneath.
Releasing runs it; releasing near the centre cancels. These are
SolidWorks' mouse gestures.

The commands are the shortcut bar's for the selection, placed clockwise
from the top, so customising the bar customises the ring. As with the
context menu, a right press selects what is under the mouse first.

A plain right click still opens the context menu, now on release on
every platform. The view tracks the right button and opens the menu
itself; the platform's own right-click event (sent on press on X11, on
release on Windows) is ignored while gestures are on, so the menu is
never opened twice. The keyboard menu is unchanged.

The view keeps out of the way while a tool or a placement is running,
since a right click cancels or finishes those, and while a text is
being edited. The new General option "Gestes de la souris avec le
bouton droit" (diagrameditor/mouse_gestures, on by default) turns it
off and restores the previous right button exactly.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
(cherry picked from commit a625142f36e619bb4d4f3551b6086f2dd8eabc86)
2026-09-26 21:40:46 +12:00
ispyisail 329831d986 Repeat the last drawing or placing command with Enter
Enter on a folio runs the last drawing or placing command again, as it
does in SolidWorks: the last tool from the add-item family (text, line,
rectangle, terminal strip plan...) or the last element placed, however
it was placed -- dragged, double-clicked, picked, or from the shortcut
bar.

Enter is handled in DiagramView::keyPressEvent, not bound as a shortcut,
so it keeps working everywhere else: search fields, the collection
tree, dialogs. On the folio it is left alone while a tool is running or
an item has the focus (a text being edited), and with any modifier held.

The same command is in Édition, named after what it will do --
"Répéter : Ajouter une ligne", "Répéter : insérer « Diode »" -- and
registered with ShortcutManager (diagrameditor.repeat_last_command, no
default key), so it can be bound or put on the shortcut bar.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
(cherry picked from commit 1e06ce97961ee18734ddaaa9b72b0303a169b9b2)
2026-09-26 21:40:32 +12:00
ispyisail b80d6e7cd3 Show the selection's commands beside the cursor after a click
After a click that selects something on a folio, a small row of commands
appears just above and to the right of the cursor, as the SolidWorks
context toolbar does. It fades as the mouse moves away and is gone past
200 pixels; a click elsewhere, the wheel, a key press or an emptied
selection hide it too. Clicking a command leaves it up, so rotate can be
clicked again.

The commands are the shortcut bar's for that selection -- elements or
conductors -- at most eight, so customising the bar customises this as
well. It is a child of the view's viewport, never a window, and never
takes the focus.

It is not shown after a drag (moving items, a rubber band), while
placing or drawing (Diagram::eventInterfaceIsRunning()), on
a read-only folio, or when switched off with the new General option
"Afficher les commandes près de la sélection" (diagrameditor/
context_toolbar, on by default).

ShortcutBarSettings::contextFor() now decides the context for both the
bar and this toolbar.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
(cherry picked from commit 78f3c1a2bc5c3cf1f7beae4bd60aefec20847d6d)
2026-09-26 21:40:32 +12:00
ispyisail 6d42556d9f Customise the shortcut bar in place
Right-click the shortcut bar, or click the "…" button at its end, and it
turns into a small window holding two lists: the bar's commands, left
to right, and every other command. Drag a command onto the bar, off
it, or to another place on it; a double click moves it to the other
list. Terminé saves and shows the bar again where it was, with the
result; Annuler, Esc or closing the window leaves it as it was.
"Valeurs par défaut" puts back the defaults for this context.

A Qt::Popup closes on a press outside it and holds the mouse grab, so
the bar is re-shown as a Qt::Tool window for the time of the edit.
Only QSettings is written, through ShortcutBarSettings, never the
project's undo stack.

The popup now looks the commands up itself (popUpShortcutBar(pos,
context)) instead of being handed actions, since it has to rebuild
them after an edit. ShortcutBarSettings::availableIds() lists what can
go on the bar, shared with the configuration page. An empty bar still
shows its "…" button, so it can be filled again.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
(cherry picked from commit 4145d4bec1d403126b2a6587105aea47092db8a2)
2026-09-26 21:40:19 +12:00
ispyisail 2da101ca90 Add a shortcut bar that opens at the cursor with S
Pressing S on a folio opens the element picker at the cursor with a row
of commands above it, chosen by what is selected, like the SolidWorks
shortcut bar:

  nothing selected     insert last element, element picker, text, line,
                       rectangle, terminal strip plan, paste, folio
                       properties
  elements selected    rotate, rotate texts, edit, copy, cut, delete
  only conductors      reset path, edit, delete

Each row is a list of ShortcutManager ids, so any registered command can
go on it and the bar carries no command list of its own. The lists are
in QSettings (diagrameditor/shortcut_bar/<context>); a context the user
has not changed follows the defaults. A disabled command keeps its
place, greyed, so a row looks the same each time. Clicking a button
closes the bar and triggers the action.

A new configuration page, "Barre de raccourcis", edits the three lists:
add, remove and reorder any diagram editor command.

To make that possible:
- ShortcutManager::action(id, owner) returns the action a given window
  registered under an id, since each editor window registers its own.
- The add-item actions (text, image, shapes, terminal strip plan) are
  registered as diagrameditor.add_<kind>, with no default key. They also
  appear in the Shortcuts page and can now be bound.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
(cherry picked from commit 53c1e213282b9f7226ca1c09e81703113236bb88)
2026-09-26 21:40:19 +12:00
ispyisail 07501d604e Add an element picker that opens at the cursor
Insert opens a small picker where the mouse is, with its search field
focused. Type to search the whole collection, Enter to place the best
hit (it is preselected), Up/Down to choose another, Esc to close. The
chosen element goes into the usual placement mode, so it can be placed
several times and repeated with A.

With the field empty the picker shows a palette: the elements of a
folder, as an icon grid. The palette is a folder rather than a setting
or a file format. Subfolders are read in, the 01_/02_ filename prefixes
the shipped collection already uses give the order, and sharing it is
putting it in the company collection. Only the path is stored,
"elementscollection/palette-path", defaulting to the user collection.
It is read each time the picker opens, capped at 60 entries and three
folder levels.

The picker builds no second collection model. It asks the Collections
panel's rankedSearch(), so both give the same results in the same order
and startup is unchanged.

"Insérer un élément…" is in the Édition menu, registered with
ShortcutManager on Insert, which nothing else uses, and disabled with no
folio open or on a read-only project.

Discussion #676.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
(cherry picked from commit 01e1e0303b96380e710da8d4bb2300b21d01e938)
2026-09-26 21:40:19 +12:00
ispyisail 73ad43bd8f Show collection search results as a ranked list
The Collections search hid non-matching rows, so the hits stayed spread
through the expanded folders. Searching "diode" against the shipped
collection left five levels of tree open, and the first visible hit was
"Avalanche diode bidirectional".

The same matches are now shown as a flat list that takes the tree's
place while a search is active, best match first:

  exact name            1000
  name starts with       800
  name contains          600, less the match position
  info field only        300
  less the name length, so "Diode" precedes "Diode Zener bidirectional"

"diode" now gives Diode, diode-tube, Diode bridge, Diodes Module Pilot
Wire, Photodiode. No new index: this ranks what match() already returns
against Qt::UserRole+1, the string built at startup from the name and
every element-info field. An element matching several "+" terms is
listed once, and each row's tooltip is its folder, so similar names can
be told apart.

Double click or Enter on a result places it, as in the tree. Down from
the search field moves into the results, so searching and placing needs
no mouse. Emptying the field brings the tree back.

The query and ranking live in rankedSearch(), which returns hits without
model indexes, so another list can reuse them.

Discussion #676.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
(cherry picked from commit 3d6a280f9608758527ef0ca2de7223a19dfbfa45)
2026-09-26 21:40:19 +12:00
ispyisail c1d9e60656 Place the last element and folio references from the folio's context menu
Right-clicking an empty folio now also offers:

- "Insérer le dernier élément": the same action as Édition and the A
  key, shown once something has been placed.
- "Renvoi de folio": the folio report elements the project already
  uses, read from its embedded collection, with dated copies of the same
  element listed once. A project that has none yet gets the coming and
  going arrows of the common collection. An entry places its element
  where the menu was opened.

The submenu is rebuilt each time the menu opens, from the embedded
collection's XML, not from the folios' scenes. It is left empty, and so
hidden, on a read-only folio.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
(cherry picked from commit 94e3f61bf93689066b3964f2316e3c8f1ee7b673)
2026-09-26 21:40:18 +12:00
ispyisail 92cbc8fd2a Merge master into stage 2 (#1042) as the base of the stage PRs 2026-09-26 21:40:18 +12:00
ispyisail db56262924 Register the drawing tools so the command search finds them
Ajouter une ligne, un rectangle, une ellipse... had no ShortcutManager
id, so the command search could not list them. Give each one an id with
no default key; they can also be bound in the Shortcuts page now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 21:35:47 +12:00
ispyisail 86cb7e430e Add a command search: type part of a command's name, press Enter
Ctrl+Shift+M (Édition → "Rechercher une commande…") opens a small
search box at the cursor listing every command of the diagram editor
window, as SolidWorks' "Search Commands" and the command palette of
many editors do. Typing narrows it, best match first: name starting
with the text, then a word starting with it, then containing it.
Matching ignores case, accents and mnemonic "&", so "editer" finds
"Éditer l'item sélectionné". Each row shows the command's key when it
has one, which also teaches the keys. Disabled commands are listed,
greyed, and cannot be run. Enter runs the highlighted one after
closing the box; Esc closes.

The list is ShortcutManager's registry, restricted to the actions this
window owns (ShortcutManager::action(id, owner)), so a second editor
window's commands never appear and nothing has to be listed by hand.

Ctrl+Shift+P, the usual key for this, is already the autonumbering
dock's.

tst_commandsearch covers the folding, the ranking, that another
window's commands are left out and that a disabled command does not
run; both behaviours were checked to fail the test when broken.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 21:34:02 +12:00
ispyisail 3d260edcdb Cell rulers: line the top ruler up with the columns when the row header is hidden
BorderTitleBlock::draw() leaves the row header's room before the first
column even when the row header is hidden; insideBorderRect() does not,
so the top ruler was one header width out of line on such a folio. Take
the first cell's position from the header sizes, as draw() does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 21:18:35 +12:00
ispyisail 53706a80d8 Add an option to draw the folio cell limits across the drawing
Affichage › "Afficher les limites des cases" draws a faint dashed line
at every column and row limit of the folio border, so that zoomed into
the middle of a folio you can see where a cell ends, not only which one
the headers name. Off by default, remembered once set
(diagrameditor/cell_lines).

Drawn by DiagramView::drawBackground(), under every item and only in
the view: printing and export render the Diagram, never the view, so
they cannot pick the lines up. PaletteGraphicsView gains scenePainter()
so a subclass can draw there on the inverted (dark palette) path too.

The lines follow the border's own cell positions, and each direction is
hidden when the folio hides that header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 21:17:19 +12:00
ispyisail 1e74bcd9de Cell rulers: no ghost labels on Windows, hidden while the folio's own header shows
The Windows 11 style gives QPalette::Button a translucent colour
(#FFFFFFB3). The rulers filled their background with it, and since they
paint with WA_OpaquePaintEvent nothing clears them first: every zoom
step blended the new labels over the old ones, leaving a fading trail.
Fill with the button colour composed over the window colour instead,
which is always opaque.

Each ruler is now also hidden while the folio's own column (or row)
header is wholly in sight, so zoomed out only the folio's headers show,
not both. When a ruler comes or goes the drawing keeps its place on
screen: the ruler covers or uncovers the edge of the viewport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 21:06:13 +12:00
ispyisail 45aec7735b Fix #1045: crash on selecting an element with the online-installer Qt
Since #983, projectDataBase::newQuery() checked a query with
sqlite3_prepare_v2() and sqlite3_stmt_readonly() on the handle of the
QSQLITE driver. Those calls go to the libsqlite3 QElectroTech links. The
QSQLITE plugin of the Qt online installer does not use that library: it
carries its own copy of SQLite, so the handle belongs to another library
and the call crashes. #1021 then put newQuery() on every element
selection, which is where #1045 hits it.

The check now runs the query with PRAGMA query_only set, through the
driver. SQLite refuses a write itself, before touching a row, so the CTE
prefix #983 closed ("WITH x AS (SELECT 1) DELETE FROM element") stays
closed. A refused or failed query comes back empty, because several
callers call exec() again on what newQuery() returns, after query_only
is off.

QElectroTech no longer calls the SQLite C API anywhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 20:06:07 +12:00
Laurent Trinques 923367d2a8 Merge pull request #1039 from ispyisail/fix/text-mover-null-drag
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m22s
Fix undo lost after a text drag that snaps back to its start
2026-09-26 08:44:04 +02:00
Laurent Trinques 6dd260a7b2 Merge pull request #1038 from ispyisail/feature/cell-rulers
Add an option to keep the folio row and column headers visible (#1034)
2026-09-26 08:43:42 +02:00
Laurent Trinques 132c564aef Merge pull request #1040 from ispyisail/feature/text-grid
Add a finer snap grid for dragged texts
2026-09-26 08:21:48 +02:00
Laurent Trinques e9c4969af7 Merge pull request #1044 from qelectrotech/revert-1037-german-translation
Revert "german translation qm file"
2026-09-26 08:06:56 +02:00
Laurent Trinques 040c4b5e29 Merge pull request #1041 from ispyisail/feature/context-menu-drawing
Add the drawing tools to the folio's right-click menu
2026-09-26 08:06:25 +02:00
Laurent Trinques 71d708e2da Merge pull request #1036 from ispyisail/feature/goto-cell
Add jumping to a folio cell such as B13 from Ctrl+G (#1034)
2026-09-26 08:04:17 +02:00
Laurent Trinques b490720652 Merge pull request #1035 from ispyisail/fix/spacemouse-capture-seconds-macos
Add macOS, Windows and --seconds to the 3D mouse recording script
2026-09-26 08:00:53 +02:00
Laurent Trinques 519f2245f6 macOS: fix arm64 bundle rejection (-10825) on macOS Sequoia
The Info.plist template shipped LSMinimumSystemVersion=12.3.0, but
the CMake build embedded the host SDK's deployment target (minos
26.0 on recent build machines) into the binary's LC_BUILD_VERSION.
Launch Services relies on the binary's minos, not the plist, so it
refused to launch the app on macOS < 26 even though it ran fine
when invoked directly.

- Set CMAKE_OSX_DEPLOYMENT_TARGET=14.0 explicitly in the cmake
  configure step, matching the real minimum imposed by the
  Homebrew Qt6 toolchain used for this build.
- Sync misc/Info.plist's LSMinimumSystemVersion to 14.0.0, and have
  MacQetDeploy_arm64_cmake.sh set it via PlistBuddy at bundle
  install time so it can't drift from the build target again.

Reported-by: guillaume.ruivo (forum)
2026-09-26 07:51:31 +02:00
ispyisail b61193f66f Revert "german translation qm file" 2026-09-26 17:50:02 +12:00
ispyisail e6291eb8e8 Place an element from the collection without dragging it
DiagramEventAddElement is already a good placement mode: the element
follows the cursor on the grid, a left click drops it, Space rotates it,
and it stays loaded for a run of the same symbol. Its only caller was
DiagramView::handleElementDrop(), so it could be reached only by
finishing a drag. Double-clicking a symbol in the Collections dock
opened the element editor instead.

- DiagramView::startElementPlacement() is split out of
  handleElementDrop(). defaultPlacementPos() uses the cursor when it is
  over the view and the centre of the visible area otherwise.

- ElementsCollectionWidget emits insertElementRequested() on double
  click, or Enter on the highlighted item. The host decides which view
  receives it, so the widget can later be reused outside the editor.

- When there is nowhere to place it (no folio open, read-only project),
  the element editor opens, as a double click did before.

- "Insérer le dernier élément" (Édition menu, default key A) places the
  last element again. DiagramView reports every placement it starts, so
  an element dropped by drag counts too. Macros are not remembered.

A rather than Space: Space rotates the pending element inside placement
mode and is bound three more times in this editor. The key is a
ShortcutManager default and can be changed in the Shortcuts page.

Double click placing is a behaviour change, so it has a preference,
"elementscollection/double-click-inserts" (default true), shown in
Configuration as an opt-out: "Double-cliquer dans la collection ouvre
l'éditeur d'élément au lieu de l'insérer".

Discussions #676 and #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 11:53:34 +12:00
ispyisail 0ca4b9f6c3 Put drawing first in the folio's context menu, rows and columns one level down
Right-clicking an empty folio offered Paste here, Folio properties and
four row/column actions. Four of six entries change the folio's layout,
which is rarely wanted and easy to hit by mistake, while nothing in the
menu helps draw.

The empty-folio menu now holds the "Ajouter" submenu (text, image,
shapes, terminal strip plan -- the same actions as the Edition menu and
toolbar), then Folio properties, then the row and column actions in a
"Lignes et colonnes" submenu. The selection menu is unchanged.

A submenu whose actions are all disabled is dropped, the same way
disabled actions already are.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 11:40:25 +12:00
ispyisail a08cdf7272 Reset the element text mover after a drag that ends where it started
ElementTextsMover::endMovement() returned early when the text had not
moved without clearing m_movement_running, so every later
beginMovement() on that folio was refused. The dragged text still
moves, because it positions itself, but the mover no longer tracks the
drag and builds no undo step for it.

To reproduce: Shift+drag one element text a couple of pixels so it
snaps back where it was, then Shift+drag another text of the element
and press Ctrl+Z. The second text stays where it was dropped; with this
change it goes back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 10:37:21 +12:00
ispyisail 2fdb225936 Reset the element text mover after a drag that ends where it started
ElementTextsMover::endMovement() returned early when the text had not
moved without clearing m_movement_running, so every later
beginMovement() on that folio was refused. The dragged text still
moves, because it positions itself, but the mover no longer tracks the
drag and builds no undo step for it.

To reproduce: Shift+drag one element text a couple of pixels so it
snaps back where it was, then Shift+drag another text of the element
and press Ctrl+Z. The second text stays where it was dropped; with this
change it goes back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 10:36:40 +12:00
ispyisail 95f3ea2f6d Share the text grid choices; add a preference, a status hint and a unit test
Drop 1:2.5: on a grid of 10 it steps by 4, which misses 10, so texts on
two elements 10 apart could never line up. Every remaining divisor is a
whole number, which tst_textgrid checks.

The divisor list and the snapping arithmetic move to the header-only
textgrid.h so the toolbar menu, the preferences page and the test share
them. The preferences page gets the same choice under Grille + Clavier;
QETApp::textGridChanged keeps every editor's toolbar button in step.
While element texts are dragged, the status bar names the text grid and
says to release Shift and hold Ctrl for free placement -- Ctrl+Shift
together is the pan shortcut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 10:22:50 +12:00
ispyisail 0e02190eb2 Add a finer snap grid for dragged texts
Texts snapped to the folio grid, so the first drag of an off-grid label
pulled it sideways by up to half a grid step (discussion #1020). Texts
now snap to a fraction of the folio grid, chosen from a "Textes 1:N"
button in the View toolbar and a menu under Affichage: Off, 1:1, 1:2,
1:2.5, 1:5, 1:10. Because the step divides the folio grid, texts on
different elements still line up. Ctrl still places a text freely.

1:1 is the default and matches the previous behaviour. The setting is
stored in QSettings; nothing changes in saved projects.

All five text movers switch together: element texts, text groups,
texts moved with a selection, conductor texts and independent texts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 09:15:35 +12:00
ispyisail 2807771200 Jump to a cell on another folio from Ctrl+G, typed as 3-B13
The popup now also takes a folio position before the cell, the way
cross references and folio reports write it by default (%f-%l%c): 3-B13
offers "Folio 3 (<title>), case B13", and Enter shows that folio and
zooms onto the cell. Case and a leading "p" are ignored, and the
separator is optional: 3-b13, 3b13, P3B13 and p3-b13 all work. A plain
B13 still means the current folio, and P3 alone is still row P,
column 3: a folio always needs a cell after it. A folio position past
the last folio, or a cell outside that folio's border, offers nothing.

The zoom on another folio is queued, so it runs once the tab switch
has been handled. Checked on the ATS example: jumping to 1-C5 from
folio 3 gives the same view, pixel for pixel, as C5 typed on folio 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 08:36:53 +12:00
ispyisail 4cffdf93b6 Merge pull request #1037 from Kellermorph/german-translation
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m15s
german translation qm file
2026-09-26 08:28:04 +12:00
ispyisail 9738345b45 Add an option to keep the folio row and column headers visible (#1034)
Affichage > "Garder les en-têtes visibles" adds a bar along the top and
the left of the diagram view that repeats the folio's column numbers and
row letters, aligned with the cells at any zoom, so they stay in sight
when the folio's own headers are scrolled away. Off by default; the
choice is stored as diagrameditor/cell_rulers.

The bars are CellRuler widgets in the view's margins
(setViewportMargins), not scene items, so printing and PDF/PNG/DXF
export never see them, and they paint with the application palette
outside of the dark-palette inversion. They keep a constant thickness;
when cells get narrower than their labels, only every 2nd, 5th, 10th...
label is written. A bar is hidden when the folio hides that header.
Showing or hiding them keeps the centre of the view where it was.

The labels come from BorderCellLabels, now also used by
BorderTitleBlock::draw(), so the bars and the border cannot disagree.
PNG export of all 133 folios of the examples is pixel-identical to
master, with border-columns_0 true and false.

Known limits: changing border-columns_0 repaints the bars at the next
scroll or zoom; the menu toggle updates the views of its own editor
window only, like the grid toggle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 08:22:12 +12:00
Kellermorph 286002fe25 german translation qm file 2026-09-25 21:48:49 +02:00
ispyisail ce0e7e6867 Add jumping to a folio cell such as B13 from Ctrl+G (#1034)
Typing a cell reference into the "Atteindre un élément" popup now offers
"Case B13"; Enter zooms the view onto that cell with one cell of margin.
The cell is read the way the border labels it: row letter(s) then column
number, honouring the "columns start at 0" setting and multi-letter rows
(AA, AB...). Cells outside the folio are not offered.

BorderTitleBlock::cellRect() is the reverse of convertPosition(); a
round trip over every cell of a 30x23 folio, under both column-numbering
settings, returned the same cell for all 1380.

When an element is labelled exactly like the cell (K1), the element stays
first so Enter keeps its old meaning; the cell is listed after it.

DiagramView::zoomToRect() re-centres from a queued call: zooming in makes
the scroll bars appear, and the viewport resize that follows is anchored
under the mouse (setResizeAnchor(AnchorUnderMouse)), which otherwise
scrolls the view away from the cell straight after the zoom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 07:37:38 +12:00
ispyisail 5f0b015186 Merge pull request #1031 from ispyisail/fix/bugtracker-240-backup-crash
Fix bugtracker #240: crash when a recovery file cannot be opened
2026-09-26 07:13:40 +12:00
ispyisail e5738c5b7d Merge pull request #1024 from arummler/fix-picture-insert
Fix picture and graph primitives issues
2026-09-26 07:13:20 +12:00
ispyisail c3c264cd78 Merge pull request #1010 from Kellermorph/full-contact-comb
Full contact comb
2026-09-26 07:12:57 +12:00
ispyisail d392ab4b8f Let misc/spacemouse-capture.py record on Windows, and say how on each system
Windows reads the device through hid.dll and SetupAPI with ctypes, as
hidapi's Windows backend does: shared, so 3DxWare can keep running, and
dropping the 0 report ID Windows adds, as hidapi does, so the bytes are
the ones QET decodes. Windows does not give out the report descriptor,
so a Windows recording has none; tst_spacemousehid then uses its
fallback layout.

The docstring, printed in full by --help, now has step-by-step
instructions for Linux, macOS and Windows, download included.

Tested: Windows Python 3.12 (embeddable) under Wine 10, against the
virtual uhid 3D mouse: --list finds it, a push sent during `right`
lands in `right`, and a button press and release land in `buttons`,
with the same bytes the Linux path records for the same push. Linux
and the mocked macOS path still pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 06:50:33 +12:00
ispyisail ebd4a57563 Let misc/spacemouse-capture.py record on macOS
The recorder only read /dev/hidraw, so it could not run on a Mac. It now
also reads through IOKit via ctypes, using the calls hidapi's mac backend
makes: a shared open (as #1028 does) and the input report callback.
hidapi passes those bytes through unchanged, so a recording is exactly
what QET decodes on macOS. Standard library only, and no sudo.

If 3DxWare holds the device, or Input Monitoring is not granted, it says
which instead of recording nothing. The JSON adds "backend" and
"other_readers" (any 3DxWare or spacenavd process that was running).

Reports that queue up while it waits for Enter are now dropped, so each
step holds only its own movement. The Linux path is otherwise unchanged.

Tested: the Linux path against the virtual uhid device
(tools/hid-capture/fake-spacemouse.py in the docker harness). A stale
report was dropped and the step's own push was kept. The macOS path has
only been run against a mocked IOKit, not on a Mac.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 06:44:08 +12:00
ispyisail 23d83ec153 Add --seconds to spacemouse-capture.py to record longer per step
The SpacePilot PRO recording on #599 has a weak `right` step: pushed
too lightly to read above the cap's normal cross-axis noise. A longer
window makes it easier to hold a firm push and gives the decoder more
samples to average over.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-26 06:43:58 +12:00
Laurent Trinques d43c535cc9 Merge pull request #1030 from ispyisail/fix/bugtracker-343-save-order
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m2s
Fix bugtracker #343: saving reorders texts and shapes after an edit
2026-09-25 15:55:44 +02:00
Andre Rummler 71782aec20 Fix wheel scale hardening 2026-09-25 15:09:21 +02:00
Laurent Trinques 59dc59cf2f Merge pull request #1029 from ispyisail/fix/bugtracker-340-pdfxid
Fix bugtracker #340: small PDF text drawn too bold in Adobe Acrobat
2026-09-25 14:35:43 +02:00
Laurent Trinques 469c8cbc70 Merge pull request #1032 from ispyisail/feat/spacemouse-3dxware-macos
Fix 3D mouse doing nothing on macOS when 3DxWare is installed
2026-09-25 14:33:59 +02:00
Andre Rummler 1330dbb435 Fix janking behaviour during resize and Ctrl action. 2026-09-25 13:37:49 +02:00
ispyisail e7f19de2da Mention ConnexionBackend in SpaceMouseBackend's class comment
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 23:25:27 +12:00
ispyisail 4c9a307cb9 Say what realLibrary() can and cannot check
Measured on a macOS 15 runner with 3DxWare 10.8.13: an ad-hoc signed
test binary with the hardened runtime loads 3DconnexionClient with or
without the entitlement, so ad-hoc signing does not enforce library
validation and CI cannot prove the entitlement is needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 23:07:34 +12:00
ispyisail 4381c6caa3 Read the 3D mouse through 3DxWare on macOS when it is installed
On macOS, 3DxWare installs a driver extension that takes the 3D mouse
over. With it installed, HidBackend opens the device but receives
nothing, so the mouse did nothing in QElectroTech until 3DxWare was
uninstalled (discussion #599, PR #1028). Most Mac owners of a 3D mouse
have 3DxWare installed.

ConnexionBackend asks 3DxWare for the motion instead, through
3DconnexionClient.framework, as Blender does. SpaceMouseListener tries
it first. When 3DxWare is not installed, or is installed but its driver
is not running, it falls back to HidBackend, so the device works in
both setups. Take-over mode stops 3DxWare's own actions in QET, so the
view does not move twice.

The library is loaded at run time from where 3DxWare installs it:
nothing is linked or bundled, and the build needs no SDK. The few
declarations are written here, from Blender's
GHOST_NDOFManagerCocoa.mm, because 3Dconnexion's SDK headers may not be
redistributed. 3DxWare's axes are y up and z away from the user; they
are mapped to QET's raw USB convention by comparing Blender's 3DxWare
and spacenavd code paths.

The release script signs with the hardened runtime, which refuses a
library another team signed. misc/qelectrotech.entitlements adds
com.apple.security.cs.disable-library-validation (Blender's notarized
build carries the same one), and MacQetDeploy_arm64_cmake.sh now passes
it to all four signings of the app, including the re-sign inside the
DMG.

Tested: tst_spacemouseconnexion runs the backend on every platform
against fakeconnexion, a stand-in library that answers from its own
thread as 3DxWare does: registration, the axis mapping, buttons, other
clients' messages, 3DxWare not installed or not running, deletion
with a message in flight. Flipping an axis sign or dropping the client
check turns it red. realLibrary() loads the real framework when
3DxWare is installed. Linux Qt 6 build with the 3D mouse enabled: all
18 tests pass.

Not tested: on a Mac with a real device. The axis signs and whether
buttons arrive as a bitmask with current 3DxWare are unverified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:54:07 +12:00
ispyisail 07d7aedc26 Fix bugtracker #240: crash when a recovery file cannot be opened
After a crash, QElectroTech offers to reopen its recovery files. If one
cannot be read, answering OK crashed the program instead of showing the
"could not open" warning: QETProject(KAutoSaveFile *) takes ownership of
the file and deletes it on failure, and openBackupFiles() then read the
file name from the deleted object to build the warning. Read the name
first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-25 21:45:07 +12:00
Kellermorph 5622308ba2 Derive empty-slot labels from the group's declared terminals
Both issues from the review, preview of empty contact comb slots only:

- Slice the flat label list over the declared poles in drawAsContacts
  (terminals per pole from labels.size()/contactCount) instead of a
  fixed stride of 2 (3 for a switch) inside drawContact: terminalCount
  and contactCount are edited independently, so with 3 poles and the
  default terminal count of 2 the fixed stride starved every pole but
  the first. Available labels are now distributed across the poles.
- Map changeover labels of a slot to their own position always
  (common=stored[0] right, NC=stored[1] bottom-left, NO=stored[2]
  top-left, missing entries stay empty) instead of falling back to the
  raw stored order, which put the numbers on the wrong contact halves
  when the terminal count was below three.
- Element editor: changing the contact count now keeps the terminal
  count in step (same terminals per contact, type default 2/3 for
  inconsistent data, minimum 3 for a switch), so the mismatch cannot
  be created anymore; legacy mismatched data is handled by the new
  slicing.
- Drop the now redundant elmt check before is_power_ctc (it already
  includes it) - the dead null check from the review.
2026-09-25 11:24:35 +02:00
ispyisail 149ae961cc Fix bugtracker #343: saving reorders texts and shapes after an edit
Diagram::toXml() writes texts, images, shapes and tables in items()
order. The diagram scene uses NoIndex, and Qt's linear index sorts its
item list by pointer address the first time any item is removed from
the scene, which the editor does constantly (selection handles, for
one). From then on the order in the saved file follows memory
addresses, so moving one element reshuffles unrelated blocks and a
version-control diff of the project becomes unreadable.

Write those blocks in stacking order instead, read from a rect query,
which Qt sorts by z and insertion order even with NoIndex. Reloading a
file rebuilds exactly that order, so a resave is stable and the drawing
does not change. Elements and conductors were already sorted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-25 21:16:34 +12:00
ispyisail 4f309770f0 Fix bugtracker #340: small PDF text drawn too bold in Adobe Acrobat
Qt 6 declares the PDF/X namespace (pdfxid) in the XMP metadata of every
PDF it writes, even when the file is not PDF/X. Adobe Acrobat then draws
small text too bold at some zoom levels. Qt only needs the declaration
for PDF/X-4 output, which QElectroTech never asks for.

Blank the declaration out after export, in place with spaces so no
offsets move, in both the export window and the --export-pdf path.
Diagnosis and the Acrobat testing by Alf, bugtracker #340.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-25 20:44:02 +12:00
Andre Rummler bb037ee3ff Fix: when pasting the picture or graphics object is nowunder the top left corner instead far away. 2026-09-25 00:24:15 +02:00
Andre Rummler d1256872c2 Fix transformation order. 2026-09-24 15:21:42 +02:00
Kellermorph 800189debb Address review: restore hover entries for empty position text
- Call updateLabel() explicitly when the xref is created in itemChange for a master that must show its configured contact groups without slaves (same pattern as the PLC branch above).
- Register the hover/click hit rect of a contact independently of its position text again, as before this feature: only the drawing stays guarded by !str.isEmpty(), and the map insert is now keyed on elmt so free slots (nullptr) never enter the map.
- Revert is_power_ctc to the original element-type test (with a null guard): the Power-flag term was redundant for every caller that passes an element, so no linked contact changes classification.
- Clarify the label-order comment: single pole NO/NC are swapped, changeover labels are rotated per pole inside drawContact() (multi pole included), multi pole NO/NC groups keep the master order.
2026-09-24 15:11:31 +02:00
Andre Rummler 522e008c26 Fix rotation of inserted picture. 2026-09-24 13:37:06 +02:00
Kellermorph c1cc9a5b98 Show all master-defined slaves in the contact comb behind a new option
Add a new cross reference setting per xref type (coil, protection,
commutator, PLC), labelled "Afficher tous les esclaves definis par le
maitre" and persisted as showallconfiguredslaves. When it is enabled,
the contacts display is selected and the master declares contact
groups, the contact comb draws every contact group of the master in the
master's own order, even when no slave is linked to it yet. Masters
without declared contact groups and the option turned off keep the
previous behaviour exactly: linked slaves only, sorted by position.

- XRefProperties: new property stored in the settings and in the
  project XML (attribute showallconfiguredslaves, absent means false so
  old files are unaffected), included in operator==.
- XRefPropertiesWidget: new checkbox placed after the terminal names
  one, enabled only while "Afficher en contacts" is selected; its
  enabled state is now also set explicitly when a type is loaded (a
  radio button that does not change emits no toggled()).
- For the PLC type the contacts/cross radios, the two display
  checkboxes and the cross options group are hidden: a PLC master is
  always drawn as its IO table, those settings have no effect there.
  Positioning and label settings, which the table really uses, stay.
- CrossRefItem: free slots draw the symbol of the group plus the
  terminal names the master defines (pairs swapped for a single pole
  NO/NC contact, labels of a changeover contact rotated one step
  counter-clockwise), without position text and without hover/click.
  Linked slaves keep drawing from their own data at their assigned
  group position; links without a group are appended at the end in
  position order.
- Xref lifecycle: the item is created and kept without linked slaves
  for snap-to-bottom (MasterElement::mustShowXrefWithoutSlave) and for
  snap-to-label (DynamicElementTextItem::updateXref and
  ElementTextItemGroup::updateXref, which now also run when the element
  lands on the scene and re-establish their project connection), so a
  freshly placed master shows its comb immediately instead of only
  after the next settings change. updateLabel() resets its geometry
  when the option is turned off again, so no stale ghost stays.
2026-09-24 09:59:21 +02:00
ispyisail 4e754cfac1 Conductor dock: sync to external property changes (#500)
The dock editor snapshotted the conductor once and never refreshed, so a
change made via the modal Edit-conductor dialog left the dock holding stale
values; on deselection apply() wrote that stale snapshot back, overwriting
the dialog's change. Subscribe to Conductor::propertiesChange and refresh via
updateUi() so the dock mirrors external edits and apply() becomes a no-op
when nothing changed in the dock.
2026-06-19 07:43:27 +12:00
Shane Ringrose 151b16ba76 Conductor dock: English translations + align checkbox wording (#500)
Add English translations (qet_en.ts) for the new conductor-panel strings
so non-French users don't see untranslated source text: the panel title,
the two undo captions, the apply-to-all checkbox, and the View-menu
toggle + status tip.

The apply-to-all checkbox now reuses the modal dialog's exact wording
("Appliquer les propriétés a l'ensemble des conducteurs de ce potentiel")
for consistency and so it shares the existing translation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:38:47 +12:00
Shane Ringrose 0902725a59 Conductor dock: apply-to-all-potential checkbox (#500)
Port the modal dialog's "apply to all conductors" option into the dock
panel, resolving the open propagation question. A persisted checkbox,
pinned above the tabs, makes each edit propagate to every conductor on
the same potential in one undo step - identical semantics to
ConductorPropertiesDialog, but the choice is remembered across edits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:35:49 +12:00
Shane Ringrose 280051745a Conductor dock: apply edits live, edit via label, fill height (#500)
Addresses the testing feedback on the conductor selection-properties
prototype:

1. Edits were never applied. The dock drives every editor through
   setLiveEdit(true), which each editor overrides to connect its field
   changes to apply(); ConductorPropertiesEditorWidget didn't override it,
   so the call was a no-op. Implement setLiveEdit() to connect the hosted
   ConductorPropertiesWidget's controls (commit-style signals) to apply().
   A m_updating guard suppresses the signals emitted while the widget is
   loaded programmatically, so a partial mid-load state is never committed.

2. Clicking a conductor's text label showed nothing, because the label is
   a ConductorTextItem, not a Conductor. Map it to its parentConductor() in
   the editor factory, mirroring the double-click-the-label dialog behaviour.

3. The panel sat at a small size hint with empty space below it. Give the
   editor an Expanding vertical size policy (and a minimum height) so it
   fills the dock like the other editors.

Refs #500

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:35:49 +12:00
Shane Ringrose e3ede85efd conductor dock: scrollable/narrowable + View-menu enable toggle (#500) 2026-06-18 10:35:49 +12:00
Shane Ringrose 95c2f6cf6d proto: conductor properties in selection-properties dock (#500) 2026-06-18 10:35:48 +12:00
2865 changed files with 283681 additions and 81475 deletions
+18 -1
View File
@@ -227,6 +227,23 @@ jobs:
cp -r "$GITHUB_WORKSPACE/titleblocks" "$FILES/titleblocks" || true cp -r "$GITHUB_WORKSPACE/titleblocks" "$FILES/titleblocks" || true
cp -r "$GITHUB_WORKSPACE/examples" "$FILES/examples" || true cp -r "$GITHUB_WORKSPACE/examples" "$FILES/examples" || true
cp -r "$GITHUB_WORKSPACE/fonts" "$FILES/fonts" || true cp -r "$GITHUB_WORKSPACE/fonts" "$FILES/fonts" || true
# The MCP server finds bin/QElectroTech.exe and elements/ from mcp/.
mkdir -p "$FILES/mcp"
cp "$GITHUB_WORKSPACE/misc/qet-mcp/qet_mcp.py" "$GITHUB_WORKSPACE/misc/qet-mcp/README.md" "$FILES/mcp/"
# Python for it, so Windows users need not install one: the official
# embeddable package, pinned by version and checksum (python.org's
# own sha256 for this file). The installer offers it unticked; the
# portable folder and the MSI carry it.
PY_EMBED_VERSION=3.14.7
PY_EMBED_SHA256=d297e5ff019966817ad8502465176139f2d3d840fa4ed84b13bed399a6ab1f15
PY_ZIP="$GITHUB_WORKSPACE/python-embed.zip"
curl -fsSL -o "$PY_ZIP" \
"https://www.python.org/ftp/python/$PY_EMBED_VERSION/python-$PY_EMBED_VERSION-embed-amd64.zip"
echo "$PY_EMBED_SHA256 $PY_ZIP" | sha256sum -c - \
|| { echo "ERROR: embeddable Python checksum mismatch"; exit 1; }
python3 -m zipfile -e "$PY_ZIP" "$FILES/mcp/python"
[ -f "$FILES/mcp/python/python.exe" ] \
|| { echo "ERROR: python.exe missing from the embeddable package"; exit 1; }
# --- Translations --- # --- Translations ---
# Since PR #751 the .qm files are no longer tracked in git: lrelease # Since PR #751 the .qm files are no longer tracked in git: lrelease
@@ -305,7 +322,7 @@ jobs:
&& echo " OK : $f" \ && echo " OK : $f" \
|| echo " MISSING: $f" || echo " MISSING: $f"
done done
for d in ico elements lang titleblocks fonts examples bin; do for d in ico elements lang titleblocks fonts examples bin mcp; do
[ -d "$FILES/$d" ] \ [ -d "$FILES/$d" ] \
&& echo " OK : $d/" \ && echo " OK : $d/" \
|| echo " MISSING: $d/" || echo " MISSING: $d/"
+22 -5
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
@@ -80,7 +80,7 @@ set(QET_HAS_QTPDF FALSE)
find_package(Qt6 QUIET COMPONENTS Pdf) find_package(Qt6 QUIET COMPONENTS Pdf)
if(TARGET Qt6::Pdf AND NOT Qt6_VERSION VERSION_LESS 6.4.0) if(TARGET Qt6::Pdf AND NOT Qt6_VERSION VERSION_LESS 6.4.0)
set(QET_HAS_QTPDF TRUE) set(QET_HAS_QTPDF TRUE)
list(APPEND QET_PRIVATE_LIBRARIES Qt::Pdf) list(APPEND QET_PRIVATE_LIBRARIES Qt6::Pdf)
add_compile_definitions(QET_HAS_QTPDF) add_compile_definitions(QET_HAS_QTPDF)
else() else()
message(STATUS "QtPdf module not available (or Qt < 6.4): PDF page import feature disabled") message(STATUS "QtPdf module not available (or Qt < 6.4): PDF page import feature disabled")
@@ -93,10 +93,10 @@ endif()
# the application depends on. When it's missing the feature is silently # the application depends on. When it's missing the feature is silently
# disabled - see the QET_HAS_SCRIPTING guard in qetscripting.*. # disabled - see the QET_HAS_SCRIPTING guard in qetscripting.*.
set(QET_HAS_SCRIPTING FALSE) set(QET_HAS_SCRIPTING FALSE)
find_package(Qt${QT_VERSION_MAJOR} QUIET COMPONENTS Qml) find_package(Qt6 QUIET COMPONENTS Qml)
if(TARGET Qt${QT_VERSION_MAJOR}::Qml) if(TARGET Qt6::Qml)
set(QET_HAS_SCRIPTING TRUE) set(QET_HAS_SCRIPTING TRUE)
list(APPEND QET_PRIVATE_LIBRARIES Qt::Qml) list(APPEND QET_PRIVATE_LIBRARIES Qt6::Qml)
add_compile_definitions(QET_HAS_SCRIPTING) add_compile_definitions(QET_HAS_SCRIPTING)
else() else()
message(STATUS "Qt Qml module not available: JavaScript scripting (--run) disabled") message(STATUS "Qt Qml module not available: JavaScript scripting (--run) disabled")
@@ -159,6 +159,18 @@ if(WIN32)
${QET_DIR}/qelectrotech.qrc ${QET_DIR}/qelectrotech.qrc
${QET_DIR}/ico/icon-themes.qrc ${QET_DIR}/ico/icon-themes.qrc
) )
# Our own manifest (it declares longPathAware). MSVC merges a .manifest
# listed as a source; MinGW needs it embedded through a resource file,
# and ld then keeps it ahead of the toolchain's default manifest.
set(QET_MANIFEST_FILE ${QET_DIR}/build-aux/windows/qelectrotech.exe.manifest)
if(MSVC)
target_sources(${PROJECT_NAME} PRIVATE ${QET_MANIFEST_FILE})
else()
configure_file(${QET_DIR}/build-aux/windows/qelectrotech-manifest.rc.in
${CMAKE_CURRENT_BINARY_DIR}/qelectrotech-manifest.rc @ONLY)
target_sources(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/qelectrotech-manifest.rc)
endif()
else() else()
add_executable( add_executable(
${PROJECT_NAME} ${PROJECT_NAME}
@@ -332,6 +344,7 @@ target_include_directories(
${QET_DIR}/sources/factory/ui ${QET_DIR}/sources/factory/ui
${QET_DIR}/sources/print ${QET_DIR}/sources/print
${QET_DIR}/sources/svg ${QET_DIR}/sources/svg
${QET_DIR}/sources/materiallist
) )
if(NOT BUILD_WITH_KF) if(NOT BUILD_WITH_KF)
@@ -357,6 +370,10 @@ if (NOT MINGW)
install(DIRECTORY elements DESTINATION share/qelectrotech) install(DIRECTORY elements DESTINATION share/qelectrotech)
install(DIRECTORY examples DESTINATION share/qelectrotech) install(DIRECTORY examples DESTINATION share/qelectrotech)
install(DIRECTORY titleblocks DESTINATION share/qelectrotech) install(DIRECTORY titleblocks DESTINATION share/qelectrotech)
# The MCP server (misc/qet-mcp) finds this QElectroTech and its elements
# from here: <prefix>/share/qelectrotech/mcp -> <prefix>/bin.
install(PROGRAMS misc/qet-mcp/qet_mcp.py DESTINATION share/qelectrotech/mcp)
install(FILES misc/qet-mcp/README.md DESTINATION share/qelectrotech/mcp)
install(FILES LICENSE ELEMENTS.LICENSE CREDIT README ChangeLog DESTINATION share/doc/qelectrotech) install(FILES LICENSE ELEMENTS.LICENSE CREDIT README ChangeLog DESTINATION share/doc/qelectrotech)
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
install(FILES misc/org.qelectrotech.qelectrotech.desktop DESTINATION share/applications) install(FILES misc/org.qelectrotech.qelectrotech.desktop DESTINATION share/applications)
+151
View File
@@ -12,6 +12,157 @@ Here are some important resources:
## Testing ## Testing
### Build the tests
Start with the dependencies and platform-specific setup in
[INSTALL.md](INSTALL.md). Tests additionally require the Qt6 Test module.
The commands below use CMake/CTest 3.20 or newer (`--test-dir` needs 3.20),
a C++17 compiler, and the Qt6 development packages, including GuiPrivate,
Svg and LinguistTools. Configuration may download dependencies with
FetchContent, including Catch2, so an initial build needs network access.
Run the following commands from the repository root. Keep build products in
a separate directory and use Debug for the regression tests:
```sh
git submodule update --init --recursive
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DPACKAGE_TESTS=ON
cmake --build build --config Debug --parallel
```
`PACKAGE_TESTS` defaults to `ON`; specify it explicitly when reusing a build
directory that previously disabled tests. `BUILD_WITH_KF` defaults to `ON`.
If KDE Frameworks are unavailable, add `-DBUILD_WITH_KF=OFF` to the configure
command, as described in INSTALL.md. Use a separate build directory when
changing compiler, Qt kit, or generator.
### Run the tests with CTest
After a successful build, list the registered tests, run them, or select a
test by name with `-R`:
```sh
ctest --test-dir build -C Debug -N
ctest --test-dir build -C Debug --output-on-failure
ctest --test-dir build -C Debug -R '^tst_qetstrings$' --output-on-failure
```
`-N` only lists tests; it does not execute them. `-C Debug` selects the
configuration for multi-configuration generators such as Visual Studio.
For single-configuration generators such as Ninja and Unix Makefiles,
`CMAKE_BUILD_TYPE=Debug` selects it at configure time. CTest runs the tests
registered by CMake; it does not build their executables.
Some tests create Qt widgets and need a graphical environment. On a Linux
machine without a display, use Xvfb as the Linux CI does:
```sh
xvfb-run -a ctest --test-dir build -C Debug --output-on-failure
```
Tests that depend on optional features may not be registered. In particular,
several scripting tests require the Qt6 Qml module at configure time.
Review the test list and any skipped tests before reporting what was covered.
### Windows
Follow INSTALL.md for SQLite3 and the MSVC or MinGW toolchain. The compiler
and Qt kit must match. For MSVC, use a Visual Studio developer PowerShell;
the example below assumes Visual Studio 2022 and a 64-bit MSVC Qt kit.
Replace the example Qt path with your installation, and supply any SQLite3
paths or toolchain file required by your setup:
```powershell
$qtPrefix = 'C:/Qt/6.x.x/msvc2022_64'
cmake -S . -B build-msvc -G 'Visual Studio 17 2022' -A x64 `
"-DCMAKE_PREFIX_PATH=$qtPrefix" -DBUILD_WITH_KF=OFF -DPACKAGE_TESTS=ON
cmake --build build-msvc --config Debug --parallel
$env:Path = "$qtPrefix/bin;$env:Path"
ctest --test-dir build-msvc -C Debug --output-on-failure
```
Putting the matching Qt `bin` directory on `PATH` lets test executables find
Qt DLLs. Other shared dependencies must also be available on `PATH`.
For MinGW with Ninja, use the matching MinGW Qt kit and compiler environment,
omit `-A x64`, select `-G Ninja`, and set `-DCMAKE_BUILD_TYPE=Debug`.
The Linux shell regression scripts below do not run natively on Windows.
### Tests outside the CTest suite
The Catch2 executable `C_unittests` is built with `PACKAGE_TESTS=ON`, but is
currently not registered with CTest. Run it separately from the repository
root after building:
```sh
./build/tests/catch/C_unittests
```
This executable creates a Qt GUI application too. On headless Linux, use:
```sh
xvfb-run -a ./build/tests/catch/C_unittests
```
For the Visual Studio example above, use this PowerShell command after
setting up `PATH`:
```powershell
& ./build-msvc/tests/catch/Debug/C_unittests.exe
```
The Python MCP suite is also separate from CTest. Its unit and protocol
tests can be run without a QElectroTech binary. Some fixtures launch POSIX
shell scripts, so use a Unix-like environment for this suite:
```sh
python3 misc/qet-mcp/test_qet_mcp.py
```
Use `python` instead of `python3` if that is your interpreter's command.
Integration tests need a built QElectroTech and the environment variables
documented in [the MCP testing guide](misc/qet-mcp/README.md#testing),
including `QET_ENABLE_SCRIPTING=1` for script-driven tests. Missing integration
prerequisites cause tests to be skipped, so a successful unit run alone does
not establish integration coverage.
### Linux regression prerequisites
In addition to the build dependencies in INSTALL.md, headless widget tests
need `xvfb-run`, Xvfb and `xauth`. The existing
[Linux workflow](.github/workflows/linux-build.yml) lists the packages used
by CI, including the QtSvg development package and SQLite driver for QtSql.
Package names vary by distribution.
The IPC open-forwarding regression requires Bash, Xvfb, Openbox and xdotool.
Use a Qt6 Debug build; the documented reproduction used Qt 6.10.2. It uses
`examples/industrial.qet` by default and starts its own display, so run it
directly rather than wrapping it in `xvfb-run`:
```sh
tests/ipc-regression/run.sh --binary build/qelectrotech
```
This test is separate from CTest. Exit code `0` means the scenario survived,
`1` means a crash, and `2` means inconclusive or unusable input. An inconclusive
run is not a pass. See [the IPC guide](tests/ipc-regression/README.md).
The quit-during-modal regression needs gdb with Python support and a binary
with the symbols used by the test (use an unstripped Debug build). It uses
Qt's offscreen platform and needs no X server. It is registered with CTest
on Linux and can be selected with:
```sh
ctest --test-dir build -C Debug -R '^modal_quit_regression$' --output-on-failure
```
Missing gdb, missing Python support in gdb, or a stripped binary can result
in exit code `77`, which CTest treats as skipped. See
[the modal-quit guide](tests/modal-quit-regression/README.md).
When submitting changes, state the build configuration, commands actually
run, results, and any skipped or unavailable tests. If commands were only
checked against the build files or documentation, say so explicitly.
## Submitting changes ## Submitting changes
Always write a clear log message for your commits. Always write a clear log message for your commits.
+30
View File
@@ -58,6 +58,36 @@ Thanks to Elbert from the NL for packaging OS/2
Thanks to zloidemon from for packaging FreeBsd (port GCC) Thanks to zloidemon from for packaging FreeBsd (port GCC)
Thanks to Mrbit for ebuild Gentoo packaging. Thanks to Mrbit for ebuild Gentoo packaging.
[de]
Dank an Qt Software für die Qt-Bibliothek (http://www.qtsoftware.com/), lizenziert unter der GNU/GPL.
Dank an das KDE-Projekt (http://www.kde.org/).
Dank an Loic für seine mathematischen Erläuterungen.
Dank an Remi Collet für die Fedora-Paketierung.
Dank an Laurent Trinques für die Debian-Paketierung.
Dank an „trem“ für die Mandriva-Paketierung.
Dank an TuxFamily (http://tuxfamily.org/) für das Hosting des Projekts.
Dank an „Nishiki“ für seine Elemente und seine Unterstützung.
Dank an qtcentre.org für die „SingleApplication“-Klasse.
Dank an Alfredo Carreto für seine spanischen Übersetzungen und die Korrektur der spanischen Übersetzungen (http://electronicosmx.net)
Dank an „Dr.Slump“ und Sivio für ihre Übersetzungen ins Italienische.
Dank an Jose Carlos Martins für seine Übersetzungen ins Portugiesische.
Dank an Pavel Fric für seine Übersetzungen ins Tschechische.
Dank an Pawel Smiech für seine polnischen Übersetzungen.
Dank an Yuriy Litkevich für seine Übersetzungen ins Russische.
Dank an Youssef Ouamalkran für seine Übersetzungen ins Katalanische.
Dank an Gabi Mandoc für seine Übersetzungen ins Rumänische.
Dank an Markus Budde, Jonas Stein und Noah Braden für ihre Übersetzungen ins Deutsche.
Dank an Mohamed Souabni für seine Übersetzungen ins Arabische.
Dank an Uroš Platiše für seine Übersetzungen ins Slowenische.
Dank an Antun Marakovic für seine Übersetzungen ins Kroatische.
Dank an Nikos Papadopoylos und Yannis Gyftomitros für ihre griechischen Übersetzungen.
Dank an Markos Chandras für die Gentoo-Paketierung.
Dank an David für die Paketierung von Slackware.
Dank an Chipsterjulien für die Paketierung der Archlinux-AUR-Pakete.
Dank an Elbert aus den Niederlanden für die Paketierung von OS/2.
Dank an zloidemon für die Paketierung von FreeBSD (GCC-Port).
Dank an Mrbit für die ebuild-Paketierung für Gentoo.
[fr] [fr]
Merci à Qt Software pour la bibliothèque Qt ( http://www.qtsoftware.com/ ), sous licence GNU/GPL. Merci à Qt Software pour la bibliothèque Qt ( http://www.qtsoftware.com/ ), sous licence GNU/GPL.
Merci au projet KDE ( http://www.kde.org/ ). Merci au projet KDE ( http://www.kde.org/ ).
@@ -13,7 +13,7 @@
"finish-args": [ "finish-args": [
"--socket=wayland", "--socket=wayland",
"--socket=fallback-x11", "--socket=fallback-x11",
"--device=dri", "--device=all",
"--share=ipc", "--share=ipc",
"--share=network", "--share=network",
"--socket=cups", "--socket=cups",
@@ -33,6 +33,23 @@
"modules": [ "modules": [
"tkinter.json", "tkinter.json",
"pypi-dependencies.json", "pypi-dependencies.json",
{
"name": "hidapi",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DCMAKE_INSTALL_PREFIX=/app",
"-DCMAKE_BUILD_TYPE=Release",
"-DHIDAPI_BUILD_HIDTEST=OFF",
"-DHIDAPI_WITH_TESTS=OFF"
],
"sources": [
{
"type": "git",
"url": "https://github.com/libusb/hidapi.git",
"tag": "hidapi-0.15.0"
}
]
},
{ {
"name": "qelectrotech", "name": "qelectrotech",
"buildsystem": "cmake", "buildsystem": "cmake",
@@ -45,7 +62,9 @@
"-DBUILD_PUGIXML=ON", "-DBUILD_PUGIXML=ON",
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON", "-DFETCHCONTENT_FULLY_DISCONNECTED=ON",
"-DFETCHCONTENT_SOURCE_DIR_PUGIXML=/run/build/qelectrotech/pugixml", "-DFETCHCONTENT_SOURCE_DIR_PUGIXML=/run/build/qelectrotech/pugixml",
"-DQET_EXPORT_PROJECT_DB=ON" "-DQET_EXPORT_PROJECT_DB=ON",
"-DQET_ENABLE_SPACEMOUSE=ON",
"-DQET_SPACEMOUSE_BACKEND=hid"
], ],
"post-install": [ "post-install": [
"mv ${FLATPAK_DEST}/share/mime/packages/qelectrotech.xml ${FLATPAK_DEST}/share/mime/packages/org.qelectrotech.QElectroTech.xml" "mv ${FLATPAK_DEST}/share/mime/packages/qelectrotech.xml ${FLATPAK_DEST}/share/mime/packages/org.qelectrotech.QElectroTech.xml"
+17
View File
@@ -274,6 +274,21 @@ Section "$(Fonts)" SEC05
File /nonfatal /r "./files/fonts" File /nonfatal /r "./files/fonts"
SectionEnd SectionEnd
; The MCP server lets an AI assistant open, check and edit drawings. It does
; nothing until a user sets up an assistant to start it.
Section "$(Mcp)" SEC06
SetOutPath "$INSTDIR\mcp"
File /nonfatal "./files/mcp/*"
SectionEnd
; Python for the MCP server, from python.org (about 12 MB). Unticked by
; default: only someone setting up an AI assistant needs it, and then only
; without a Python of their own. Uninstall removes it with $INSTDIR.
Section /o "$(McpPython)" SEC07
SetOutPath "$INSTDIR\mcp\python"
File /nonfatal /r "./files/mcp/python/*"
SectionEnd
;-------------------------------- ;--------------------------------
; Component descriptions ; Component descriptions
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
@@ -282,6 +297,8 @@ SectionEnd
!insertmacro MUI_DESCRIPTION_TEXT ${SEC03} $(var3) !insertmacro MUI_DESCRIPTION_TEXT ${SEC03} $(var3)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC04} $(var4) !insertmacro MUI_DESCRIPTION_TEXT ${SEC04} $(var4)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC05} $(var5) !insertmacro MUI_DESCRIPTION_TEXT ${SEC05} $(var5)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC06} $(var6)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC07} $(var7)
!insertmacro MUI_FUNCTION_DESCRIPTION_END !insertmacro MUI_FUNCTION_DESCRIPTION_END
;-------------------------------- ;--------------------------------
+64
View File
@@ -19,6 +19,10 @@
LangString var3 ${LANG_ENGLISH} "Examples of cartridges" LangString var3 ${LANG_ENGLISH} "Examples of cartridges"
LangString var4 ${LANG_ENGLISH} "Examples of diagrams" LangString var4 ${LANG_ENGLISH} "Examples of diagrams"
LangString var5 ${LANG_ENGLISH} "Fonts" LangString var5 ${LANG_ENGLISH} "Fonts"
LangString Mcp ${LANG_ENGLISH} "AI assistant (MCP)"
LangString var6 ${LANG_ENGLISH} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_ENGLISH} "Python for the AI assistant"
LangString var7 ${LANG_ENGLISH} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_ENGLISH} "Uninstallation of the previous version failed.$\nPlease uninstall ${SOFT_NAME} manually before continuing." LangString uninstFailed ${LANG_ENGLISH} "Uninstallation of the previous version failed.$\nPlease uninstall ${SOFT_NAME} manually before continuing."
@@ -43,6 +47,10 @@
LangString var3 ${LANG_KOREAN} "표제란 예제" LangString var3 ${LANG_KOREAN} "표제란 예제"
LangString var4 ${LANG_KOREAN} "도면 예제" LangString var4 ${LANG_KOREAN} "도면 예제"
LangString var5 ${LANG_KOREAN} "글꼴" LangString var5 ${LANG_KOREAN} "글꼴"
LangString Mcp ${LANG_KOREAN} "AI assistant (MCP)"
LangString var6 ${LANG_KOREAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_KOREAN} "Python for the AI assistant"
LangString var7 ${LANG_KOREAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_KOREAN} "이전 버전을 제거하지 못했습니다.$\n계속하기 전에 ${SOFT_NAME}을(를) 수동으로 제거해 주세요." LangString uninstFailed ${LANG_KOREAN} "이전 버전을 제거하지 못했습니다.$\n계속하기 전에 ${SOFT_NAME}을(를) 수동으로 제거해 주세요."
@@ -67,6 +75,10 @@
LangString var3 ${LANG_POLISH} "Przykłady tabliczek rysunkowych" LangString var3 ${LANG_POLISH} "Przykłady tabliczek rysunkowych"
LangString var4 ${LANG_POLISH} "Przykłady schematów" LangString var4 ${LANG_POLISH} "Przykłady schematów"
LangString var5 ${LANG_POLISH} "Czcionki" LangString var5 ${LANG_POLISH} "Czcionki"
LangString Mcp ${LANG_POLISH} "AI assistant (MCP)"
LangString var6 ${LANG_POLISH} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_POLISH} "Python for the AI assistant"
LangString var7 ${LANG_POLISH} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_POLISH} "Odinstalowanie poprzedniej wersji nie powiodło się.$\nPrzed kontynuowaniem odinstaluj ręcznie program ${SOFT_NAME}." LangString uninstFailed ${LANG_POLISH} "Odinstalowanie poprzedniej wersji nie powiodło się.$\nPrzed kontynuowaniem odinstaluj ręcznie program ${SOFT_NAME}."
@@ -91,6 +103,10 @@
LangString var3 ${LANG_GREEK} "Παραδείγματα πινακίδων" LangString var3 ${LANG_GREEK} "Παραδείγματα πινακίδων"
LangString var4 ${LANG_GREEK} "Παραδείγματα διαγραμμάτων" LangString var4 ${LANG_GREEK} "Παραδείγματα διαγραμμάτων"
LangString var5 ${LANG_GREEK} "Γραμματοσειρές" LangString var5 ${LANG_GREEK} "Γραμματοσειρές"
LangString Mcp ${LANG_GREEK} "AI assistant (MCP)"
LangString var6 ${LANG_GREEK} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_GREEK} "Python for the AI assistant"
LangString var7 ${LANG_GREEK} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_GREEK} "Η απεγκατάσταση της προηγούμενης έκδοσης απέτυχε.$\nΠαρακαλώ απεγκαταστήστε χειροκίνητα το ${SOFT_NAME} πριν συνεχίσετε." LangString uninstFailed ${LANG_GREEK} "Η απεγκατάσταση της προηγούμενης έκδοσης απέτυχε.$\nΠαρακαλώ απεγκαταστήστε χειροκίνητα το ${SOFT_NAME} πριν συνεχίσετε."
@@ -115,6 +131,10 @@
LangString var3 ${LANG_CZECH} "Příklady popisových polí" LangString var3 ${LANG_CZECH} "Příklady popisových polí"
LangString var4 ${LANG_CZECH} "Příklady schémat" LangString var4 ${LANG_CZECH} "Příklady schémat"
LangString var5 ${LANG_CZECH} "Písma" LangString var5 ${LANG_CZECH} "Písma"
LangString Mcp ${LANG_CZECH} "AI assistant (MCP)"
LangString var6 ${LANG_CZECH} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_CZECH} "Python for the AI assistant"
LangString var7 ${LANG_CZECH} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_CZECH} "Odinstalování předchozí verze se nezdařilo.$\nPřed pokračováním prosím odinstalujte ${SOFT_NAME} ručně." LangString uninstFailed ${LANG_CZECH} "Odinstalování předchozí verze se nezdařilo.$\nPřed pokračováním prosím odinstalujte ${SOFT_NAME} ručně."
@@ -139,6 +159,10 @@
LangString var3 ${LANG_SPANISH} "Ejemplos de cartelas" LangString var3 ${LANG_SPANISH} "Ejemplos de cartelas"
LangString var4 ${LANG_SPANISH} "Ejemplos de esquemas" LangString var4 ${LANG_SPANISH} "Ejemplos de esquemas"
LangString var5 ${LANG_SPANISH} "Fuentes" LangString var5 ${LANG_SPANISH} "Fuentes"
LangString Mcp ${LANG_SPANISH} "AI assistant (MCP)"
LangString var6 ${LANG_SPANISH} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_SPANISH} "Python for the AI assistant"
LangString var7 ${LANG_SPANISH} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_SPANISH} "La desinstalación de la versión anterior ha fallado.$\nPor favor, desinstale ${SOFT_NAME} manualmente antes de continuar." LangString uninstFailed ${LANG_SPANISH} "La desinstalación de la versión anterior ha fallado.$\nPor favor, desinstale ${SOFT_NAME} manualmente antes de continuar."
@@ -163,6 +187,10 @@
LangString var3 ${LANG_GERMAN} "Schriftfeld-Beispiele" LangString var3 ${LANG_GERMAN} "Schriftfeld-Beispiele"
LangString var4 ${LANG_GERMAN} "Schaltplan-Beispiele" LangString var4 ${LANG_GERMAN} "Schaltplan-Beispiele"
LangString var5 ${LANG_GERMAN} "Schriften" LangString var5 ${LANG_GERMAN} "Schriften"
LangString Mcp ${LANG_GERMAN} "AI assistant (MCP)"
LangString var6 ${LANG_GERMAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_GERMAN} "Python for the AI assistant"
LangString var7 ${LANG_GERMAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_GERMAN} "Die Deinstallation der vorherigen Version ist fehlgeschlagen.$\nBitte deinstallieren Sie ${SOFT_NAME} manuell, bevor Sie fortfahren." LangString uninstFailed ${LANG_GERMAN} "Die Deinstallation der vorherigen Version ist fehlgeschlagen.$\nBitte deinstallieren Sie ${SOFT_NAME} manuell, bevor Sie fortfahren."
@@ -187,6 +215,10 @@
LangString var3 ${LANG_RUSSIAN} "Примеры штампов" LangString var3 ${LANG_RUSSIAN} "Примеры штампов"
LangString var4 ${LANG_RUSSIAN} "Примеры схем" LangString var4 ${LANG_RUSSIAN} "Примеры схем"
LangString var5 ${LANG_RUSSIAN} "Шрифты" LangString var5 ${LANG_RUSSIAN} "Шрифты"
LangString Mcp ${LANG_RUSSIAN} "AI assistant (MCP)"
LangString var6 ${LANG_RUSSIAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_RUSSIAN} "Python for the AI assistant"
LangString var7 ${LANG_RUSSIAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_RUSSIAN} "Удаление предыдущей версии завершилось с ошибкой.$\nПожалуйста, удалите ${SOFT_NAME} вручную перед продолжением." LangString uninstFailed ${LANG_RUSSIAN} "Удаление предыдущей версии завершилось с ошибкой.$\nПожалуйста, удалите ${SOFT_NAME} вручную перед продолжением."
@@ -211,6 +243,10 @@
LangString var3 ${LANG_ARABIC} "أمثلة على كتل العنوان" LangString var3 ${LANG_ARABIC} "أمثلة على كتل العنوان"
LangString var4 ${LANG_ARABIC} "أمثلة على المخططات" LangString var4 ${LANG_ARABIC} "أمثلة على المخططات"
LangString var5 ${LANG_ARABIC} "الخطوط" LangString var5 ${LANG_ARABIC} "الخطوط"
LangString Mcp ${LANG_ARABIC} "AI assistant (MCP)"
LangString var6 ${LANG_ARABIC} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_ARABIC} "Python for the AI assistant"
LangString var7 ${LANG_ARABIC} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_ARABIC} "فشل إلغاء تثبيت الإصدار السابق. يرجى إلغاء تثبيت ${SOFT_NAME} يدويًا قبل المتابعة." LangString uninstFailed ${LANG_ARABIC} "فشل إلغاء تثبيت الإصدار السابق. يرجى إلغاء تثبيت ${SOFT_NAME} يدويًا قبل المتابعة."
@@ -235,6 +271,10 @@
LangString var3 ${LANG_CATALAN} "Exemples de cartutxos" LangString var3 ${LANG_CATALAN} "Exemples de cartutxos"
LangString var4 ${LANG_CATALAN} "Exemples d'esquemes" LangString var4 ${LANG_CATALAN} "Exemples d'esquemes"
LangString var5 ${LANG_CATALAN} "Tipus de lletra" LangString var5 ${LANG_CATALAN} "Tipus de lletra"
LangString Mcp ${LANG_CATALAN} "AI assistant (MCP)"
LangString var6 ${LANG_CATALAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_CATALAN} "Python for the AI assistant"
LangString var7 ${LANG_CATALAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_CATALAN} "La desinstal·lació de la versió anterior ha fallat.$\nSi us plau, desinstal·leu ${SOFT_NAME} manualment abans de continuar." LangString uninstFailed ${LANG_CATALAN} "La desinstal·lació de la versió anterior ha fallat.$\nSi us plau, desinstal·leu ${SOFT_NAME} manualment abans de continuar."
@@ -259,6 +299,10 @@
LangString var3 ${LANG_ITALIAN} "Cartigli di esempio" LangString var3 ${LANG_ITALIAN} "Cartigli di esempio"
LangString var4 ${LANG_ITALIAN} "Schemi di esempio" LangString var4 ${LANG_ITALIAN} "Schemi di esempio"
LangString var5 ${LANG_ITALIAN} "Caratteri" LangString var5 ${LANG_ITALIAN} "Caratteri"
LangString Mcp ${LANG_ITALIAN} "AI assistant (MCP)"
LangString var6 ${LANG_ITALIAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_ITALIAN} "Python for the AI assistant"
LangString var7 ${LANG_ITALIAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_ITALIAN} "La disinstallazione della versione precedente non è riuscita.$\nSi prega di disinstallare ${SOFT_NAME} manualmente prima di continuare." LangString uninstFailed ${LANG_ITALIAN} "La disinstallazione della versione precedente non è riuscita.$\nSi prega di disinstallare ${SOFT_NAME} manualmente prima di continuare."
@@ -283,6 +327,10 @@
LangString var3 ${LANG_PORTUGUESE} "Exemplos de legendas" LangString var3 ${LANG_PORTUGUESE} "Exemplos de legendas"
LangString var4 ${LANG_PORTUGUESE} "Exemplos de esquemas" LangString var4 ${LANG_PORTUGUESE} "Exemplos de esquemas"
LangString var5 ${LANG_PORTUGUESE} "Fontes" LangString var5 ${LANG_PORTUGUESE} "Fontes"
LangString Mcp ${LANG_PORTUGUESE} "AI assistant (MCP)"
LangString var6 ${LANG_PORTUGUESE} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_PORTUGUESE} "Python for the AI assistant"
LangString var7 ${LANG_PORTUGUESE} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_PORTUGUESE} "A desinstalação da versão anterior falhou.$\nPor favor, desinstale ${SOFT_NAME} manualmente antes de continuar." LangString uninstFailed ${LANG_PORTUGUESE} "A desinstalação da versão anterior falhou.$\nPor favor, desinstale ${SOFT_NAME} manualmente antes de continuar."
@@ -307,6 +355,10 @@
LangString var3 ${LANG_ROMANIAN} "Exemple de cartușe" LangString var3 ${LANG_ROMANIAN} "Exemple de cartușe"
LangString var4 ${LANG_ROMANIAN} "Exemple de scheme" LangString var4 ${LANG_ROMANIAN} "Exemple de scheme"
LangString var5 ${LANG_ROMANIAN} "Fonturi" LangString var5 ${LANG_ROMANIAN} "Fonturi"
LangString Mcp ${LANG_ROMANIAN} "AI assistant (MCP)"
LangString var6 ${LANG_ROMANIAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_ROMANIAN} "Python for the AI assistant"
LangString var7 ${LANG_ROMANIAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_ROMANIAN} "Dezinstalarea versiunii anterioare a eșuat.$\nVă rugăm să dezinstalați ${SOFT_NAME} manual înainte de a continua." LangString uninstFailed ${LANG_ROMANIAN} "Dezinstalarea versiunii anterioare a eșuat.$\nVă rugăm să dezinstalați ${SOFT_NAME} manual înainte de a continua."
@@ -331,6 +383,10 @@
LangString var3 ${LANG_CROATIAN} "Primjeri zaglavlja" LangString var3 ${LANG_CROATIAN} "Primjeri zaglavlja"
LangString var4 ${LANG_CROATIAN} "Primjeri shema" LangString var4 ${LANG_CROATIAN} "Primjeri shema"
LangString var5 ${LANG_CROATIAN} "Fontovi" LangString var5 ${LANG_CROATIAN} "Fontovi"
LangString Mcp ${LANG_CROATIAN} "AI assistant (MCP)"
LangString var6 ${LANG_CROATIAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_CROATIAN} "Python for the AI assistant"
LangString var7 ${LANG_CROATIAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_CROATIAN} "Deinstalacija prethodne verzije nije uspjela.$\nMolimo deinstalirajte ${SOFT_NAME} ručno prije nastavka." LangString uninstFailed ${LANG_CROATIAN} "Deinstalacija prethodne verzije nije uspjela.$\nMolimo deinstalirajte ${SOFT_NAME} ručno prije nastavka."
@@ -355,6 +411,10 @@
LangString var3 ${LANG_DUTCH} "Voorbeelden van titelblokken" LangString var3 ${LANG_DUTCH} "Voorbeelden van titelblokken"
LangString var4 ${LANG_DUTCH} "Voorbeelden van schema's" LangString var4 ${LANG_DUTCH} "Voorbeelden van schema's"
LangString var5 ${LANG_DUTCH} "Lettertypen" LangString var5 ${LANG_DUTCH} "Lettertypen"
LangString Mcp ${LANG_DUTCH} "AI assistant (MCP)"
LangString var6 ${LANG_DUTCH} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_DUTCH} "Python for the AI assistant"
LangString var7 ${LANG_DUTCH} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_DUTCH} "Het verwijderen van de vorige versie is mislukt.$\nVerwijder ${SOFT_NAME} handmatig voordat u verdergaat." LangString uninstFailed ${LANG_DUTCH} "Het verwijderen van de vorige versie is mislukt.$\nVerwijder ${SOFT_NAME} handmatig voordat u verdergaat."
@@ -379,6 +439,10 @@
LangString var3 ${LANG_DANISH} "Titel blokke eksempler" LangString var3 ${LANG_DANISH} "Titel blokke eksempler"
LangString var4 ${LANG_DANISH} "Diagram eksempler" LangString var4 ${LANG_DANISH} "Diagram eksempler"
LangString var5 ${LANG_DANISH} "Skrifttyper" LangString var5 ${LANG_DANISH} "Skrifttyper"
LangString Mcp ${LANG_DANISH} "AI assistant (MCP)"
LangString var6 ${LANG_DANISH} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_DANISH} "Python for the AI assistant"
LangString var7 ${LANG_DANISH} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_DANISH} "Afinstallation af den tidligere version mislykkedes.$\nAfinstaller venligst ${SOFT_NAME} manuelt, inden du fortsætter." LangString uninstFailed ${LANG_DANISH} "Afinstallation af den tidligere version mislykkedes.$\nAfinstaller venligst ${SOFT_NAME} manuelt, inden du fortsætter."
+4
View File
@@ -19,4 +19,8 @@
LangString var3 ${LANG_FRENCH} "Exemples de cartouches" LangString var3 ${LANG_FRENCH} "Exemples de cartouches"
LangString var4 ${LANG_FRENCH} "Exemples de schémas" LangString var4 ${LANG_FRENCH} "Exemples de schémas"
LangString var5 ${LANG_FRENCH} "Polices" LangString var5 ${LANG_FRENCH} "Polices"
LangString Mcp ${LANG_FRENCH} "Assistant IA (MCP)"
LangString var6 ${LANG_FRENCH} "Permet à un assistant IA d'ouvrir, vérifier et modifier vos schémas. Nécessite Python : le vôtre, ou celui proposé ci-dessous"
LangString McpPython ${LANG_FRENCH} "Python pour l'assistant IA"
LangString var7 ${LANG_FRENCH} "Python de python.org, utilisé uniquement par le composant Assistant IA. Inutile si Python est déjà installé (environ 12 Mo)"
LangString uninstFailed ${LANG_FRENCH} "La désinstallation de la version précédente a échoué.$\nVeuillez désinstaller ${SOFT_NAME} manuellement avant de continuer." LangString uninstFailed ${LANG_FRENCH} "La désinstallation de la version précédente a échoué.$\nVeuillez désinstaller ${SOFT_NAME} manuellement avant de continuer."
+48
View File
@@ -25,6 +25,10 @@
LangString var3 ${LANG_HUNGARIAN} "Rajzfejléc-példák" LangString var3 ${LANG_HUNGARIAN} "Rajzfejléc-példák"
LangString var4 ${LANG_HUNGARIAN} "Kapcsolási rajz példák" LangString var4 ${LANG_HUNGARIAN} "Kapcsolási rajz példák"
LangString var5 ${LANG_HUNGARIAN} "Betűtípusok" LangString var5 ${LANG_HUNGARIAN} "Betűtípusok"
LangString Mcp ${LANG_HUNGARIAN} "AI assistant (MCP)"
LangString var6 ${LANG_HUNGARIAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_HUNGARIAN} "Python for the AI assistant"
LangString var7 ${LANG_HUNGARIAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_HUNGARIAN} "Az előző verzió eltávolítása nem sikerült.$\nKérjük, távolítsa el manuálisan a ${SOFT_NAME} programot, mielőtt folytatná." LangString uninstFailed ${LANG_HUNGARIAN} "Az előző verzió eltávolítása nem sikerült.$\nKérjük, távolítsa el manuálisan a ${SOFT_NAME} programot, mielőtt folytatná."
@@ -52,6 +56,10 @@
LangString var3 ${LANG_JAPANESE} "表題欄の例" LangString var3 ${LANG_JAPANESE} "表題欄の例"
LangString var4 ${LANG_JAPANESE} "回路図の例" LangString var4 ${LANG_JAPANESE} "回路図の例"
LangString var5 ${LANG_JAPANESE} "フォント" LangString var5 ${LANG_JAPANESE} "フォント"
LangString Mcp ${LANG_JAPANESE} "AI assistant (MCP)"
LangString var6 ${LANG_JAPANESE} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_JAPANESE} "Python for the AI assistant"
LangString var7 ${LANG_JAPANESE} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_JAPANESE} "前のバージョンのアンインストールに失敗しました。$\n続行する前に、${SOFT_NAME} を手動でアンインストールしてください。" LangString uninstFailed ${LANG_JAPANESE} "前のバージョンのアンインストールに失敗しました。$\n続行する前に、${SOFT_NAME} を手動でアンインストールしてください。"
@@ -79,6 +87,10 @@
LangString var3 ${LANG_MONGOLIAN} "Гарчгийн блокын жишээнүүд" LangString var3 ${LANG_MONGOLIAN} "Гарчгийн блокын жишээнүүд"
LangString var4 ${LANG_MONGOLIAN} "Схемийн жишээнүүд" LangString var4 ${LANG_MONGOLIAN} "Схемийн жишээнүүд"
LangString var5 ${LANG_MONGOLIAN} "Фонт" LangString var5 ${LANG_MONGOLIAN} "Фонт"
LangString Mcp ${LANG_MONGOLIAN} "AI assistant (MCP)"
LangString var6 ${LANG_MONGOLIAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_MONGOLIAN} "Python for the AI assistant"
LangString var7 ${LANG_MONGOLIAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_MONGOLIAN} "Өмнөх хувилбарыг устгаж чадсангүй.$\nҮргэлжлүүлэхийн өмнө ${SOFT_NAME}-г гараар устгана уу." LangString uninstFailed ${LANG_MONGOLIAN} "Өмнөх хувилбарыг устгаж чадсангүй.$\nҮргэлжлүүлэхийн өмнө ${SOFT_NAME}-г гараар устгана уу."
@@ -106,6 +118,10 @@
LangString var3 ${LANG_NORWEGIAN} "Eksempler på titelblokker" LangString var3 ${LANG_NORWEGIAN} "Eksempler på titelblokker"
LangString var4 ${LANG_NORWEGIAN} "Eksempler på skjemaer" LangString var4 ${LANG_NORWEGIAN} "Eksempler på skjemaer"
LangString var5 ${LANG_NORWEGIAN} "Skrifttyper" LangString var5 ${LANG_NORWEGIAN} "Skrifttyper"
LangString Mcp ${LANG_NORWEGIAN} "AI assistant (MCP)"
LangString var6 ${LANG_NORWEGIAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_NORWEGIAN} "Python for the AI assistant"
LangString var7 ${LANG_NORWEGIAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_NORWEGIAN} "Avinstallasjon av forrige versjon mislyktes.$\nVennligst avinstaller ${SOFT_NAME} manuelt før du fortsetter." LangString uninstFailed ${LANG_NORWEGIAN} "Avinstallasjon av forrige versjon mislyktes.$\nVennligst avinstaller ${SOFT_NAME} manuelt før du fortsetter."
@@ -143,6 +159,10 @@
LangString var3 ${LANG_PORTUGUESEBR} "Exemplos de legendas" LangString var3 ${LANG_PORTUGUESEBR} "Exemplos de legendas"
LangString var4 ${LANG_PORTUGUESEBR} "Exemplos de esquemas" LangString var4 ${LANG_PORTUGUESEBR} "Exemplos de esquemas"
LangString var5 ${LANG_PORTUGUESEBR} "Fontes" LangString var5 ${LANG_PORTUGUESEBR} "Fontes"
LangString Mcp ${LANG_PORTUGUESEBR} "AI assistant (MCP)"
LangString var6 ${LANG_PORTUGUESEBR} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_PORTUGUESEBR} "Python for the AI assistant"
LangString var7 ${LANG_PORTUGUESEBR} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_PORTUGUESEBR} "A desinstalação da versão anterior falhou.$\nPor favor, desinstale ${SOFT_NAME} manualmente antes de continuar." LangString uninstFailed ${LANG_PORTUGUESEBR} "A desinstalação da versão anterior falhou.$\nPor favor, desinstale ${SOFT_NAME} manualmente antes de continuar."
@@ -170,6 +190,10 @@
LangString var3 ${LANG_SERBIAN} "Примери заглавља" LangString var3 ${LANG_SERBIAN} "Примери заглавља"
LangString var4 ${LANG_SERBIAN} "Примери шема" LangString var4 ${LANG_SERBIAN} "Примери шема"
LangString var5 ${LANG_SERBIAN} "Фонтови" LangString var5 ${LANG_SERBIAN} "Фонтови"
LangString Mcp ${LANG_SERBIAN} "AI assistant (MCP)"
LangString var6 ${LANG_SERBIAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_SERBIAN} "Python for the AI assistant"
LangString var7 ${LANG_SERBIAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_SERBIAN} "Деинсталација претходне верзије није успела.$\nМолимо деинсталирајте ${SOFT_NAME} ручно пре наставка." LangString uninstFailed ${LANG_SERBIAN} "Деинсталација претходне верзије није успела.$\nМолимо деинсталирајте ${SOFT_NAME} ручно пре наставка."
@@ -197,6 +221,10 @@
LangString var3 ${LANG_SLOVAK} "Príklady popisových polí" LangString var3 ${LANG_SLOVAK} "Príklady popisových polí"
LangString var4 ${LANG_SLOVAK} "Príklady schém" LangString var4 ${LANG_SLOVAK} "Príklady schém"
LangString var5 ${LANG_SLOVAK} "Písma" LangString var5 ${LANG_SLOVAK} "Písma"
LangString Mcp ${LANG_SLOVAK} "AI assistant (MCP)"
LangString var6 ${LANG_SLOVAK} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_SLOVAK} "Python for the AI assistant"
LangString var7 ${LANG_SLOVAK} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_SLOVAK} "Odinštalovanie predchádzajúcej verzie zlyhalo.$\nPred pokračovaním odinštalujte ${SOFT_NAME} ručne." LangString uninstFailed ${LANG_SLOVAK} "Odinštalovanie predchádzajúcej verzie zlyhalo.$\nPred pokračovaním odinštalujte ${SOFT_NAME} ručne."
@@ -224,6 +252,10 @@
LangString var3 ${LANG_SLOVENIAN} "Primeri glav risb" LangString var3 ${LANG_SLOVENIAN} "Primeri glav risb"
LangString var4 ${LANG_SLOVENIAN} "Primeri shem" LangString var4 ${LANG_SLOVENIAN} "Primeri shem"
LangString var5 ${LANG_SLOVENIAN} "Pisave" LangString var5 ${LANG_SLOVENIAN} "Pisave"
LangString Mcp ${LANG_SLOVENIAN} "AI assistant (MCP)"
LangString var6 ${LANG_SLOVENIAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_SLOVENIAN} "Python for the AI assistant"
LangString var7 ${LANG_SLOVENIAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_SLOVENIAN} "Odstranitev prejšnje različice ni uspela.$\nPred nadaljevanjem ročno odstranite ${SOFT_NAME}." LangString uninstFailed ${LANG_SLOVENIAN} "Odstranitev prejšnje različice ni uspela.$\nPred nadaljevanjem ročno odstranite ${SOFT_NAME}."
@@ -251,6 +283,10 @@
LangString var3 ${LANG_SWEDISH} "Exempel på ritningshuvuden" LangString var3 ${LANG_SWEDISH} "Exempel på ritningshuvuden"
LangString var4 ${LANG_SWEDISH} "Exempel på scheman" LangString var4 ${LANG_SWEDISH} "Exempel på scheman"
LangString var5 ${LANG_SWEDISH} "Teckensnitt" LangString var5 ${LANG_SWEDISH} "Teckensnitt"
LangString Mcp ${LANG_SWEDISH} "AI assistant (MCP)"
LangString var6 ${LANG_SWEDISH} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_SWEDISH} "Python for the AI assistant"
LangString var7 ${LANG_SWEDISH} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_SWEDISH} "Avinstallationen av den föregående versionen misslyckades.$\nAvinstallera ${SOFT_NAME} manuellt innan du fortsätter." LangString uninstFailed ${LANG_SWEDISH} "Avinstallationen av den föregående versionen misslyckades.$\nAvinstallera ${SOFT_NAME} manuellt innan du fortsätter."
@@ -278,6 +314,10 @@
LangString var3 ${LANG_TURKISH} "Başlık bloğu örnekleri" LangString var3 ${LANG_TURKISH} "Başlık bloğu örnekleri"
LangString var4 ${LANG_TURKISH} "Şema örnekleri" LangString var4 ${LANG_TURKISH} "Şema örnekleri"
LangString var5 ${LANG_TURKISH} "Yazı Tipleri" LangString var5 ${LANG_TURKISH} "Yazı Tipleri"
LangString Mcp ${LANG_TURKISH} "AI assistant (MCP)"
LangString var6 ${LANG_TURKISH} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_TURKISH} "Python for the AI assistant"
LangString var7 ${LANG_TURKISH} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_TURKISH} "Önceki sürümün kaldırılması başarısız oldu.$\nDevam etmeden önce lütfen ${SOFT_NAME}'i manuel olarak kaldırın." LangString uninstFailed ${LANG_TURKISH} "Önceki sürümün kaldırılması başarısız oldu.$\nDevam etmeden önce lütfen ${SOFT_NAME}'i manuel olarak kaldırın."
@@ -305,6 +345,10 @@
LangString var3 ${LANG_UKRAINIAN} "Приклади штампів" LangString var3 ${LANG_UKRAINIAN} "Приклади штампів"
LangString var4 ${LANG_UKRAINIAN} "Приклади схем" LangString var4 ${LANG_UKRAINIAN} "Приклади схем"
LangString var5 ${LANG_UKRAINIAN} "Шрифти" LangString var5 ${LANG_UKRAINIAN} "Шрифти"
LangString Mcp ${LANG_UKRAINIAN} "AI assistant (MCP)"
LangString var6 ${LANG_UKRAINIAN} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_UKRAINIAN} "Python for the AI assistant"
LangString var7 ${LANG_UKRAINIAN} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_UKRAINIAN} "Видалення попередньої версії завершилося помилкою.$\nБудь ласка, видаліть ${SOFT_NAME} вручну перед продовженням." LangString uninstFailed ${LANG_UKRAINIAN} "Видалення попередньої версії завершилося помилкою.$\nБудь ласка, видаліть ${SOFT_NAME} вручну перед продовженням."
@@ -332,4 +376,8 @@
LangString var3 ${LANG_SIMPCHINESE} "标题栏示例" LangString var3 ${LANG_SIMPCHINESE} "标题栏示例"
LangString var4 ${LANG_SIMPCHINESE} "图纸示例" LangString var4 ${LANG_SIMPCHINESE} "图纸示例"
LangString var5 ${LANG_SIMPCHINESE} "字体" LangString var5 ${LANG_SIMPCHINESE} "字体"
LangString Mcp ${LANG_SIMPCHINESE} "AI assistant (MCP)"
LangString var6 ${LANG_SIMPCHINESE} "Lets an AI assistant open, check and edit your drawings. Needs Python: your own, or the one offered below"
LangString McpPython ${LANG_SIMPCHINESE} "Python for the AI assistant"
LangString var7 ${LANG_SIMPCHINESE} "Python from python.org, used only by the AI assistant component. Not needed if Python is already installed (about 12 MB)"
LangString uninstFailed ${LANG_SIMPCHINESE} "卸载旧版本失败。$\n请在继续之前手动卸载 ${SOFT_NAME}。" LangString uninstFailed ${LANG_SIMPCHINESE} "卸载旧版本失败。$\n请在继续之前手动卸载 ${SOFT_NAME}。"
@@ -1,5 +1,12 @@
Windows Registry Editor Version 5.00 Windows Registry Editor Version 5.00
[-HKEY_CURRENT_USER\Software\Classes\Applications\qelectrotech.exe\shell\open\command]
[-HKEY_CURRENT_USER\Software\Classes\.qet]
[-HKEY_CURRENT_USER\Software\Classes\qet_diagram_file]
[-HKEY_CURRENT_USER\Software\Classes\.elmt]
[-HKEY_CURRENT_USER\Software\Classes\qet_element_file]
[-HKEY_CURRENT_USER\Software\Classes\.titleblock]
[-HKEY_CURRENT_USER\Software\Classes\qet_titleblock_file]
[-HKEY_CLASSES_ROOT\Applications\qelectrotech.exe\shell\open\command] [-HKEY_CLASSES_ROOT\Applications\qelectrotech.exe\shell\open\command]
[-HKEY_CLASSES_ROOT\.qet] [-HKEY_CLASSES_ROOT\.qet]
[-HKEY_CLASSES_ROOT\qet_diagram_file] [-HKEY_CLASSES_ROOT\qet_diagram_file]
@@ -5,7 +5,9 @@ rem detecte le dossier courant et suppose que celui-ci contient bin\qelectrotech
set current_dir=%~dp0 set current_dir=%~dp0
cd /d %current_dir% cd /d %current_dir%
set expected_qet_exe=%current_dir%Lancer QET.bat rem l'archive portable fournit "Lancer QET_qt6.bat", l'installeur "Lancer QET.bat"
set expected_qet_exe=%current_dir%Lancer QET_qt6.bat
if not exist "%expected_qet_exe%" set expected_qet_exe=%current_dir%Lancer QET.bat
set expected_project_ico=%current_dir%ico\application-x-qet-project.ico set expected_project_ico=%current_dir%ico\application-x-qet-project.ico
set expected_element_ico=%current_dir%ico\application-x-qet-element.ico set expected_element_ico=%current_dir%ico\application-x-qet-element.ico
set expected_titleblock_ico=%current_dir%ico\application-x-qet-titleblock.ico set expected_titleblock_ico=%current_dir%ico\application-x-qet-titleblock.ico
@@ -45,49 +47,50 @@ set final_element_ico=%expected_element_ico:\=\\%
set final_titleblock_ico=%expected_titleblock_ico:\=\\% set final_titleblock_ico=%expected_titleblock_ico:\=\\%
rem genere le fichier .reg pour enregistrer les associations de fichiers rem genere le fichier .reg pour enregistrer les associations de fichiers
rem HKEY_CURRENT_USER : ne demande pas les droits administrateur
set reg_file=qet_install_file_associations.reg set reg_file=qet_install_file_associations.reg
( (
echo Windows Registry Editor Version 5.00 echo Windows Registry Editor Version 5.00
echo. echo.
rem Declaration de l'application rem Declaration de l'application
echo [HKEY_CLASSES_ROOT\Applications\qelectrotech.exe\shell\open\command] echo [HKEY_CURRENT_USER\Software\Classes\Applications\qelectrotech.exe\shell\open\command]
echo @="\"%final_qet_exe%\" \"%%1\"" echo @="\"%final_qet_exe%\" \"%%1\""
rem association de fichier *.qet rem association de fichier *.qet
echo [HKEY_CLASSES_ROOT\.qet] echo [HKEY_CURRENT_USER\Software\Classes\.qet]
echo @="qet_diagram_file" echo @="qet_diagram_file"
echo [HKEY_CLASSES_ROOT\qet_diagram_file] echo [HKEY_CURRENT_USER\Software\Classes\qet_diagram_file]
echo @="QET diagram" echo @="QET diagram"
echo "EditFlags"=dword:00000000 echo "EditFlags"=dword:00000000
echo "BrowserFlags"=dword:00000008 echo "BrowserFlags"=dword:00000008
echo [HKEY_CLASSES_ROOT\qet_diagram_file\DefaultIcon] echo [HKEY_CURRENT_USER\Software\Classes\qet_diagram_file\DefaultIcon]
echo @="%final_project_ico%,0" echo @="%final_project_ico%,0"
echo [HKEY_CLASSES_ROOT\qet_diagram_file\shell\open\command] echo [HKEY_CURRENT_USER\Software\Classes\qet_diagram_file\shell\open\command]
echo @="\"%final_qet_exe%\" \"%%1\"" echo @="\"%final_qet_exe%\" \"%%1\""
rem association de fichier *.elmt rem association de fichier *.elmt
echo [HKEY_CLASSES_ROOT\.elmt] echo [HKEY_CURRENT_USER\Software\Classes\.elmt]
echo @="qet_element_file" echo @="qet_element_file"
echo [HKEY_CLASSES_ROOT\qet_element_file] echo [HKEY_CURRENT_USER\Software\Classes\qet_element_file]
echo @="QET element" echo @="QET element"
echo "EditFlags"=dword:00000000 echo "EditFlags"=dword:00000000
echo "BrowserFlags"=dword:00000008 echo "BrowserFlags"=dword:00000008
echo [HKEY_CLASSES_ROOT\qet_element_file\DefaultIcon] echo [HKEY_CURRENT_USER\Software\Classes\qet_element_file\DefaultIcon]
echo @="%final_element_ico%,0" echo @="%final_element_ico%,0"
echo [HKEY_CLASSES_ROOT\qet_element_file\shell\open\command] echo [HKEY_CURRENT_USER\Software\Classes\qet_element_file\shell\open\command]
echo @="\"%final_qet_exe%\" \"%%1\"" echo @="\"%final_qet_exe%\" \"%%1\""
rem association de fichier *.titleblock rem association de fichier *.titleblock
echo [HKEY_CLASSES_ROOT\.titleblock] echo [HKEY_CURRENT_USER\Software\Classes\.titleblock]
echo @="qet_titleblock_file" echo @="qet_titleblock_file"
echo [HKEY_CLASSES_ROOT\qet_titleblock_file] echo [HKEY_CURRENT_USER\Software\Classes\qet_titleblock_file]
echo @="QET title block template" echo @="QET title block template"
echo "EditFlags"=dword:00000000 echo "EditFlags"=dword:00000000
echo "BrowserFlags"=dword:00000008 echo "BrowserFlags"=dword:00000008
echo [HKEY_CLASSES_ROOT\qet_titleblock_file\DefaultIcon] echo [HKEY_CURRENT_USER\Software\Classes\qet_titleblock_file\DefaultIcon]
echo @="%final_titleblock_ico%,0" echo @="%final_titleblock_ico%,0"
echo [HKEY_CLASSES_ROOT\qet_titleblock_file\shell\open\command] echo [HKEY_CURRENT_USER\Software\Classes\qet_titleblock_file\shell\open\command]
echo @="\"%final_qet_exe%\" \"%%1\"" echo @="\"%final_qet_exe%\" \"%%1\""
) > %reg_file% ) > %reg_file%
@@ -0,0 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Embeds qelectrotech.exe.manifest as the program's manifest
// (resource id 1 = CREATEPROCESS_MANIFEST_RESOURCE_ID, type 24 = RT_MANIFEST).
1 24 "@QET_MANIFEST_FILE@"
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- SPDX-License-Identifier: GPL-2.0-or-later -->
<!--
Application manifest for QElectroTech.exe, embedded by CMake on Windows.
It replaces the toolchain's default manifest, so it repeats what that one
declares (supported Windows versions, run as the invoking user) and adds
longPathAware: with LongPathsEnabled set in the registry, Windows then
lets the program open files whose full path is 260 characters or more.
-->
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
</assembly>
+2 -2
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
@@ -16,7 +16,7 @@
message( message(
"_____________________________________________________________________" "_____________________________________________________________________"
"\nCopyright 2006 The QElectroTech Team" "\nCopyright 2006-2026 The QElectroTech Team"
"\nThis file is part of QElectroTech." "\nThis file is part of QElectroTech."
"\n\nQElectroTech is free software: you can redistribute it and/or modify" "\n\nQElectroTech is free software: you can redistribute it and/or modify"
+2 -2
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
@@ -63,7 +63,7 @@ message("QET_DIR :" ${QET_DIR})
message("GIT_COMMIT_SHA :" ${GIT_COMMIT_SHA}) message("GIT_COMMIT_SHA :" ${GIT_COMMIT_SHA})
if(BUILD_WITH_KF) if(BUILD_WITH_KF)
message("KF_GIT_TAG :" ${KF_GIT_TAG}) message("KF_GIT_TAG :" ${KF_GIT_TAG})
else() else()
add_definitions(-DBUILD_WITHOUT_KF) add_definitions(-DBUILD_WITHOUT_KF)
endif() endif()
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
+13 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
@@ -25,6 +25,7 @@ message(" - find_spacemouse")
# spnav Linux, through the spacenavd daemon (libspnav) # spnav Linux, through the spacenavd daemon (libspnav)
# hid any platform, directly over USB (hidapi), no 3Dconnexion driver # hid any platform, directly over USB (hidapi), no 3Dconnexion driver
# auto spnav on Linux when libspnav is found, hid otherwise # auto spnav on Linux when libspnav is found, hid otherwise
# On macOS the 3DxWare backend is added to whichever of these is chosen.
# A backend whose library is not found downgrades the option to off with a # A backend whose library is not found downgrades the option to off with a
# warning, rather than failing configure for an opt-in feature. # warning, rather than failing configure for an opt-in feature.
set(QET_SPACEMOUSE_BACKEND "auto" CACHE STRING "3D mouse backend: auto, spnav or hid") set(QET_SPACEMOUSE_BACKEND "auto" CACHE STRING "3D mouse backend: auto, spnav or hid")
@@ -33,6 +34,7 @@ set_property(CACHE QET_SPACEMOUSE_BACKEND PROPERTY STRINGS auto spnav hid)
set(QET_SPACEMOUSE_ENABLED FALSE) set(QET_SPACEMOUSE_ENABLED FALSE)
set(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED FALSE) set(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED FALSE)
set(QET_SPACEMOUSE_BACKEND_HID_ENABLED FALSE) set(QET_SPACEMOUSE_BACKEND_HID_ENABLED FALSE)
set(QET_SPACEMOUSE_BACKEND_CONNEXION_ENABLED FALSE)
if(QET_ENABLE_SPACEMOUSE) if(QET_ENABLE_SPACEMOUSE)
find_package(PkgConfig) find_package(PkgConfig)
@@ -73,6 +75,16 @@ if(QET_ENABLE_SPACEMOUSE)
endif() endif()
endif() endif()
# macOS: with 3DxWare installed the device can only be read through
# 3DxWare, so its backend comes too, whichever backend was asked for.
# It loads 3DxWare's library at run time and needs nothing to build.
if(APPLE)
set(QET_SPACEMOUSE_ENABLED TRUE)
set(QET_SPACEMOUSE_BACKEND_CONNEXION_ENABLED TRUE)
add_definitions(-DQET_SPACEMOUSE_BACKEND_CONNEXION)
message("QET_ENABLE_SPACEMOUSE ON (backend: 3DxWare when installed)")
endif()
if(QET_SPACEMOUSE_ENABLED) if(QET_SPACEMOUSE_ENABLED)
add_definitions(-DQET_SPACEMOUSE_SUPPORT) add_definitions(-DQET_SPACEMOUSE_SUPPORT)
else() else()
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
+92 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
@@ -73,6 +73,7 @@ set(QET_RES_FILES
${QET_DIR}/sources/editor/ui/terminaleditor.ui ${QET_DIR}/sources/editor/ui/terminaleditor.ui
${QET_DIR}/sources/ElementsCollection/ui/renamedialog.ui ${QET_DIR}/sources/ElementsCollection/ui/renamedialog.ui
${QET_DIR}/sources/factory/ui/addtabledialog.ui ${QET_DIR}/sources/factory/ui/addtabledialog.ui
${QET_DIR}/sources/materiallist/materialselectiondialog.ui
${QET_DIR}/sources/NameList/ui/namelistdialog.ui ${QET_DIR}/sources/NameList/ui/namelistdialog.ui
${QET_DIR}/sources/NameList/ui/namelistwidget.ui ${QET_DIR}/sources/NameList/ui/namelistwidget.ui
${QET_DIR}/sources/print/projectprintwindow.ui ${QET_DIR}/sources/print/projectprintwindow.ui
@@ -168,11 +169,20 @@ set(QET_SRC_FILES
${QET_DIR}/sources/borderproperties.h ${QET_DIR}/sources/borderproperties.h
${QET_DIR}/sources/bordertitleblock.cpp ${QET_DIR}/sources/bordertitleblock.cpp
${QET_DIR}/sources/bordertitleblock.h ${QET_DIR}/sources/bordertitleblock.h
${QET_DIR}/sources/bordercelllabels.h
${QET_DIR}/sources/cellruler.cpp
${QET_DIR}/sources/cellruler.h
${QET_DIR}/sources/conductorautonumerotation.cpp ${QET_DIR}/sources/conductorautonumerotation.cpp
${QET_DIR}/sources/conductorautonumerotation.h ${QET_DIR}/sources/conductorautonumerotation.h
${QET_DIR}/sources/conductornumexport.cpp ${QET_DIR}/sources/conductornumexport.cpp
${QET_DIR}/sources/wiringlistexport.h ${QET_DIR}/sources/wiringlistexport.h
${QET_DIR}/sources/wiringlistexport.cpp ${QET_DIR}/sources/wiringlistexport.cpp
${QET_DIR}/sources/wirehops.h
${QET_DIR}/sources/wirehops.cpp
${QET_DIR}/sources/wiringrules.h
${QET_DIR}/sources/wiringrules.cpp
${QET_DIR}/sources/conductorrouter.h
${QET_DIR}/sources/conductorrouter.cpp
${QET_DIR}/sources/ui/wiringlistdialog.h ${QET_DIR}/sources/ui/wiringlistdialog.h
${QET_DIR}/sources/ui/wiringlistdialog.cpp ${QET_DIR}/sources/ui/wiringlistdialog.cpp
${QET_DIR}/sources/conductornumexport.h ${QET_DIR}/sources/conductornumexport.h
@@ -182,6 +192,7 @@ set(QET_SRC_FILES
${QET_DIR}/sources/conductorprofile.h ${QET_DIR}/sources/conductorprofile.h
${QET_DIR}/sources/conductorproperties.cpp ${QET_DIR}/sources/conductorproperties.cpp
${QET_DIR}/sources/conductorproperties.h ${QET_DIR}/sources/conductorproperties.h
${QET_DIR}/sources/conductormultiedit.h
${QET_DIR}/sources/conductorsegment.cpp ${QET_DIR}/sources/conductorsegment.cpp
${QET_DIR}/sources/conductorsegment.h ${QET_DIR}/sources/conductorsegment.h
${QET_DIR}/sources/conductorsegmentprofile.h ${QET_DIR}/sources/conductorsegmentprofile.h
@@ -189,6 +200,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/configdialog.h ${QET_DIR}/sources/configdialog.h
${QET_DIR}/sources/createdxf.cpp ${QET_DIR}/sources/createdxf.cpp
${QET_DIR}/sources/createdxf.h ${QET_DIR}/sources/createdxf.h
${QET_DIR}/sources/dxfexport.cpp
${QET_DIR}/sources/dxfexport.h
${QET_DIR}/sources/dxfpaintdevice.cpp ${QET_DIR}/sources/dxfpaintdevice.cpp
${QET_DIR}/sources/dxfpaintdevice.h ${QET_DIR}/sources/dxfpaintdevice.h
${QET_DIR}/sources/diagramcommands.cpp ${QET_DIR}/sources/diagramcommands.cpp
@@ -229,6 +242,10 @@ set(QET_SRC_FILES
${QET_DIR}/sources/exportpropertieswidget.h ${QET_DIR}/sources/exportpropertieswidget.h
${QET_DIR}/sources/genericpanel.cpp ${QET_DIR}/sources/genericpanel.cpp
${QET_DIR}/sources/genericpanel.h ${QET_DIR}/sources/genericpanel.h
${QET_DIR}/sources/itemgroups.cpp
${QET_DIR}/sources/itemgroups.h
${QET_DIR}/sources/shownkinds.cpp
${QET_DIR}/sources/shownkinds.h
${QET_DIR}/sources/lastusedstyle.cpp ${QET_DIR}/sources/lastusedstyle.cpp
${QET_DIR}/sources/lastusedstyle.h ${QET_DIR}/sources/lastusedstyle.h
${QET_DIR}/sources/machine_info.cpp ${QET_DIR}/sources/machine_info.cpp
@@ -282,6 +299,14 @@ set(QET_SRC_FILES
${QET_DIR}/sources/recentfiles.h ${QET_DIR}/sources/recentfiles.h
${QET_DIR}/sources/shortcutmanager.cpp ${QET_DIR}/sources/shortcutmanager.cpp
${QET_DIR}/sources/shortcutmanager.h ${QET_DIR}/sources/shortcutmanager.h
${QET_DIR}/sources/shortcutbarsettings.cpp
${QET_DIR}/sources/shortcutbarsettings.h
${QET_DIR}/sources/diagramcontexttoolbar.cpp
${QET_DIR}/sources/diagramcontexttoolbar.h
${QET_DIR}/sources/diagramgestureoverlay.cpp
${QET_DIR}/sources/diagramgestureoverlay.h
${QET_DIR}/sources/commandsearchpopup.cpp
${QET_DIR}/sources/commandsearchpopup.h
${QET_DIR}/sources/titleblockcell.cpp ${QET_DIR}/sources/titleblockcell.cpp
${QET_DIR}/sources/titleblockcell.h ${QET_DIR}/sources/titleblockcell.h
${QET_DIR}/sources/titleblockproperties.cpp ${QET_DIR}/sources/titleblockproperties.cpp
@@ -299,6 +324,18 @@ set(QET_SRC_FILES
${QET_DIR}/sources/autoNum/numerotationcontext.h ${QET_DIR}/sources/autoNum/numerotationcontext.h
${QET_DIR}/sources/autoNum/renumberelementscommand.cpp ${QET_DIR}/sources/autoNum/renumberelementscommand.cpp
${QET_DIR}/sources/autoNum/renumberelementscommand.h ${QET_DIR}/sources/autoNum/renumberelementscommand.h
${QET_DIR}/sources/autoNum/autonumschemecommand.cpp
${QET_DIR}/sources/autoNum/autonumschemecommand.h
${QET_DIR}/sources/autoNum/elementautonumschemecommand.cpp
${QET_DIR}/sources/autoNum/elementautonumschemecommand.h
${QET_DIR}/sources/autoNum/ui/counterwarning.cpp
${QET_DIR}/sources/autoNum/ui/counterwarning.h
${QET_DIR}/sources/autoNum/ui/pastenumberingimport.cpp
${QET_DIR}/sources/autoNum/ui/pastenumberingimport.h
${QET_DIR}/sources/autoNum/ui/renumberpreviewdialog.cpp
${QET_DIR}/sources/autoNum/ui/renumberpreviewdialog.h
${QET_DIR}/sources/undocommand/freezeelementlabelcommand.cpp
${QET_DIR}/sources/undocommand/freezeelementlabelcommand.h
${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.cpp ${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.cpp
${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.h ${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.h
${QET_DIR}/sources/autoNum/ui/autonumberingmanagementw.cpp ${QET_DIR}/sources/autoNum/ui/autonumberingmanagementw.cpp
@@ -314,6 +351,7 @@ set(QET_SRC_FILES
${QET_DIR}/sources/autoNum/ui/selectautonumw.cpp ${QET_DIR}/sources/autoNum/ui/selectautonumw.cpp
${QET_DIR}/sources/autoNum/ui/selectautonumw.h ${QET_DIR}/sources/autoNum/ui/selectautonumw.h
${QET_DIR}/sources/dataBase/legacyelementtypes.h
${QET_DIR}/sources/dataBase/projectdatabase.cpp ${QET_DIR}/sources/dataBase/projectdatabase.cpp
${QET_DIR}/sources/dataBase/projectdatabase.h ${QET_DIR}/sources/dataBase/projectdatabase.h
${QET_DIR}/sources/dataBase/sqlreadonly.cpp ${QET_DIR}/sources/dataBase/sqlreadonly.cpp
@@ -333,6 +371,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/diagramevent/diagrameventaddimage.h ${QET_DIR}/sources/diagramevent/diagrameventaddimage.h
${QET_DIR}/sources/diagramevent/diagrameventaddshape.cpp ${QET_DIR}/sources/diagramevent/diagrameventaddshape.cpp
${QET_DIR}/sources/diagramevent/diagrameventaddshape.h ${QET_DIR}/sources/diagramevent/diagrameventaddshape.h
${QET_DIR}/sources/diagramevent/diagrameventfillet.cpp
${QET_DIR}/sources/diagramevent/diagrameventfillet.h
${QET_DIR}/sources/diagramevent/diagrameventaddpath.cpp ${QET_DIR}/sources/diagramevent/diagrameventaddpath.cpp
${QET_DIR}/sources/diagramevent/diagrameventaddpath.h ${QET_DIR}/sources/diagramevent/diagrameventaddpath.h
${QET_DIR}/sources/diagramevent/diagrameventaddtext.cpp ${QET_DIR}/sources/diagramevent/diagrameventaddtext.cpp
@@ -448,6 +488,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.h ${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.h
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.cpp ${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.cpp
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.h ${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.h
${QET_DIR}/sources/ElementsCollection/elementpickerpopup.cpp
${QET_DIR}/sources/ElementsCollection/elementpickerpopup.h
${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.cpp ${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.cpp
${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.h ${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.h
${QET_DIR}/sources/ElementsCollection/elementslocation.cpp ${QET_DIR}/sources/ElementsCollection/elementslocation.cpp
@@ -456,6 +498,10 @@ set(QET_SRC_FILES
${QET_DIR}/sources/ElementsCollection/elementstreeview.h ${QET_DIR}/sources/ElementsCollection/elementstreeview.h
${QET_DIR}/sources/ElementsCollection/fileelementcollectionitem.cpp ${QET_DIR}/sources/ElementsCollection/fileelementcollectionitem.cpp
${QET_DIR}/sources/ElementsCollection/fileelementcollectionitem.h ${QET_DIR}/sources/ElementsCollection/fileelementcollectionitem.h
${QET_DIR}/sources/ElementsCollection/qetlabelsfile.cpp
${QET_DIR}/sources/ElementsCollection/qetlabelsfile.h
${QET_DIR}/sources/ElementsCollection/terminaluuids.cpp
${QET_DIR}/sources/ElementsCollection/terminaluuids.h
${QET_DIR}/sources/ElementsCollection/xmlelementcollection.cpp ${QET_DIR}/sources/ElementsCollection/xmlelementcollection.cpp
${QET_DIR}/sources/ElementsCollection/xmlelementcollection.h ${QET_DIR}/sources/ElementsCollection/xmlelementcollection.h
${QET_DIR}/sources/ElementsCollection/xmlprojectelementcollectionitem.cpp ${QET_DIR}/sources/ElementsCollection/xmlprojectelementcollectionitem.cpp
@@ -693,6 +739,10 @@ set(QET_SRC_FILES
${QET_DIR}/sources/ui/aboutqetdialog.cpp ${QET_DIR}/sources/ui/aboutqetdialog.cpp
${QET_DIR}/sources/ui/aboutqetdialog.h ${QET_DIR}/sources/ui/aboutqetdialog.h
${QET_DIR}/sources/ui/aiassistantdialog.cpp
${QET_DIR}/sources/ui/aiassistantdialog.h
${QET_DIR}/sources/ui/aiassistantsetup.cpp
${QET_DIR}/sources/ui/aiassistantsetup.h
${QET_DIR}/sources/ui/abstractelementpropertieseditorwidget.cpp ${QET_DIR}/sources/ui/abstractelementpropertieseditorwidget.cpp
${QET_DIR}/sources/ui/abstractelementpropertieseditorwidget.h ${QET_DIR}/sources/ui/abstractelementpropertieseditorwidget.h
${QET_DIR}/sources/ui/alignmenttextdialog.cpp ${QET_DIR}/sources/ui/alignmenttextdialog.cpp
@@ -703,6 +753,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/ui/borderpropertieswidget.h ${QET_DIR}/sources/ui/borderpropertieswidget.h
${QET_DIR}/sources/ui/compositetexteditdialog.cpp ${QET_DIR}/sources/ui/compositetexteditdialog.cpp
${QET_DIR}/sources/ui/compositetexteditdialog.h ${QET_DIR}/sources/ui/compositetexteditdialog.h
${QET_DIR}/sources/ui/conductorpropertieseditorwidget.cpp
${QET_DIR}/sources/ui/conductorpropertieseditorwidget.h
${QET_DIR}/sources/ui/contactgroupselectiondialog.cpp ${QET_DIR}/sources/ui/contactgroupselectiondialog.cpp
${QET_DIR}/sources/ui/contactgroupselectiondialog.h ${QET_DIR}/sources/ui/contactgroupselectiondialog.h
${QET_DIR}/sources/ui/conductorpropertiesdialog.cpp ${QET_DIR}/sources/ui/conductorpropertiesdialog.cpp
@@ -725,6 +777,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/ui/diagrampropertieseditordockwidget.h ${QET_DIR}/sources/ui/diagrampropertieseditordockwidget.h
${QET_DIR}/sources/ui/backupdialog.cpp ${QET_DIR}/sources/ui/backupdialog.cpp
${QET_DIR}/sources/ui/backupdialog.h ${QET_DIR}/sources/ui/backupdialog.h
${QET_DIR}/sources/ui/backuprestoredialog.cpp
${QET_DIR}/sources/ui/backuprestoredialog.h
${QET_DIR}/sources/ui/dialogwaiting.cpp ${QET_DIR}/sources/ui/dialogwaiting.cpp
${QET_DIR}/sources/ui/dialogwaiting.h ${QET_DIR}/sources/ui/dialogwaiting.h
${QET_DIR}/sources/ui/duplicateoffsetdialog.cpp ${QET_DIR}/sources/ui/duplicateoffsetdialog.cpp
@@ -757,6 +811,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/ui/importelementtextpatterndialog.h ${QET_DIR}/sources/ui/importelementtextpatterndialog.h
${QET_DIR}/sources/ui/jumptoelementdialog.cpp ${QET_DIR}/sources/ui/jumptoelementdialog.cpp
${QET_DIR}/sources/ui/jumptoelementdialog.h ${QET_DIR}/sources/ui/jumptoelementdialog.h
${QET_DIR}/sources/ui/wiringruleswarning.cpp
${QET_DIR}/sources/ui/wiringruleswarning.h
${QET_DIR}/sources/ui/inditextpropertieswidget.cpp ${QET_DIR}/sources/ui/inditextpropertieswidget.cpp
${QET_DIR}/sources/ui/inditextpropertieswidget.h ${QET_DIR}/sources/ui/inditextpropertieswidget.h
${QET_DIR}/sources/ui/linksingleelementwidget.cpp ${QET_DIR}/sources/ui/linksingleelementwidget.cpp
@@ -771,6 +827,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/ui/multipastedialog.h ${QET_DIR}/sources/ui/multipastedialog.h
${QET_DIR}/sources/ui/potentialselectordialog.cpp ${QET_DIR}/sources/ui/potentialselectordialog.cpp
${QET_DIR}/sources/ui/potentialselectordialog.h ${QET_DIR}/sources/ui/potentialselectordialog.h
${QET_DIR}/sources/ui/prefixconfigurationdialog.cpp
${QET_DIR}/sources/ui/prefixconfigurationdialog.h
${QET_DIR}/sources/ui/projectpropertiesdialog.cpp ${QET_DIR}/sources/ui/projectpropertiesdialog.cpp
${QET_DIR}/sources/ui/projectpropertiesdialog.h ${QET_DIR}/sources/ui/projectpropertiesdialog.h
${QET_DIR}/sources/ui/reportpropertiewidget.cpp ${QET_DIR}/sources/ui/reportpropertiewidget.cpp
@@ -794,6 +852,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/ui/configpage/guidespropertieswidget.h ${QET_DIR}/sources/ui/configpage/guidespropertieswidget.h
${QET_DIR}/sources/ui/configpage/shortcutsconfigpage.cpp ${QET_DIR}/sources/ui/configpage/shortcutsconfigpage.cpp
${QET_DIR}/sources/ui/configpage/shortcutsconfigpage.h ${QET_DIR}/sources/ui/configpage/shortcutsconfigpage.h
${QET_DIR}/sources/ui/configpage/shortcutbarconfigpage.cpp
${QET_DIR}/sources/ui/configpage/shortcutbarconfigpage.h
${QET_DIR}/sources/undocommand/addelementtextcommand.cpp ${QET_DIR}/sources/undocommand/addelementtextcommand.cpp
${QET_DIR}/sources/undocommand/addelementtextcommand.h ${QET_DIR}/sources/undocommand/addelementtextcommand.h
@@ -819,6 +879,10 @@ set(QET_SRC_FILES
${QET_DIR}/sources/undocommand/removediagramcommand.h ${QET_DIR}/sources/undocommand/removediagramcommand.h
${QET_DIR}/sources/undocommand/setautonumcontextcommand.cpp ${QET_DIR}/sources/undocommand/setautonumcontextcommand.cpp
${QET_DIR}/sources/undocommand/setautonumcontextcommand.h ${QET_DIR}/sources/undocommand/setautonumcontextcommand.h
${QET_DIR}/sources/undocommand/groupitemscommand.cpp
${QET_DIR}/sources/undocommand/groupitemscommand.h
${QET_DIR}/sources/undocommand/alignselectioncommand.cpp
${QET_DIR}/sources/undocommand/alignselectioncommand.h
${QET_DIR}/sources/undocommand/rotateselectioncommand.cpp ${QET_DIR}/sources/undocommand/rotateselectioncommand.cpp
${QET_DIR}/sources/undocommand/rotateselectioncommand.h ${QET_DIR}/sources/undocommand/rotateselectioncommand.h
${QET_DIR}/sources/undocommand/promoteshapecommand.cpp ${QET_DIR}/sources/undocommand/promoteshapecommand.cpp
@@ -830,6 +894,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/utils/conductorcreator.cpp ${QET_DIR}/sources/utils/conductorcreator.cpp
${QET_DIR}/sources/utils/conductorcreator.h ${QET_DIR}/sources/utils/conductorcreator.h
${QET_DIR}/sources/utils/colordialogdoubleclick.cpp
${QET_DIR}/sources/utils/colordialogdoubleclick.h
${QET_DIR}/sources/utils/macosxopenevent.cpp ${QET_DIR}/sources/utils/macosxopenevent.cpp
${QET_DIR}/sources/utils/macosxopenevent.h ${QET_DIR}/sources/utils/macosxopenevent.h
${QET_DIR}/sources/utils/qetsettings.cpp ${QET_DIR}/sources/utils/qetsettings.cpp
@@ -837,6 +903,13 @@ set(QET_SRC_FILES
${QET_DIR}/sources/utils/qetutils.cpp ${QET_DIR}/sources/utils/qetutils.cpp
${QET_DIR}/sources/utils/qetutils.h ${QET_DIR}/sources/utils/qetutils.h
${QET_DIR}/sources/materiallist/materialentrydialog.cpp
${QET_DIR}/sources/materiallist/materialentrydialog.h
${QET_DIR}/sources/materiallist/materiallist.cpp
${QET_DIR}/sources/materiallist/materiallist.h
${QET_DIR}/sources/materiallist/materialselectiondialog.cpp
${QET_DIR}/sources/materiallist/materialselectiondialog.h
${QET_DIR}/sources/xml/terminalstripitemxml.cpp ${QET_DIR}/sources/xml/terminalstripitemxml.cpp
${QET_DIR}/sources/xml/terminalstripitemxml.h ${QET_DIR}/sources/xml/terminalstripitemxml.h
${QET_DIR}/sources/xml/terminalstriplayoutpatternxml.cpp ${QET_DIR}/sources/xml/terminalstriplayoutpatternxml.cpp
@@ -873,6 +946,17 @@ list(APPEND QET_SRC_FILES
${QET_DIR}/sources/scripting/qetscriptapi.h ${QET_DIR}/sources/scripting/qetscriptapi.h
${QET_DIR}/sources/scripting/qetscripting.cpp ${QET_DIR}/sources/scripting/qetscripting.cpp
${QET_DIR}/sources/scripting/qetscripting.h ${QET_DIR}/sources/scripting/qetscripting.h
${QET_DIR}/sources/scripting/scriptheader.h
${QET_DIR}/sources/scripting/scriptlibrary.cpp
${QET_DIR}/sources/scripting/scriptlibrary.h
${QET_DIR}/sources/scripting/scriptmanagerdialog.cpp
${QET_DIR}/sources/scripting/scriptmanagerdialog.h
${QET_DIR}/sources/scripting/liveserver.cpp
${QET_DIR}/sources/scripting/liveserver.h
${QET_DIR}/sources/scripting/macrorecorder.cpp
${QET_DIR}/sources/scripting/macrorecorder.h
${QET_DIR}/sources/scripting/assistantinfo.cpp
${QET_DIR}/sources/scripting/assistantinfo.h
) )
if(QET_SPACEMOUSE_ENABLED) if(QET_SPACEMOUSE_ENABLED)
@@ -905,6 +989,13 @@ if(QET_SPACEMOUSE_BACKEND_HID_ENABLED)
) )
endif() endif()
if(QET_SPACEMOUSE_BACKEND_CONNEXION_ENABLED)
list(APPEND QET_SRC_FILES
${QET_DIR}/sources/spacemouse/connexionbackend.cpp
${QET_DIR}/sources/spacemouse/connexionbackend.h
)
endif()
set(TS_FILES set(TS_FILES
${QET_DIR}/lang/qet_ar.ts ${QET_DIR}/lang/qet_ar.ts
${QET_DIR}/lang/qet_ca.ts ${QET_DIR}/lang/qet_ca.ts
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2006 The QElectroTech Team # Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech. # This file is part of QElectroTech.
# #
# QElectroTech is free software: you can redistribute it and/or modify # QElectroTech is free software: you can redistribute it and/or modify
+12 -4
View File
@@ -42,10 +42,18 @@ unit conventions and decimal quantities are not changed implicitly.
## BOM Policy ## BOM Policy
The default BOM query exports one row per Simple or Master element. Slave The default BOM query exports one row per Simple, Master, Slave or Terminal
contacts, terminal-strip elements, reports, thumbnails, and conductor element: an auxiliary contact block and a terminal block are parts of their
definitions are excluded. A Master device is counted once and PLC I/O entries own (discussion #847). Reports, thumbnails and conductor definitions are
are not expanded. The existing `exclude_from_bom` property is honored by the excluded. A Master device is counted once and PLC I/O entries are not
expanded.
Two options leave rows out (#1178). Contact blocks: uncheck **Contacts
esclaves** in the dialog, or pass `--no-slaves`. Junctions, the
terminal-type symbols drawn where wires branch (`114_connections`), which
have no label, designation, manufacturer or manufacturer reference: check
**Laisser de côté les jonctions**, or pass `--no-junctions`. A terminal block
with a label or a part number is kept. The existing `exclude_from_bom` property is honored by the
SQL view, including common true representations (`true`, `1`, `yes`, `on`). SQL view, including common true representations (`true`, `1`, `yes`, `on`).
The default columns are: The default columns are:
+1582 -925
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

-48
View File
@@ -1,48 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
<!-- DIN rail -->
<rect x="6" y="86" width="116" height="18" rx="3" fill="#7f8c8d"/>
<rect x="6" y="92" width="116" height="6" fill="#4d4d4d"/>
<!-- six terminal blocks: L, L, N, PE, L, spare -->
<g fill="#eff0f1">
<rect x="12" y="26" width="16" height="66" rx="2"/>
<rect x="30" y="26" width="16" height="66" rx="2"/>
<rect x="48" y="26" width="16" height="66" rx="2"/>
<rect x="66" y="26" width="16" height="66" rx="2"/>
<rect x="84" y="26" width="16" height="66" rx="2"/>
<rect x="102" y="26" width="16" height="66" rx="2"/>
</g>
<!-- right-hand shading of each block -->
<g fill="#bdc3c7">
<rect x="24" y="28" width="4" height="62"/>
<rect x="42" y="28" width="4" height="62"/>
<rect x="60" y="28" width="4" height="62"/>
<rect x="78" y="28" width="4" height="62"/>
<rect x="96" y="28" width="4" height="62"/>
<rect x="114" y="28" width="4" height="62"/>
</g>
<!-- colored marker bands -->
<rect x="12" y="26" width="16" height="10" rx="2" fill="#7f8c8d"/>
<rect x="30" y="26" width="16" height="10" rx="2" fill="#7f8c8d"/>
<rect x="48" y="26" width="16" height="10" rx="2" fill="#3daee9"/>
<rect x="66" y="26" width="16" height="10" rx="2" fill="#27ae60"/>
<rect x="70" y="26" width="8" height="10" fill="#fdbc4b"/>
<rect x="84" y="26" width="16" height="10" rx="2" fill="#7f8c8d"/>
<rect x="102" y="26" width="16" height="10" rx="2" fill="#da4453"/>
<!-- screws -->
<g fill="#31363b">
<circle cx="20" cy="50" r="4.5"/><circle cx="20" cy="74" r="4.5"/>
<circle cx="38" cy="50" r="4.5"/><circle cx="38" cy="74" r="4.5"/>
<circle cx="56" cy="50" r="4.5"/><circle cx="56" cy="74" r="4.5"/>
<circle cx="74" cy="50" r="4.5"/><circle cx="74" cy="74" r="4.5"/>
<circle cx="92" cy="50" r="4.5"/><circle cx="92" cy="74" r="4.5"/>
<circle cx="110" cy="50" r="4.5"/><circle cx="110" cy="74" r="4.5"/>
</g>
<g fill="#eff0f1">
<rect x="16" y="49" width="8" height="2"/><rect x="16" y="73" width="8" height="2"/>
<rect x="34" y="49" width="8" height="2"/><rect x="34" y="73" width="8" height="2"/>
<rect x="52" y="49" width="8" height="2"/><rect x="52" y="73" width="8" height="2"/>
<rect x="70" y="49" width="8" height="2"/><rect x="70" y="73" width="8" height="2"/>
<rect x="88" y="49" width="8" height="2"/><rect x="88" y="73" width="8" height="2"/>
<rect x="106" y="49" width="8" height="2"/><rect x="106" y="73" width="8" height="2"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 B

Some files were not shown because too many files have changed in this diff Show More