Commit Graph

322 Commits

Author SHA1 Message Date
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
Kellermorph 9771ca96c9 Make the cable feature speak English like the rest of the interface
English has been the source language of the program for a while now
(upstream: "Make English the source language ..."), the cable feature
came in with French ones: about 200 translatable strings over 30 files,
the two settings pages built from .ui files, four comments and one test
which asserted the old wording (blocking review comment by ispyisail).

The wording follows what the code base already uses where it exists:
Plant (=) and Location (+) as qetinformation.cpp names those element
information, Designation for the field the cable form calls its BMK,
Length, Sheet for folio, Automatic numbering, Cross-reference, and Right
aligned / Centred / Left aligned the way cabletextformatdialog.cpp
already spells the three alignment entries.

Two things are more than a word-for-word swap:

- xrefpropertieswidget.cpp:291 wrote the label of the master field on
  every type change with a hardcoded tr("Maitre"), so the English
  "Master" coming from the .ui was overwritten for every type, cable or
  not: a real bug next to being French. The wording now comes back from
  the .ui and only a cable renames the field to "Text:", so the two
  cannot drift apart again.

- tst_cabletypelist kept asserting that a new catalogue is *labelled*
  in French. That label line is the translated header and reads English
  now. The test still reads files with French headers on purpose:
  headerAliasMap() keeps the five old French labels as aliases next to
  the English ones, so catalogues written by earlier builds, including
  the one the reviewer reads her own types from, keep loading.

The .ts files are deliberately not regenerated here. Upstream
regenerates them on master, as it did for the language change itself,
and a translation is keyed on the source string: while the sources were
French a German user saw French in the cable dialogs, and an English
source string with no translation yet shows as it is.

Build clean, ctest 92/93, the one failure being the pre-existing
headless tst_menubarkeyboard.
2026-10-10 13:48:45 +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 420e211383 Merge pull request #1444 from ispyisail/fix/table-chain-deleted-folio
Fix a split table going blank and crashing after a folio is deleted
2026-10-10 21:12:06 +13:00
ispyisail 5b98f1a1e9 Skip the folio-deletion test where --run is not built
CI builds without Qt Qml, so --run is not compiled in and the test's
QElectroTech opened the project and waited until the 180 s timeout.
The test now skips when QET_HAS_SCRIPTING is not defined, as the other
--run tests are left out of such builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
2026-10-10 20:56:25 +13:00
Kellermorph 3f23e3dd2b Create the folder of the cable types file before writing into it
The catalogue is written with QSaveFile, which cannot write into a
folder that is not there. A fresh profile has no documents folder, and a
path typed into the settings may name a folder which was never created,
so the first write failed with "No such file or directory" and no way
for the user to see that the folder was the problem rather than the file
(non-blocking review comment by ispyisail).

writeFile() makes the folder first, which covers every writer: creating
the template, appending a type and editing one all go through it.

Covered by tst_cabletypelist::aMissingFolderIsCreated, writing into
<tmp>/not/there/yet/qet_cable_types.csv. Verified in both directions:
without the fix the test fails with 'createFile(path, &error) returned
FALSE. (No such file or directory)', with it the file is created and
reads back.
2026-10-10 09:42:59 +02:00
Kellermorph a16964c055 Fix the double free when a deleted cable is undone and edited again
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.
2026-10-10 09:24:15 +02: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 5e6677ebec Keep the project database current when folios, symbols and wires change
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
2026-10-10 19:07:03 +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 13dbacef1e Merge pull request #1442 from ispyisail/feat/db-link-table
Add a link table to the project database
2026-10-10 18:19:57 +13:00
ispyisail 3089408b19 Add a link table to the project database
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
2026-10-10 17:22:56 +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
ispyisail 4b99c73c03 Merge pull request #1428 from bhangart/fix/free-text-nonfinite-position
Load a free text's non-finite position or rotation as 0
2026-10-10 07:51:59 +13:00
ispyisail 82716f6291 Merge pull request #1427 from bhangart/test/menubar-wait-active
Wait for the window to be active in the menu bar keyboard tests
2026-10-10 07:51:55 +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
ispyisail 2d5a249039 Fix tst_genericdevice failing on machines without Liberation Sans
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>
2026-10-10 07:28:03 +13: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 7866a37f14 Wait for the window to be active in the menu bar keyboard tests
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>
2026-10-09 14:30:27 +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 a3594c6633 Add a generic device: a box symbol with terminals on any side
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>
2026-10-09 23:31:26 +13:00
ispyisail ff35d0dc69 Merge pull request #1416 from bhangart/feature/drop-images-on-folio
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m40s
Drop picture files on a folio to add them
2026-10-09 22:59:20 +13: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 e18bd1c182 Merge pull request #1412 from bhangart/fix/mirror-twice-keeps-position
Mirror a symbol twice and it comes back where it was
2026-10-09 22:45:58 +13:00
ispyisail af978ab907 Merge pull request #1411 from bhangart/fix/settings-tests-windows
Give the toolbar and gesture settings tests their own settings scope
2026-10-09 22:39:37 +13:00
ispyisail aeb97fcaaa Merge pull request #1410 from ispyisail/feature/settings-profile-parts
Add saving and loading only part of the settings
2026-10-09 22:35:37 +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
ispyisail d159a8be94 Settings files: leave out dialog sizes, trackpad gestures with controls
- Dialog sizes and positions (dialoggeometry/...) describe this computer,
  like the other window layout keys: no longer saved to a settings file,
  and kept when one is loaded.
