935 Commits

Author SHA1 Message Date
ispyisail 4b4b9a2ad0 Merge pull request #1435 from bhangart/fix/line-pivot-follows-resize
Keep a line's pivot at its centre when one of its ends is dragged
2026-10-13 16:29:20 +13:00
Laurent Trinques e335aa2e55 Merge branch 'test-1438' 2026-10-11 05:15:26 +02:00
ispyisail ee0593ce7d Merge pull request #1432 from bhangart/feature/image-crop-on-folio
Crop a picture directly on the folio
2026-10-11 16:12:14 +13:00
Laurent Trinques 1cd8fadbc4 Reapply "Add cable management: drawing, cores, numbering, cross-references and cable list"
This reverts commit 2bb3343d2d.
2026-10-11 05:00:01 +02:00
Laurent Trinques 2bb3343d2d Revert "Add cable management: drawing, cores, numbering, cross-references and cable list" 2026-10-10 16:49:39 +02:00
Laurent Trinques 738ffbfc36 Merge pull request #1438 from Kellermorph/wiring-manager
Add cable management: drawing, cores, numbering, cross-references and cable list
2026-10-10 16:33:12 +02:00
Beat Hangartner 446479e8d2 Fix a crop in progress outliving an undo or the picture's removal
While a picture was being cropped on the folio:

- Ctrl+Z or Ctrl+Y of an earlier crop of the same picture left the
  crop handles where they were, 10 px beside the frame for a crop
  that starts at (10, 10).
- undoing the picture's insertion dropped the crop but left the
  picture as the folio's keyboard item, also after a redo put it
  back.

setImageSource() and leaving the scene now end the crop without
applying it. Nothing is pushed on the undo stack from there, since
both can run inside an undo command. Delete and cut are unchanged:
they deselect the picture first, which applies the crop as its own
undo step before theirs.

No file-format change. Not covered by an automated test: crop mode
needs a folio and its view, which a test can only get from the
application object library of #1431. Checked with a throw-away test
on a local merge of both branches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 11:01:29 +02:00
Beat Hangartner 8261524208 Put the Escape comment back on m_drag_cancelled
The comment "Escape pressed: ignore the rest of this mouse gesture"
explains m_drag_cancelled. The crop commit moved it onto the
declaration of dragCropHandle(), added below it. No code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:54:42 +02:00
ispyisail ada516d103 Merge remote-tracking branch 'upstream/master' into fix/report-arrow-without-terminal
# Conflicts:
#	tests/qttest/tst_resaveunchanged.cpp
2026-10-10 21:12:49 +13:00
ispyisail 9a4a303503 Merge remote-tracking branch 'upstream/master' into fix/table-chain-deleted-folio 2026-10-10 20:08:03 +13:00
ispyisail 44b2129d5a Fix a crash opening a project whose folio report arrow has no terminal
A folio report arrow's text looks for a wire to show: on the arrow
itself, else on the arrow it is linked to, through that arrow's first
terminal. Some report symbols have no terminal -- ref_voyant_2_h.elmt in
the collection, and older collections' arrows -- so a project linking an
arrow with no wire to one of them crashed QElectroTech as it opened.
The other arrow's terminals are now checked first, as the arrow's own
already were.

Found on a project attached to the forum (saved by 0.70), which crashed
on opening.

Test: tst_resaveunchanged::reportArrowWithoutTerminalOpens builds the
case from Projet_vierge.qet's own symbols; it crashes without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
2026-10-10 20:05:58 +13:00
ispyisail 727188f564 Fix master's failing link order test: sort a copy in linkedElements()
Element::linkedElements() sorted the element's own list of linked
elements by position before returning it, so any caller reordered what
is saved. #1441 restores the file's order after linking; #1442 fills the
link table through linkedElements(), which sorted it again, so on master
a coil's contacts are saved in position order and
tst_resaveunchanged::savedLinkOrderKept fails.

linkedElements() now returns a sorted copy. Callers still get the list
by position; the saved order is left alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
2026-10-10 19:33:06 +13:00
ispyisail ee164f1bf1 Fix a split table losing its data and crashing after a folio is deleted
A summary or parts-list table split over several folios saves its data
with its first part only; each later part names the part before it.
Deleting a folio keeps its tables in memory for undo, still in the
chain, so saving then named the deleted part as the previous table of
the next one. On reopening, every part after the gap had no data, and
closing the project crashed: the first orphaned part, destroyed, handed
its missing data on to the next part through a null model.

