Commit Graph

10028 Commits

Author SHA1 Message Date
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
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 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 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