Deleting a cable and pressing Ctrl+Z twice leaves the cable out of the
project while AddCableCommand still stands and still thinks it has to
clean that cable up. The next edit of any kind makes the stack throw the
undone commands away: ~RemoveCableCommand frees the cable, and
~AddCableCommand frees the same memory again. A plain build hides it,
AddressSanitizer reports it as a heap-use-after-free in
addcablecommand.cpp (blocking review comment by ispyisail).
AddCableCommand now holds the cable as a QPointer, which is what
RemoveCableCommand already did: whoever of the two runs first frees it,
the other one sees a null pointer and has nothing left to do, in either
order of destruction. The destructor also stopped dereferencing
project() without checking it, which it did on that very line.
Covered by tst_cableundointegration, a probe linked against the
application's objects the way the other integration probes are. It draws
a cable, deletes it, undoes twice and pushes a further edit -- the
sequence which used to crash. Verified in both directions: with the raw
pointer the probe dies with SIGSEGV inside ~AddCableCommand called from
QUndoStack::push, with the QPointer it prints its PASS line.
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
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
The project database is kept up to date edit by edit, and a script's
qet.query() and the folio tables read it without a full rebuild. Three
edits left it out of date until the next rebuild:
- adding or removing a folio: the folios after it kept their old
position and folio number, and a removed folio's diagram_info row
stayed;
- moving a symbol: its folio cell (element.pos) stayed where the symbol
was added;
- deleting a wire: its two ends stayed in the terminal table, which
lists the terminals a wire ends on.
Positions and folio numbers are now written again after a folio is
added, removed or moved (one function for the three), the removed
folio's diagram_info row goes, a moved symbol's cell is written when
the database is next read (as drawing items are), and a deleted wire's
ends go unless another wire ends there.
Found by comparing the tables after random scripted edits with those
a fresh open of the saved project gives. Left alone: labels and wire
numbers made from a folio formula, which the drawing itself keeps until
the project is reopened.
Tests (tst_databasefromdocument): folioAddedAndRemoved,
movedElementCell, deletedWireTerminals; each fails without its part of
the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
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
One row per element and element it is linked to -- a coil and its
contacts, a pair of folio reports -- from each side, with the contact
group the element saved for it. Nothing reads it yet: cross-references
are the main thing one folio needs from another, and this is what lets
them be answered without building every folio.
Filled like the other tables: from the document when it can be read
exactly (a link only one side lists, between kinds that cannot link, or
giving a contact a second coil is left to the folios), from the folios
otherwise. A link made or undone afterwards rewrites that element's rows
when the database is next read, as drawing items are, since the contact
group is set after the link is made.
Test: tst_databasefromdocument compares the link table both ways on
every example; linksAreListed checks a coil and contact (with a group)
in m_000.qet, oneSidedLinkFallsBack the refusal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
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
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.
edgeCases() expected an empty device to be exactly MinHeight (30) tall.
The sizing rule is max(MinHeight, 2 x label height) rounded up to the
grid, so with a taller fallback font the body is 40 and the test fails.
The Linux CI image has no Liberation Sans; fontconfig substitutes Nimbus
Sans (fonts-urw-base35), whose line height gives 40.
Compute the expected height from the label font's metrics, as the
layout does.
Reproduced locally with a fontconfig file listing only the urw-base35
fonts: the old test fails with the CI's exact message (40 vs 30), the
new one passes. Doubling the label factor to 3 in genericdevice.cpp
still makes the new check fail, with Liberation and with Nimbus.
Reported by bhangart on #1418.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two defects found while re-testing the cable management after the
upstream merge, plus one change of intent for the numbering question.
The question "define a cable numbering rule now?" kept its answer in
the program settings, so saying no once silenced every project of the
program -- the exact opposite of what it should do. The answer now
travels with the project itself, stored on the <cable_autonums>
element as ask_numbering_rule="false"; a file which does not hold the
answer asks again, and taking the rule away in the project properties
or in the numbering window brings the question back for that project
alone. Read-only projects are no longer asked at all, since there is
nowhere in them to define a rule.
The numbering window could not hand out the first rule of a project:
it drew the rule, worked the numbers out with it, and only wrote the
rule into the project together with those numbers -- while the
planning refused to run on a project which had no rule saved yet.
The counters of a cable rule are kept under its fixed name anyway, so
planning now falls back to that name when the project holds no rule
yet: a rule which is being drawn in the window counts from the moment
it is complete, and the first renumbering of a project works.
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>
"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>
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>
tst_imagehandleescape covers #1414 through the real editor: a picture's
resize, rotate and pivot handle are pressed and dragged with scene mouse
events, Escape goes to the DiagramView, then the rest of the gesture.
Each time the picture is back as it was at the press, still selected,
and no undo step is recorded; without a drag, Escape still clears the
selection. The three drag cases fail with #1414 reversed (Escape cleared
the selection under the drag).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first test that runs a real QETApp, offscreen (qetapptest.h): it
links qelectrotech_objects, keeps its settings, configuration and data
in a temporary folder, and triggers the editor's own actions.
tst_rotateselectionimage covers #1413: Space ("rotate_selection") turns
a picture's rotationAngle, not QGraphicsItem's rotation, in place, and a
picture read back from its XML has the angle; undo and redo, twice; and
a group turn ("rotate_group_selection") moves each picture's pivot a
quarter turn around the group's centre. All three fail with the code
from before #1413.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The executable compiled every source file itself, so a test could only
reach the application's classes by listing their .cpp files one by one,
which stops working as soon as a class needs a folio, its view or the
editor window -- that pulls in nearly everything.
The sources (except main.cpp) and the two .qrc files now build once as
qelectrotech_objects, an OBJECT library; the executable is main.cpp
linked with it. Its link libraries and include folders are PUBLIC, and
it passes on this directory's compile definitions, so a target in
tests/ that links it compiles and links like the application.
lupdate's targets scan the library (main.cpp has no tr() text).
The executable, its compile flags and what it saves do not change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The French of the six new texts (the crop mode's name and hints, "Crop on
the folio", "Reset crop", "Properties..."), as lupdate's own entries,
inserted where lupdate places them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
The texts added with dropping picture files on a folio (#1416) had no
entry in the translation files yet, so the French interface showed them
in English, and the English one wrote "Add 1 image(s)".
- qet_fr.ts: the French of all nine texts (the eight of #1416 and the
"%1: %2" of the list of refused files), "%1 : %2" with the French
space before the colon.
- qet_en.ts: the singular and plural of "Add %n image(s)".
The entries are lupdate's own, inserted where lupdate places them, so a
later refresh of the .ts files moves nothing; lupdate run over sources/
finds them all as existing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The message listing the files a drop could not add wrote each line as
"name : reason", with the space French puts before a colon, and as a
fixed string that no translation could change. It is now a tr() text,
"name: reason", so each language can write it its own way.
Suggested in the review of #1416.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tst_menubarkeyboard sends Alt+F and F10 right after the test window is
exposed, then sleeps 300 ms and checks. Menu mnemonics and the F10
shortcut (a Qt::WindowShortcut) only fire in the active window, and
being exposed does not mean being active: on a loaded Xvfb, or on macOS
with another application in front, activation arrives later and the key
is dropped. The sleep comes after the key and cannot help. This made
shortcutOpensMenuBar fail on #1420's Linux CI ("F10 did not activate the
menu bar") while the same base passed on #1421 and #1422, and made the
Alt+F control fail in 1 of about 33 local runs with the cocoa platform.
Every test now waits for the window to be active before pressing a key
(qWaitForWindowActive, with its own failure message), and the two tests
that expect a menu poll for it with QTRY_VERIFY2 instead of a fixed
sleep. plainF10DoesNothingInQt keeps its 300 ms: it checks that nothing
happens, which cannot be polled for; with the window now active first,
that check can no longer pass merely because the key went nowhere.
Test-only change. Checked: with a second window active, F10 sent to the
exposed but inactive window does not activate its menu bar (offscreen
and cocoa); once it is active, it does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
lupdate over sources/ adds the 32 strings merged since the conversion to
English (#1390); with the entries already empty on master, 59 had
no Polish text and 2 only a lupdate guess. All 61 are translated with the
file's terms; "cable" is
"przewód" and its wires "żyły".
Corrections to existing entries:
- "master" (master element) is "główny", not "nadrzędny", in the 11
entries that still used it (properties, cross-references, search and
replace, terminal editor); "nadrzędny" stays where it means a parent
folder or collection.
- the help text for %folio in two widgets still had a French sentence
("Les variables suivantes sont utilisables :") inside the Polish text.
- "Komentaż PLC" typo, now "Komentarz PLC".
- 6 colons with a French space before them ("Marginesy :").
Most of the diff is lupdate moving line numbers. Checked with lrelease
(3847 translations, no warning) and the placeholder check for every
changed entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lupdate over sources/ adds the 32 strings merged since the conversion to
English (#1390), among them picture drop, element scaling, paste element
information, the terminal strip cable fields and the settings-loading
dialog. With the 29 entries that were already empty on master, 61
entries had no German text, so the German UI showed
English there. All 61 are translated with the terms the file already
uses (Bauteil, Anschluss, Klemme, Raster, Kabel/Adern, Spiegeln).
As arummler noted in #1381, "Programmable Logic Controller (PLC)" read
"SPS (PLC)"; it is now "Speicherprogrammierbare Steuerung (SPS)" in both
places, and "PLC-Querverweis" becomes "SPS-Querverweis" like every other
PLC string in the file.
Most of the diff is lupdate moving line numbers. Checked with lrelease
(3847 translations, no warning) and the placeholder check for every
changed entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
25 of the 79 plural messages in qet_fr.ts used the old "(s)" marker in
both forms, e.g. "Ce document PDF contient %n page(s)" for one page and
for many. They come from the time French was the source language: the
French source text had "(s)", and when French became a translation in
#1379 and #1390 that text was copied into both forms. Reported by
arummler in #1381.
Each of them now has a singular form (also used for 0, as Qt's French
rule does) and a plural form, with the agreements that follow ("%n
borne de cet élément n'est pas sur la grille" / "%n bornes ... ne sont
pas"). Only <numerusform> lines change; no other message is touched.
Checked with lrelease (3815 finished, no warning); placeholders checked
for every form.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7c6a0411d set PACKAGE_TESTS=OFF in windows-build.yml to make the job
green again. The job had turned red because three tests merged on
2026-10-07 (tst_toolbarsettings, tst_diagramtoolbarsettings,
tst_gesturesettings) set no settings scope, so QSettings on Windows had
no registry key and they failed. #1411 gives them their scope. With
PACKAGE_TESTS=OFF the "Run the unit tests" step finds no tests and
still succeeds, so the job checks nothing.
Set PACKAGE_TESTS back to ON.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Beat Hangartner <beat@hangartners.ch>
Edit > Add > Generic device (also on the Add toolbar, and found by the
command search as diagrameditor.add_generic_device) opens a three-page
wizard for devices used too rarely to draw a symbol for, such as a VSD
or a power supply:
1. Layout: terminals per side (0 for none), a gap every N terminals,
divider marks on gaps, groups lined up across opposite sides, pitch
and extra size.
2. Terminals: a table per side and the same list as one line, where
In{4} is In1..In4, Q{z4} is Q0..Q3, X{3-6} is X3..X6 and an empty
entry is a gap; number pins 1..n counter-clockwise; paste rows of
side, name, function from a spreadsheet or a CSV file.
3. Information: name, label, manufacturer, reference, description.
A preview beside every page shows the box as it will be placed. Finish,
available from the first page, writes an ordinary symbol to the folder
"Generic devices" of the project's collection and puts it on the cursor.
The box sizes itself to its terminals and their names; every terminal
lands on the grid and gets a uuid. No new XML: any QElectroTech reads it.
The layout is pure (sources/genericdevice/genericdevice.cpp) and tested
by tst_genericdevice. ElementPictureFactory gains pictureFromDefinition()
to draw the preview with the same code as a placed element.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>