toXml() now names the nearest previous part still in the project, or
saves the data if there is none. setPreviousTable() no longer copies a
null model, so files already saved this way open and close.

Tests (tst_resaveunchanged): missingPreviousTableOpens,
deletedFolioKeepsTableChain; both fail without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
2026-10-10 19:03:50 +13:00
ispyisail 5441e2ae76 Keep a coil's contacts in the order the file saved them
On opening a project, a coil and its contacts are linked in the order
the folios' items are visited. When contacts are on an earlier folio
than the coil they link first, so the coil's list followed the folio
order instead of the file's, and contacts on one folio swapped places
from run to run. Saving the same project twice could then give two
different files.

After linking, Element::initLink() now puts the linked elements back in
the order the file listed them. Elements linked but not in that list
keep their place after it.

Test: tst_resaveunchanged::savedLinkOrderKept reverses a coil's two
contacts in m_000.qet and checks a resave keeps that order; it fails
without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
2026-10-10 16:54:26 +13:00
ispyisail 4c7ea90c51 Merge pull request #1429 from bhangart/fix/image-missing-size
Load a picture without a usable size at size 1, not 0
2026-10-10 07:52:03 +13:00
Kellermorph 69eb071f45 Merge upstream/master: translation wave plus generic device
Upstream moved 56 commits ahead of the last merge. Six files conflicted,
all of them files where upstream's French -> English tr() translation wave
touched lines we had also touched with the cable feature. Resolution rule
applied everywhere: take upstream's English source strings, keep our cable
additions.

Resolved:

- sources/ui/xrefpropertieswidget.ui : kept our structure (m_labels_gb
  pulled out of the display group, m_slave_row, the m_font_pb "Police..."
  button of the cable cross-reference) and mechanically applied upstream's
  20 English string replacements on top of it.

- sources/ui/xrefpropertieswidget.cpp : our version plus upstream's 7
  translated combo entries; the tr("Cable") item we add for the cable
  cross-reference type stays.

- sources/autoNum/ui/numparteditorw.cpp : upstream's English part names
  ("number format 1", "Sheet no.", "Plant", "Location", ...) for the
  number-format list, with our comment explaining why type 3 (cable) takes
  the conductor kinds of variable.

- sources/ui/configpage/configpages.cpp : upstream's English tab titles
  (Conductors / Elements / Sheets) plus our Cables tab.

- sources/ui/configpage/projectconfigpages.cpp : upstream's "Sheets" tab
  title plus our Cables tab block (single-rule SelectAutonumW(3)).

- sources/qetdiagrameditor.cpp : union of both sides - upstream's new
  "Add a generic device" action (icon, status tip, data id) and its
  English menu names ("File"/"Edit"/"Project"/"Display"), together with our
  cable action, the Numbering and Lists menus, the cable list actions and
  the CSV export in the Project menu.

Kept French: our own cable tr() source strings stay French for now. That
is the smaller change and the translation pass can follow separately.

Verified: full build 1065/1065, ctest 91/92 (only the pre-existing
headless tst_menubarkeyboard fails: no window manager, so Alt+F and F10
cannot open a menu bar), smoke start exit 124, no conflict marker left in
the tree.
2026-10-09 20:44:58 +02:00
Beat Hangartner e1cb6006c6 Load a picture without a usable size at size 1, not 0
DiagramImageItem::fromXml() read the size attribute with a plain
toDouble(). A picture whose size was missing or not a number got size
0, so it was invisible on the folio, left out of the SVG export, and
a resave wrote size="0" back, which then stayed 0 on every load. "nan"
was kept and written back as such. The <transform> child, which newer
files carry, already defaults its scale to 1.

Read the size with an ok flag and use 1, the size a picture is added
with, when it is missing, not a number, not finite, or 0.

No file-format change for usable sizes. A picture that an earlier save
left at size="0" shows again, at size 1, after this change.