- The trackpad gestures setting (diagramview/gestures) is in the part
  for toolbars, shortcuts and gestures rather than all other settings.
- Loading only the defaults for new projects now says it is done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:09:28 +13:00
Beat Hangartner 2192084483 Mirror a symbol twice and it comes back where it was
Mirroring a symbol on a folio keeps its centre in place and then snapped
the new position to the grid. A symbol that was not on the grid was
pulled onto it, so a second mirror the same way left it a few pixels
away from where it started. In perceuse.qet that is 171 symbols, each
way. tst_scriptmirror::mirrorBackAndSave() picks its symbol in an order
that changes from run to run, so it failed only some of the time (three
runs in eight on macOS, and on the Windows CI).

The move is now snapped to the grid instead of the position. A symbol on
the grid stays on it, one off the grid keeps its offset, and because
qRound() rounds halves away from zero both ways, a second mirror moves
it exactly back.

The new tst_scriptmirror::mirrorTwiceComesBack mirrors every symbol of
perceuse.qet twice, horizontally and vertically, and checks that every
terminal is back in place. It fails on master (171 symbols moved).

No change to the file format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:48:13 +02:00
Beat Hangartner 6de8d406aa Give the toolbar and gesture settings tests their own settings scope
tst_toolbarsettings, tst_diagramtoolbarsettings and tst_gesturesettings
never set an organization or application name. On macOS and Linux,
QSettings then falls back to an "Unknown Organization" file, so the
tests pass there. On Windows it has no registry key: every write is
dropped and every read gives the default, and nine checks fail.

Each test now sets its own organization and application name in
initTestCase(), as tst_scriptingsetting and tst_sheetbackgroundsetting
already do. That also keeps the tests away from the settings of the
account that runs them.

