Commit Graph

10187 Commits

Author SHA1 Message Date
Laurent Trinques 1dfb70870f Merge pull request #1325 from ispyisail/feature/customise-command-ids
Add the missing folio editor commands to the Shortcuts page
2026-10-06 16:27:36 +02:00
Laurent Trinques 0e36f4efb6 Merge pull request #1324 from bhangart/fix/dxf-wrapped-text
Export wrapped texts to DXF line by line
2026-10-06 16:25:53 +02:00
Laurent Trinques 1a3a9e59f2 Merge pull request #1323 from bhangart/fix/element-editor-text-field-width-anchor
Keep an element editor text field in place when its width changes
2026-10-06 16:23:53 +02:00
Laurent Trinques 4432f6b5ab Merge pull request #1322 from bhangart/fix/element-editor-width-spinbox
Show "Auto" in the width box of element editor text fields
2026-10-06 16:22:02 +02:00
Laurent Trinques 33b0069e17 Merge pull request #1321 from bhangart/feature/text-resize-handles
Add corner handles to change the width of a text box
2026-10-06 16:19:42 +02:00
Laurent Trinques e4e8cc2892 Merge pull request #1320 from bhangart/feature/free-text-width
Let free texts keep a width they wrap to
2026-10-06 16:18:25 +02:00
Laurent Trinques 164f538d6a Merge pull request #1331 from ispyisail/fix/1306-free-terminal-table
Fix the free terminal table staying empty after a move (#1306)
2026-10-06 16:13:09 +02:00
Laurent Trinques 6a5ff20f51 Merge pull request #1310 from bhangart/fix/image-crop-undo
Undo the crop, colours and original of a picture together with its pixels
2026-10-06 16:12:13 +02:00
ispyisail 306c682112 Fix the free terminal table staying empty after a move (#1306)
In the terminal strip manager, after a free terminal was moved into a
strip, clicking another free terminal showed nothing on the right. It
only came back after selecting a strip first.

The tree remembers whether a free terminal is selected and only tells
the window to update when that, or the selected strip, changes. Moving
a terminal rebuilds the tree, which clears the selection and shows the
empty page, but the "free terminal selected" flag stayed true. The next
click on a free terminal then looked like no change and was ignored.

The flag is now reset when the selection is cleared, and updated on
every change it takes part in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 22:02:11 +13:00
Beat Hangartner f6ac685ebe Drop a machine-specific path from the DXF test fixture
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:53:49 +02:00
Beat Hangartner 4b187e049f Keep the DXF text lines of master for texts that do not wrap
layoutLines() trimmed the trailing spaces of every line and kept
non-breaking spaces, which the DXF export used to write as plain spaces
(toPlainText()). A DXF R10 file has no code page, so a reader assuming
Windows-1252 could show them as 'Â '. Now a paragraph that does not
wrap gives exactly its line of toPlainText().split('\n') again
(trailing spaces and tabs kept, non-breaking spaces as plain spaces,
<br> as a line break). In a paragraph that wraps, every line loses its
trailing spaces, so spaces that wrap to a line of their own give an
empty line: not written, but it keeps the spacing of the next lines.

Test: tst_textlines compares with toPlainText() for trailing spaces,
tabs, non-breaking spaces, <br> and paragraphs, and checks wrapped
lines (fails before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:53:49 +02:00
Beat Hangartner 069cf1e7b5 Tidy the free text width
- setTextWidth() returns without a signal when the width does not
  change, which is also the case on every load of a text without one.
- Say why IndependentTextItem::textWidth()/setTextWidth() hide the
  non-virtual QGraphicsTextItem functions, and what that means for code
  holding a base pointer.
- The fixture no longer carries a /tmp path from the machine it was
  made on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:50:08 +02:00
Beat Hangartner 315de47a12 Keep the width of free texts in the project database
The independent_text table had the text and rotation of a free text but
not its width, and a width change did not update the row at all -- not
even its width and height, which follow the wrapped text. Add a
text_width column (NULL for the automatic width) and update the row on
textWidthChanged.

Test: tst_scriptfreetextwidth reads the table with qet.query() (fails
before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:50:08 +02:00
Beat Hangartner 5cad0c6159 Keep the text resize handles working after a scene change
A scene drops the event filters of an item that leaves it. The handles
installed theirs once, in the constructor, and only if the text was in
a scene: after the text was removed and added back (undoing a delete
does that), or when the handles were made before the text was in a
scene, they no longer reacted, and a drag moved the text instead.
TextResizeHandles now installs the filters again whenever it enters a
scene.

Tests: dragAfterLeavingTheScene, dragWhenCreatedOutsideAScene (both
fail before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:46:17 +02:00
Beat Hangartner 6a681cde67 Keep an element editor text field in place when its width changes
In the element editor, changing the width of a right-aligned or centred
dynamic text field (with the width box) moved its aligned edge: the
field always grew or shrank from its left edge, so a right-aligned
field's right edge moved. PartDynamicTextField::setTextWidth() now
keeps the point chosen by the alignment in place, with the
prepareAlignment()/finishAlignment() pair setPlainText() and setFont()
already use. Not while the field is read from the .elmt, whose saved
position is the one to keep.

No file format change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:41:16 +02:00
Beat Hangartner a0cfa580bb Round the width when filling the element editor width box
updateForm() filled the box with setValue(textWidth()), and setValue(int)
truncates: a field 61.7 px wide showed 61 while editingFinished compared
with qRound() = 62, so leaving the box still set the width to 61 and
pushed an undo step. Round in both places.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:41:01 +02:00
Beat Hangartner d464428642 Export wrapped texts to DXF line by line
A symbol text with a width (text_width) is drawn wrapped on the folio,
but the DXF export split texts on '\n' only, so it was written as one
long line, running past its box and over the drawing. The export now
writes the lines of folio texts as they are laid out
(TextLines::layoutLines(), new header-only textlines.h). Texts without
a width give the same lines as before.

Tests: tst_textlines, tst_dxfwrappedtext (fails without this change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:00:26 +02:00
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
Beat Hangartner 1d37ce9b84 Show "Auto" in the width box of element editor text fields
The width box of a dynamic text field in the element editor showed -1
for the automatic width and stopped at 500 px. It now shows "Auto" and
"px", and goes up to 10000 px, so a wider field is not cut to 500 just
by being shown.

Leaving the box without changing it no longer pushes a change either:
the box holds whole pixels, while a width fitted to the text (or set
elsewhere) can have decimals, and the comparison made the field's
width round itself on every focus loss.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:57:03 +02:00
Beat Hangartner 010e44ab8a Add corner handles to change the width of a text box
TextResizeHandles puts four handles at the corners of a selected text
(any QGraphicsTextItem with a qreal "textWidth" property, -1 = automatic
width). Dragging one changes the width: the opposite corner stays in
place in scene coordinates, for any rotation and rotation point, the
text wraps and the height follows it. Vertical mouse movements are
ignored, the width never goes below the longest word and is rounded to
whole pixels, and a click without a move changes nothing.
Double-clicking a handle goes back to the automatic width.

The handles are a child of the text, so they follow it when it moves,
rotates or is zoomed (as for the symbol text handles, #1002). The width
is changed live; the owner makes it undoable with TextResizeCommand,
which sets the width then the position on both undo and redo -- a text
may move itself when its width changes, so two property commands would
restore them in the wrong order.

The geometry is header-only (textresize.h). Nothing uses the handles
yet: the follow-up changes add them to free texts and to text fields in
the element editor.

Test: tst_textresizehandles, on a plain QGraphicsTextItem.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:53:38 +02:00
Beat Hangartner 1e3c618495 Let free texts keep a width they wrap to
A free text (IndependentTextItem) gets a textWidth property: -1 is the
automatic width (one line, or lines broken by hand, as today); a
positive width makes the text wrap between words. Its top-left corner
stays in place. The property is what a spin box and resize handles will
set in follow-up changes.

File format: the width is saved as text_width on <input>, only when one
is set, so a project without widths is saved byte-identical
(tst_resaveunchanged). An older QElectroTech ignores the attribute and
shows the text on one line, with no load error.

DiagramTextItem::setHtml() no longer widens a centred or right-aligned
rich text to idealWidth() + 40 when the text has a width of its own;
with the automatic width it still does.

Test: tst_freetextwidth (--resave keeps the width only where set,
--export-svg draws the text wrapped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:52:27 +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
Beat Hangartner 0513344b92 Address review: drop dummy undo children, applyCrop() returns bool
- replaceImage(), mirror() and setTransparentColor() no longer add a
  dummy QUndoCommand child: the imageSource child already keeps
  QPropertyUndoCommand::mergeWith() from merging two of them.
- The explanation of the position and pivot maths moves from crop() to
  applyCrop(), where that code now lives; the stale older doc block of
  crop() goes.
- applyCrop() returns false when nothing was cropped, and
  qet.cropImage() passes that on. tst_imagecropundo checks it for the
  current crop, an empty rectangle and one outside the picture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:01:30 +02:00
ispyisail 8e648f7332 Add the missing folio editor commands to the Shortcuts page
44 commands in the folio editor's menus and toolbars had no
ShortcutManager id, so they could not be given a key, found by the
command search or put on the shortcut bar: Export to PDF, the grid and
guide toggles, Selection/View mode, most of the Project menu, Tile and
Cascade, the Help menu items, and others. Register each with no default
key, as the add-item tools already are.

What's This keeps the Shift+F1 that Qt gives it. No other command
had a key before, so nothing changes for anyone who has not set one.

Left out: the option lists inside submenus (text grid step, the
Show menu's item kinds, background colours). They are choices, not
commands.

First stage of discussion #1319.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:54:49 +13: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