Tests: tst_imagemissingsize runs the binary's --resave and --export-svg
on a fixture with six small pictures: size missing, "0", "nan", "big",
"0.5" and "2". The first four fail on master (saved as 0, 0, nan, 0)
and the export draws only 3 of the 6 pictures there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Beat Hangartner <beat@hangartners.ch>
2026-10-09 15:58:49 +02:00
Beat Hangartner 9136c605f4 Load a free text's non-finite position or rotation as 0
"nan", "inf" and "-inf" parse as numbers, and IndependentTextItem::
fromXml() used them as they came for x, y and rotation. A project with
such a value kept it: --resave wrote x="nan" back, and the SVG export
drew the text at matrix(1,0,0,1,nan,95), which is not valid SVG.
Conductors and element texts already load a non-finite position as 0.

Load a non-finite x, y or rotation of a free text as 0, the same way.

No file-format change: finite values load and save as before.

Tests: tst_freetextnonfinite runs the binary's --resave and --export-svg
on a fixture with x="nan", y="inf", x="-inf", rotation="nan" and a
finite text (61.7, 61.3, 37 degrees, kept as they are). The four
non-finite cases and the export check fail on master.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Beat Hangartner <beat@hangartners.ch>
2026-10-09 15:58:44 +02:00
Beat Hangartner c52cd3b2fc Keep a line's pivot at its centre when one of its ends is dragged
A shape's pivot follows its centre when it is resized, unless it was
placed by hand: setLine() and setRect() do that, and so did dragging a
rectangle's or an ellipse's handles, which goes through setRect().
Dragging an end of a line moved the end directly and left the pivot
where it was, so the next rotation turned the line around a point
beside it. Undo and redo already re-centred it, through setLine().

The line's handle drag now applies the same rule.

New tst_linepivotresize, on a real folio with scene mouse events: the
pivot is at the new centre after an end is dragged (fails without the
change: it stays at the old centre), and after undo and redo; a pivot
placed by hand stays; a rectangle's pivot follows as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:47:53 +02:00
Beat Hangartner 05131d1238 Show crop bars instead of handle points while cropping
Black bars on a white outline, an L at each corner and a short bar in
the middle of each side, at the same size on screen at any zoom, with
the matching resize cursor over each. The handles underneath still
take the drags but are no longer drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:46:46 +02:00
Beat Hangartner d86f2d5461 Crop a picture directly on the folio
Double-clicking a picture, or "Crop on the folio" in its context
menu, crops it in place: the whole original is shown, dimmed outside
the region kept, and eight handles move the crop's edges; a drag inside
the frame moves the crop window over the original. Enter, a double-click
or a click elsewhere applies the crop as one undo step, keeping the
kept region where it is on the folio; Escape drops it. Rotated and
scaled pictures are cropped along their own sides.

While cropping, the picture is the folio's keyboardItem (see the
Escape commit before this one), so the view hands it Enter and Escape
before its own shortcuts: Enter does not repeat the last placing
command, and Escape does not clear the selection, which would apply
the crop.

The context menu gains "Reset crop", which shows the
whole original again, and "Properties...", since double-click no
longer opens them. The crop dialog stays available.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:46:46 +02:00
Kellermorph 955dfb3a73 Merge remote-tracking branch 'upstream/master' into wiring-manager
Conflicts resolved in 24 files. Notable decisions:

- ico/icon-themes.qrc: upstream's icon folder restructure kept; the 36
  cable icon entries re-added under the new qet/qet-dark path scheme
  (XML valid, every referenced file exists).
- Toolbar system: upstream's DiagramToolbarSettings now owns the
  toolbar filling. The cable tool is registered as
  diagrameditor.add_cable (generic group loop) and listed on the
  "diagram" toolbar right after the auto-break conductor action,
  separated by a divider, exactly like before the merge.
- Paste: the cable lines of a copy are handed to
  PasteNumberingImport::push() as extra commands, so paste + cable
  lines + the numbering question stay ONE undo step on both paste
  paths (DiagramView::paste and DiagramEventAddPaste::commit).
- PDF links: upstream's deterministic link sorting kept together with
  the cable cross-reference injector (injectLinkTo -> links list).
- Element renumbering: upstream's ElementAutoNumSchemeCommand::renumber
  replaces the old formula-based body of
  QETProject::renumberElementsBySchemeTitle; the cable numbering rule
  API (cableAutoNum, axis priority, rule name) is kept in full.
- Auto-numbering pages: upstream's scheme management kept, the Câbles
  tab added as index 4 (changeToCableTab), both entry points
  changeToCable() and changeToElement() kept side by side.