Tests only; no change to the app or its files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:48:13 +02:00
ispyisail 24e67da33c Add saving and loading part of the settings (discussion #1405)
Save settings as... now asks which parts to save: toolbars, keyboard
shortcuts and mouse gestures; defaults for new projects (folio, title
block, wires, numbering); everything else. All three ticked writes the
same complete file as before (format 1, collection folders included).

A file with only some parts is format 2 and lists its parts. Loading it
replaces those parts and keeps every other setting. The folders of the
collections are never in such a file: they rarely exist on another
computer. A QElectroTech that only knows format 1 refuses it rather than
loading it as complete and losing every other setting.

A file holding only the defaults for new projects loads without closing
QElectroTech, since QETProject reads them again for every new project.

Which part a key belongs to is ConfigProfile::partOf(); a key it does
not list falls in "everything else", so a new setting can never be left
out of every part. The test checks that what BorderProperties writes for
the folio defaults lands in the new-project part.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:16:15 +13:00
Beat Hangartner 360065036c Spread several dropped pictures over the folio in a grid
Pictures dropped together were cascaded 20 px apart from the drop
point; once scaled to fit the folio they lay almost on top of each
other. They are now laid out side by side in a roughly square grid
over the folio's drawing area, inside the same 20 % margin, each
shrunk into its cell with its proportions kept. A single dropped
picture still lands on the drop point.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:00:31 +02:00
Beat Hangartner c76bc5eb4e Fit dropped pictures into the folio frame, with a margin
A dropped picture too large for the folio is now scaled to leave 20 %
of the drawing area free on every side, instead of being fitted to
the visible part of the view, and every dropped picture is kept inside
the frame -- a scaled-down one also off that margin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:00:31 +02:00
Beat Hangartner 034013ff67 Drop picture files on a folio to add them
Picture files (png, jpg, bmp, svg) dragged from the file manager onto a
folio are added there: the first centred on the drop point, the others
cascaded from it, and one undo step removes them all. A picture larger
than the visible part of the folio is scaled down to fit it. Files that
cannot be used are listed once after the others have been placed, and a
drop holding only other files (a .qet project) still reaches the main
window, which opens it.

The checks a picture must pass before it is embedded in the project
move into ImageDrop::load and are now shared by the drop, the add image
dialog and the script API: a regular file of at most 10 MB, and at most
64 megapixels, read from the header before any pixel is allocated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:00:31 +02:00
ispyisail 1db025ceae Element files: anchor="alignment" puts a static text's x at its alignment (#1251)
A static text's x/y in an .elmt file are the baseline-left of the text.
A program writing an element (a pinout generator, dxf2elmt) then has to
measure the text with QET's font metrics to right-align or centre it.

With the opt-in anchor="alignment", x is the left edge, centre or right
edge selected by Halignment; y stays the baseline. The box is the text
without the document margin, as ElementPictureFactory draws it, so the
editor and the folio agree. Rotation pivots on x/y as before.

The element editor reads it and writes it back for the texts that had
it; it does not add it to other texts. Files without anchor draw and
save as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:31:16 +13: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
Laurent Trinques 1058c82390 Merge pull request #1398 from saschbe/fix/local-small-handles
Add smaller resize handle sizes
2026-10-08 16:29:07 +02: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
saschbe 31ba717a94 Add smaller resize handle sizes 2026-10-08 15:39:03 +02:00
ispyisail 9baa853cf3 Merge pull request #1392 from bhangart/fix/grid-size-guard
Never use a folio grid step below 1 from the settings
2026-10-08 19:20:19 +13:00
Beat Hangartner ede7bd99e1 Never use a folio grid step below 1 from the settings
The folio grid steps, diagrameditor/Xgrid and Ygrid, are read from the
settings in six places with a plain toInt() and used as they come. The
preferences page cannot store anything below 1, but a hand-edited or
damaged settings file can hold 0, a negative number or text:

- Diagram::snapToGrid() divides by the step, so 0 is a SIGFPE on the
  first click that places a symbol;
- Diagram::drawBackground() runs "while (g_x % xGrid)" and then loops
  "gx += xGrid", so 0 crashes every repaint and a negative step never
  ends;
- the paste, duplicate and align paths divide by it or multiply with it.

Add foliogrid.h, a header-only helper: FolioGrid::step() reads a
settings entry and returns the built-in step (Diagram::xGrid, 10) when
the entry is missing, not a number, below 1 or above what an int
holds (QVariant::toInt() wraps such a value around). Every reader of
the two keys goes through it; the settings page, which only writes the
spin box values, is unchanged.

No file-format change, and no change for any step the preferences page
can produce.

Tests: tst_foliogrid covers the helper: 1, 10, "7", int max are kept;
missing, 0, -5, "ten", "", "nan", 99999999999 (as text and as a
number) and int max + 1 fall back; 7.9 rounds to 8 as before; and the
two settings keys through a QSettings scope of the test's own. The
helper is new, so the test cannot fail on master; the six readers are
the 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:04:35 +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 c7c8b98e01 Merge pull request #1386 from bhangart/fix/sequential-placeholder-order
Replace %sequ_10 before %sequ_1 in label formulas
2026-10-08 13:46:16 +13:00
ispyisail 02031fbdde Merge pull request #1385 from bhangart/fix/legacy-seqhf-attribute
Recognise seqhf_1 when loading sequential numbers saved the old way
2026-10-08 13:46:09 +13:00
ispyisail 8145cd3f7a Merge pull request #1384 from bhangart/fix/borderproperties-qreal-sizes
Read the default border's column width and row height as decimals
2026-10-08 13:46:02 +13:00
ispyisail 9517f80abc Merge pull request #1383 from bhangart/fix/conductor-size-settings
Read the default conductor width from the settings as a decimal
2026-10-08 13:45:55 +13:00
ispyisail 82c77ea250 Expect the English wording in the dark-picture checkbox test
The interface text is now English at the source, so the untranslated
checkbox and tooltip read in English.

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