- SelectAutonumW: dirty tracking and the contextEdited signal moved
  into upstream's insertPartRow() row API, so single-rule mode, the
  rule-removable button and "confirm without change leaves the project
  alone" keep working.
- xrefpropertieswidget: upstream's offset clamping kept; the cable
  branch (Texte field, Police button, hidden display options) kept;
  the stack-overlapping checkbox is hidden for cables.

Verified after the merge: build green (515/515, no errors), ctest
85/86 (only the pre-existing headless tst_menubarkeyboard failure),
offscreen smoke exit 124.
2026-10-09 14:09:08 +02:00
ispyisail d02f6c1c12 Merge pull request #1417 from ispyisail/fix/1408-shared-symbol-uuid
Fix wires moving to another symbol after saving an older project (#1408)
2026-10-09 22:54:57 +13:00
ispyisail f5c643da06 Merge pull request #1415 from bhangart/fix/shape-pivot-flag-undo
Undo restores whether a shape's pivot was placed by hand
2026-10-09 22:50:30 +13:00
ispyisail 053ff0b6cf Fix wires moving to another symbol after saving an older project (#1408)
Symbols copied in old versions can share one uuid on a folio. Up to
0.100 that was harmless: a wire on a terminal without uuid was saved by
terminal number. Since #1118 every terminal has a uuid, so every wire was
saved by symbol uuid and terminal uuid, and on reopening findTerminal()
took the first symbol carrying it: all the copies' wires landed on it.

- Save: a wire end on a symbol whose uuid another symbol of the folio
  also carries is written by terminal number again.
- Load: when a wire's uuid names several symbols, pickEnds() takes the
  pair whose distance matches the wire's saved path, then the symbols
  whose labels match the ones saved with the wire, then two terminals no
  wire joins yet; otherwise the first, as before. This repairs a file a
  build with #1118 saved once.

tst_sharedsymboluuid gives the three lamps of
Habitat-Schemas_developpes.qet one uuid: master loses a net after a
save (8 of 9) and reopens the file it saved with 8; with this, 9 both
times. The 25 examples save the same as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:30:26 +13:00
Beat Hangartner 606a526344 Undo restores whether a shape's pivot was placed by hand
A shape remembers whether its pivot was placed by hand: a hand-placed
pivot is saved and no longer follows the shape's centre when the shape
is resized. Dragging the pivot handle marked it as hand-placed, but the
undo step restored only the pivot and the position. After Ctrl+Z the
pivot was back at the centre and still marked as hand-placed, so it
stayed behind on the next resize and was saved as a <transform>.

pivotIsCustom becomes a property and goes into the pivot drag's undo
step, the same fix as #1367 for pictures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:59:17 +02:00
Beat Hangartner be8e2372a9 Cancel a picture's handle drag with Escape
Pressing Escape while a handle of a picture was dragged went to
DiagramView, which clears the selection. That removed the handles under
the mouse, so the drag stopped where it was: the picture kept the new
size, angle or pivot, and since the release never arrived, no undo step
was recorded and the change could not be undone.

Diagram gets a keyboardItem: an item in a state Escape must end. While
a resize, rotate, skew or pivot handle is dragged, the picture is that
item; DiagramView sends each key to it first, and only keys it does not
take reach the view's own shortcuts. Escape puts the picture back as it
was when the handle was pressed (position, transform, and whether the
pivot was placed by hand) and ignores the rest of the gesture, so no
undo step is recorded. The drag also ends when the handles go away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:59:15 +02:00
ispyisail 80c8e297ec Regenerate the English source conversion on master 8b5a09f3fc
Generated by tools/qet-en-source/regenerate.sh, not edited by hand. The
tree is the fresh conversion; this commit only joins it to the PR's
history so the branch is never force-pushed.
2026-10-09 05:05:49 +13:00
Kellermorph bf9b34398d Add cable management: drawing, cores, numbering, cross-references and cable list
Cables are drawn line objects (toolbar button next to the auto
break/reconnect button, click-move-click, right click cancels), not
components. The type comes from a CSV catalog following the material
list pattern (designation, cores, core colours, editable through the
entry dialog with "Nouvelle entrée" / "Modifier l'entrée").

Each core is bound to the wire its colour label stands on, and the
cable reference is generated per core and written into the Conductor
entry via Conductor::setCableReference(label, cable uuid, slot). The
conductor entry is where it has to live because the terminal strip
plan (Klemmenplan) consumes it in the next step.

Numbering: "Cables" rule under Programmeinstellungen/Neues
Projekt/Nummerierung auto (mirrored in project properties), literal
"W" fallback without a rule, French gate dialog when no rule exists,
Nummerierung menu with the renumber dialog (preferred axis, per-folio
counter, rule and counter state written back, hand-typed names ask,
whole-project scope).

Also included: label block and under-line texts with per-text font and
alignment plus the material-list cell margins, free-core placement and
right-click removal, claim questions for wires of other cables,
multi-selection drag as one undo step, type change with confirmation,
cross-references for type "Cable" (%f-%l%c, own font, clickable links
in exported PDFs), report propagation of the cable definition,
Listes menu (table of contents, material list, terminal strip manager,
terminal generator plugin, cable list), cable list with freely
selectable columns (installation/localisation of cable, start and end,
Blatt start/end, length, used cores...), CSV export dialog with column
pick and preview in the Projekt menu, script API (addTable kind
"cable_list", exportCableList) and a unit test for the type catalog.
2026-10-08 15:56:00 +02:00
Levi Jetzer aba7a3d8b5 Reapply "Feature: Terminal potential grouping"
This reverts commit 1793dcb9a8.
2026-10-08 15:00:13 +02:00
Beat Hangartner 1023b38f8e Sort elements and terminals by position with a strict weak ordering
Two position comparisons handed to std::sort were not strict weak
orderings, which std::sort requires; with the wrong kind of comparator
the sort is undefined behaviour (libstdc++ can read past the range,
MSVC debug builds assert "invalid comparator").

- comparPos(), used when renumbering the elements of a project, ended
  with "<=" on x and y, so two elements at the same position (pasted at
  the origin, placed by a script, stacked symbols) were each "before"
  the other.
- The terminal numbering dialog compared positions with a 1 px
  tolerance ("within 1 px counts as aligned, then compare the other
  axis"), which is not transitive: terminals at x = 2, 1.1 and 0.2 give
  a < b, b < c and c < a.

Move the comparisons into positionorder.h, a header-only helper:
xThenY()/yThenX() with "<", and roundedXThenY()/roundedYThenX(), which
round the positions to whole pixels first so that items a fraction of a
pixel apart still count as aligned, as the tolerance meant to, while
staying transitive. comparPos() keeps its folio and row-letter stages
and calls xThenY() for the last one.

No file-format change. Elements at distinct positions sort exactly as
before; the terminal order changes only for terminals less than a pixel
apart that straddle a half-pixel boundary.

Tests: tst_positionorder checks each order on a grid of awkward
positions by brute force (irreflexive, asymmetric, transitive, with a
transitive equivalence), the three-terminal cycle, twenty items at one
position, and that std::sort leaves the list sorted and intact. The
helper is new, so the test cannot fail on master; the call sites are
the two replacements in the diff.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Beat Hangartner <beat@hangartners.ch>
2026-10-08 06:03:31 +02:00
ispyisail 51675c8a65 Merge pull request #1380 from bhangart/fix/image-stale-crop-on-load
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m15s
Drop a picture's stale crop when loading a project
2026-10-08 13:51:14 +13:00
ispyisail f5962ecd06 Make English the source language of the interface text
Every tr() source string in the code and the forms is now the English
text, and French is a translation (qet_fr.ts) like the other languages.
Generated, not hand-edited: master ef795a21e5 converted by
qet-en-source (harness 4c4ddc9).

3144 messages get an English source, 665 already had one, 0 left French; 8 shared-wording pairs disambiguated.
C++: 2904 literals in 233 files. Forms: 809 strings in 65 files. All 34 .ts files re-keyed; qet_fr.ts filled.
lupdate check: PASS. What each of 38 languages shows, old vs new: same 118266, English instead of French 38433, comment fallback 51, differences 0.
2026-10-08 12:50:13 +13:00
Beat Hangartner e3a1a67c66 Recognise seqhf_1 when loading sequential numbers saved the old way
Before the <sequentialNumbers> element, an element's or conductor's
sequential numbers were saved as the attributes sequ_1, sequf_1,
seqt_1, seqtf_1, seqh_1 and seqhf_1. The readers check for one of them
to take the old route, and all three lists had the same slip: sequf_1
twice, seqhf_1 never (Element::fromXml, Conductor::fromXml, and
readSequence() in the project database). A file whose only old
sequence was the hundred-folio one took the new route, found no
<sequentialNumbers>, and lost it; the database built its labels from
an empty sequence instead of refusing the fast path as it does for the
other five attributes.

Name seqhf_1 in the three lists.

No file-format change: nothing is written differently, and a file with
any of the other five attributes loads exactly as before.

Tests: tst_legacysequentialattributes runs the binary's --resave on a
fixture whose element and conductor carry only seqhf_1 (and a second
pair carrying sequ_1 as a control) and reads the <sequentialNumbers>
written back. The two seqhf_1 cases fail on master.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Beat Hangartner <beat@hangartners.ch>
2026-10-07 23:38:34 +02:00
Beat Hangartner be48b59e8f Drop a picture's stale crop when loading a project
Before #1310, undoing a crop left the crop rectangle in place, so a
project saved afterwards has a <crop> but shows the uncropped picture.
Since #1368 the picture shown is computed from the original and the
crop, so the first mirror or colour key of such a picture applied the
old crop again, and undoing that edit showed the cropped picture.

The picture shown is always exactly the size of the crop. fromXml() now
drops a crop that does not match it: the crop becomes the whole
original, and when the shown picture is not the original's size either,
the shown picture becomes the original. Valid files load and save as
before.

New tst_imagestalecrop: a valid crop is kept; a stale one is dropped
for a shown picture of the original's size and of another size, where a
later crop cuts the picture that was shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:54:42 +02:00
ispyisail ef795a21e5 Merge pull request #1330 from saschbe/fix/attached-text-formatting
Fix plain-text paste styling in attached element texts
2026-10-08 08:24:23 +13:00
saschbe 43c17b41cb Use field styling for plain clipboard paste in attached user texts 2026-10-07 17:44:28 +02:00
Beat Hangartner afa35e094d Undo restores whether a picture's pivot was placed by hand
A picture remembers whether its pivot was placed by hand: a hand-placed
pivot is saved and kept through a resize, a default one is not saved
and recentres after a resize. Two actions changed that flag outside
their undo step:

- applyCrop() marked the pivot as default after moving it to the centre
  of the kept region. After Ctrl+Z the pivot was back where the user
  had put it, but marked as default, so the next save dropped it.
- dragging the pivot handle marked it as hand-placed. After Ctrl+Z the
  pivot was back at the centre but still marked as hand-placed, so a
  later resize left it at the handle's anchor corner.

pivotIsCustom becomes a property, and both actions put it into their
undo command next to rawPivot. tst_imagecropundo checks that a project
saved after undoing a crop keeps its hand-placed pivot.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 10:54:53 +02:00
ispyisail 140f9f6e39 Merge pull request #1355 from ispyisail/feat/upright-symbol-texts
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m54s
Add an option to keep texts of turned symbols horizontal
2026-10-07 21:39:49 +13:00
ispyisail fd0df50c17 Merge pull request #1354 from ispyisail/feat/1335-flip-elements
Add horizontal and vertical mirror for symbols on a folio (#1335)
2026-10-07 21:30:18 +13:00
ispyisail aac004bcdd Merge pull request #1368 from bhangart/refactor/image-pixmap-from-source
Compute a picture's displayed pixmap from its source
2026-10-07 21:24:39 +13:00
ispyisail 3662032289 Merge feat/1335-flip-elements into feat/upright-symbol-texts
Brings in master's DXF blocks (#1339) through #1354. The upright texts
now live in drawSymbol() too, and a turned symbol whose texts stay
horizontal is drawn in full like a mirrored one: an INSERT would turn
its texts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:17:38 +13:00
ispyisail a55c1312de Merge pull request #1357 from ispyisail/fix/aligned-text-lines-auto-width
Fix line alignment of multi-line symbol texts with Auto width (#1348)
2026-10-07 21:12:20 +13:00
ispyisail 14a31e895e Merge master into feat/1335-flip-elements
Conflicts with the DXF blocks export (#1350) and the picture factory
leak fix. The mirror now lives in drawSymbol(); a mirrored symbol is
drawn in full rather than as an INSERT of its block, since a negative
INSERT scale would mirror its texts too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:11:00 +13:00
Laurent Trinques e2fd2d1c20 Merge pull request #1342 from bhangart/feature/free-text-resize-handles
Resize free texts by dragging a corner
2026-10-07 06:36:06 +02:00
Beat Hangartner 1b0ab6d26a Compute a picture's displayed pixmap from its source
The displayed pixmap and imageSource (original, crop rectangle,
transparent colours) were two undo values that had to change together,
although the pixmap follows from the source. An action that updated
one and not the other would bring back the bug fixed in #1310.

setImageSource() now recomputes the displayed pixmap, and crop, colour
key, mirror and replace push one undo step on imageSource alone. A
plain QUndoCommand holds the property change, so two identical actions
in a row stay two steps. Loading a project still shows the saved pixmap
as before.

tst_imagecropundo also checks the size of the saved picture after undo
and after redo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 06:25:14 +02:00
ispyisail 36df4b3010 Centre or right-align the lines of symbol texts with an automatic width (#1348)
A symbol text on a folio with its width on "Auto" kept every line of a
multi-line text on the left, whatever its alignment: a QTextDocument
with no text width has no width to centre or right-align lines in. It
only worked once the user gave the text a width wide enough.

With the width on Auto and a centre or right alignment, give the
document the width of its longest line. The size of the text does not
change, only where the shorter lines sit. The fit is redone when the
text, the alignment, the width or the font changes; on a font change
the text's anchor is kept, as the font change itself does.

textWidth() now returns the width the user set (-1 for Auto), the value
that is saved, rather than the document's, as IndependentTextItem
already does. Readers (properties panel, resize-handle undo, script
API, project database) see -1 for Auto as before.

Checked: every folio of the 25 example projects renders identically to
master (136 PNGs); a centred and a right-aligned two-line text now
render aligned; changing their font size leaves the same box and anchor
as master, and without the font-change refit the lines wrap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:12:22 +13:00
Beat Hangartner 9882a79a93 Refresh the free text handles when the text comes back to a folio
itemChange() removed the corner handles when the text left the scene
but did not bring them back when it returned still selected, which Qt
allows (undoing a scripted delete does it) without a selection change.
Refresh on every scene change. The two handle helpers are protected:
nothing outside the class calls them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 01:51:00 +02:00
ispyisail 592cbdbf0b Keep the texts of turned symbols horizontal, per project (#1335)
A project can now keep the texts drawn in its symbols, and the names of
their terminals, horizontal when a symbol is turned: Project properties >
General, "Garder horizontaux les textes des symboles pivotés". The box of
each text turns with the symbol; the text does not, and reads as it does
in the symbol itself.

It is a project setting, saved as <symbol_texts upright="true"/> and
only when on. A new project starts with it on; a project saved without
it (every existing one) reads with it off, so it looks and saves exactly
as before, and looks the same on every computer. The MCP server's new
projects start with it on too.

It builds on the mirror of #1354, which already redraws the texts of a
mirrored symbol readable: what the symbol does to its texts is now its
mirrors and, with the setting on, its turn (Element::symbolTextsTransform()).
ElementPictureFactory caches one drawing per such transform, terminal
names undo it the same way, and the DXF export places the texts alike.

The fields of a symbol (label, comment...) already keep their angle with
"Garder la rotation visuelle" and are left as they are.

Known limit: two texts stacked in a symbol end up side by side when it is
turned, and can overlap when kept horizontal; the setting can be turned
off for such a project.

Test: tst_uprightsymboltexts turns the symbols of a folio and checks the
angle of each motor's "M" in the DXF, with the setting off and on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:43:04 +13:00
ispyisail a3b1468b0f Mirror: terminal names, reload drawings, turned text groups, a leak
Found by review of #1354:
- Terminal names (shown on the folio) read backwards on a mirrored
  symbol: Terminal::paint() now mirrors them back about the centre of
  their box, in the frame they are drawn in.
- "Reload the drawings of the symbols" left mirrored symbols with the old
  drawing: reloadPicture() refreshes the mirrored pictures too.
- Turning a group of texts on a mirrored symbol left the group's
  readable mirror about the centre of its old box until the next layout:
  ElementTextItemGroup::setRotation() reapplies it, as texts already do.
- The mirrored build of a symbol's drawing leaked the texts of its
  primitives, which only the drawing without mirror keeps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:13:27 +13:00