Commit Graph

9603 Commits

Author SHA1 Message Date
ispyisail 4c9a307cb9 Say what realLibrary() can and cannot check
Measured on a macOS 15 runner with 3DxWare 10.8.13: an ad-hoc signed
test binary with the hardened runtime loads 3DconnexionClient with or
without the entitlement, so ad-hoc signing does not enforce library
validation and CI cannot prove the entitlement is needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 23:07:34 +12:00
ispyisail 4381c6caa3 Read the 3D mouse through 3DxWare on macOS when it is installed
On macOS, 3DxWare installs a driver extension that takes the 3D mouse
over. With it installed, HidBackend opens the device but receives
nothing, so the mouse did nothing in QElectroTech until 3DxWare was
uninstalled (discussion #599, PR #1028). Most Mac owners of a 3D mouse
have 3DxWare installed.

ConnexionBackend asks 3DxWare for the motion instead, through
3DconnexionClient.framework, as Blender does. SpaceMouseListener tries
it first. When 3DxWare is not installed, or is installed but its driver
is not running, it falls back to HidBackend, so the device works in
both setups. Take-over mode stops 3DxWare's own actions in QET, so the
view does not move twice.

The library is loaded at run time from where 3DxWare installs it:
nothing is linked or bundled, and the build needs no SDK. The few
declarations are written here, from Blender's
GHOST_NDOFManagerCocoa.mm, because 3Dconnexion's SDK headers may not be
redistributed. 3DxWare's axes are y up and z away from the user; they
are mapped to QET's raw USB convention by comparing Blender's 3DxWare
and spacenavd code paths.

The release script signs with the hardened runtime, which refuses a
library another team signed. misc/qelectrotech.entitlements adds
com.apple.security.cs.disable-library-validation (Blender's notarized
build carries the same one), and MacQetDeploy_arm64_cmake.sh now passes
it to all four signings of the app, including the re-sign inside the
DMG.

Tested: tst_spacemouseconnexion runs the backend on every platform
against fakeconnexion, a stand-in library that answers from its own
thread as 3DxWare does: registration, the axis mapping, buttons, other
clients' messages, 3DxWare not installed or not running, deletion
with a message in flight. Flipping an axis sign or dropping the client
check turns it red. realLibrary() loads the real framework when
3DxWare is installed. Linux Qt 6 build with the 3D mouse enabled: all
18 tests pass.

Not tested: on a Mac with a real device. The axis signs and whether
buttons arrive as a bitmask with current 3DxWare are unverified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:54:07 +12:00
ispyisail 07d7aedc26 Fix bugtracker #240: crash when a recovery file cannot be opened
After a crash, QElectroTech offers to reopen its recovery files. If one
cannot be read, answering OK crashed the program instead of showing the
"could not open" warning: QETProject(KAutoSaveFile *) takes ownership of
the file and deletes it on failure, and openBackupFiles() then read the
file name from the deleted object to build the warning. Read the name
first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-25 21:45:07 +12:00
Kellermorph 5622308ba2 Derive empty-slot labels from the group's declared terminals
Both issues from the review, preview of empty contact comb slots only:

- Slice the flat label list over the declared poles in drawAsContacts
  (terminals per pole from labels.size()/contactCount) instead of a
  fixed stride of 2 (3 for a switch) inside drawContact: terminalCount
  and contactCount are edited independently, so with 3 poles and the
  default terminal count of 2 the fixed stride starved every pole but
  the first. Available labels are now distributed across the poles.
- Map changeover labels of a slot to their own position always
  (common=stored[0] right, NC=stored[1] bottom-left, NO=stored[2]
  top-left, missing entries stay empty) instead of falling back to the
  raw stored order, which put the numbers on the wrong contact halves
  when the terminal count was below three.
- Element editor: changing the contact count now keeps the terminal
  count in step (same terminals per contact, type default 2/3 for
  inconsistent data, minimum 3 for a switch), so the mismatch cannot
  be created anymore; legacy mismatched data is handled by the new
  slicing.
- Drop the now redundant elmt check before is_power_ctc (it already
  includes it) - the dead null check from the review.
2026-09-25 11:24:35 +02:00
ispyisail 149ae961cc Fix bugtracker #343: saving reorders texts and shapes after an edit
Diagram::toXml() writes texts, images, shapes and tables in items()
order. The diagram scene uses NoIndex, and Qt's linear index sorts its
item list by pointer address the first time any item is removed from
the scene, which the editor does constantly (selection handles, for
one). From then on the order in the saved file follows memory
addresses, so moving one element reshuffles unrelated blocks and a
version-control diff of the project becomes unreadable.

Write those blocks in stacking order instead, read from a rect query,
which Qt sorts by z and insertion order even with NoIndex. Reloading a
file rebuilds exactly that order, so a resave is stable and the drawing
does not change. Elements and conductors were already sorted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-25 21:16:34 +12:00
Laurent Trinques ac40d110fb Merge pull request #1028 from ispyisail/fix/spacemouse-macos-shared-open
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m51s
Fix 3D mouse not detected on macOS while 3DxWare is running
2026-09-25 11:13:33 +02:00
ispyisail 4f309770f0 Fix bugtracker #340: small PDF text drawn too bold in Adobe Acrobat
Qt 6 declares the PDF/X namespace (pdfxid) in the XMP metadata of every
PDF it writes, even when the file is not PDF/X. Adobe Acrobat then draws
small text too bold at some zoom levels. Qt only needs the declaration
for PDF/X-4 output, which QElectroTech never asks for.

Blank the declaration out after export, in place with spaces so no
offsets move, in both the export window and the --export-pdf path.
Diagnosis and the Acrobat testing by Alf, bugtracker #340.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-25 20:44:02 +12:00
ispyisail 72cf86ece0 Open the 3D mouse non-exclusively on macOS
hidapi's macOS backend opens every device with
kIOHIDOptionsTypeSeizeDevice unless told otherwise (hid_init() calls
hid_darwin_set_open_exclusive(1) for backward compatibility). When
3DxWare is running it already holds the SpacePilot/SpaceMouse, so the
seize fails, hid_open_path() returns NULL and HidBackend::scan() keeps
retrying every 3 s without ever finding the device.

Reported in discussion #599: a SpacePilot Pro (046d:c629) is listed by
hid_enumerate() and works in 3DxWare, but has no effect in QET's macOS
build. Enumeration never opens a device, so it did not exercise this.

Guarded on HID_API_VERSION >= 0.12, where the setter first appeared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:27:37 +12:00
Laurent Trinques 8aefdcc481 Merge pull request #1027 from ispyisail/fix/bugtracker-95-collection-project-name
Fix bugtracker #95: Collections panel shows "Projet sans titre" for untitled projects
2026-09-25 09:05:25 +02:00
ispyisail 3a45625d69 Fix bugtracker #95: collections pane shows "Untitled project" for a titleless project
The root of a project's embedded collection showed "Projet sans titre"
whenever the project had no title, while the project panel shows the
file name. Fall back to the file name the same way, and only use
"Projet sans titre" for a project that has neither.

The name was also computed once, so changing the project title or saving
it under a new name left the pane stale until the collections were
reloaded. Update it on projectTitleChanged and projectFilePathChanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:24:17 +12:00
Laurent Trinques 988357057f macOS add SPACEMOUSE_BACKEND=hid 2026-09-25 05:15:06 +02:00
Laurent Trinques e9676ef87e Merge pull request #1026 from ispyisail/feature/spacemouse-windows
Build the Windows package with 3D mouse support
2026-09-25 04:30:40 +02:00
Laurent Trinques ca507dc0f3 Merge pull request #1025 from ispyisail/feature/spacemouse-hid
Add a 3D mouse backend that reads the device over USB (hidapi)
2026-09-25 04:12:35 +02:00
Laurent Trinques 1932150ea6 Merge pull request #1023 from ispyisail/feature/spacemouse-settings
Add 3D mouse speed, direction and twist-to-zoom settings
2026-09-25 03:53:15 +02:00
Laurent Trinques 23241fba69 Merge pull request #1022 from ispyisail/fix/spacemouse-element-editor
Fix 3D mouse doing nothing in the element editor
2026-09-25 03:52:23 +02:00
ispyisail f2f1051914 Build the Windows package with 3D mouse support (hidapi)
Installs MSYS2's hidapi and turns on QET_ENABLE_SPACEMOUSE with the
hidapi backend, so the Windows package reads a 3Dconnexion SpaceMouse
directly over USB, with no 3Dconnexion driver (discussion #599).

The existing transitive DLL scan copies libhidapi-0.dll into the package,
and both installers take everything in bin/. Two checks make a silent
loss fail the build instead: CMake only warns when hidapi is missing, so
the exe must link against it, and the DLL must be deployed, or
QElectroTech.exe would not start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:30:48 +12:00
ispyisail 313aafc95b Add misc/spacemouse-capture.py to record a 3D mouse's raw USB reports
For device owners on Linux: guided movements, with every raw report and
the device's report descriptor saved to one JSON file. Dropped into
tests/qttest/fixtures/spacemouse/, a recording is checked by
tst_spacemousehid against what the user was asked to do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:00:59 +12:00
ispyisail aaeaff55cc Add a hidapi 3D mouse backend that needs no 3Dconnexion driver
The 3D mouse only worked on Linux, through spacenavd. This adds a second
backend that reads the device directly over USB through hidapi, with no
3Dconnexion driver or SDK: the route to Windows and macOS (discussion
#599), and usable on Linux without spacenavd.

SpaceMouseHid decodes the raw reports from the device's own report
descriptor -- where each axis and button sits, its range, absolute or
relative -- so no per-model table is needed, with the classic report
1/2/3 layout as a fallback when the descriptor cannot be read and the
0x1c button list newer devices send. Absolute axes are rescaled to
+-500 exactly as spacenavd does, so both backends give QET the same
values. HidBackend polls from the main thread (fast while moving, slow
when still), emits one sample per poll, and looks for a device every 3 s
so plugging one in or back in needs no restart.

QET_SPACEMOUSE_BACKEND (auto, spnav, hid) picks the backend; auto keeps
libspnav on Linux when it is found and uses hidapi otherwise. hidapi is
found through pkg-config as hidapi-hidraw (Linux) or hidapi (MSYS2,
Homebrew).

A sample arriving in the same millisecond as the previous one now counts
for no time instead of a full period, so a burst of queued samples no
longer moves the view further than the time it covers.

Tested without a device: tst_spacemousehid (descriptor parsing, broken
and hostile descriptors, every report form, recordings from real devices
once they are added to fixtures/spacemouse), and end to end on Linux
through a virtual USB device created with /dev/uhid: the same moves give
byte-identical screenshots through the hidapi and libspnav backends, an
absolute axis is rescaled as spacenavd does, buttons trigger their
bound action, and unplugging and replugging while QET runs (including
with a dialog open that a device button opened) reconnects cleanly.
Not tested on Windows, macOS or real hardware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 10:55:30 +12:00
Andre Rummler bb037ee3ff Fix: when pasting the picture or graphics object is nowunder the top left corner instead far away. 2026-09-25 00:24:15 +02:00
ispyisail 17ebbffbca Add 3D mouse speed, direction, dead zone and twist-to-zoom settings
The 3D mouse's pan and zoom speeds were fixed guesses, and each sample
was applied as it came, so the speed on screen depended on how often
the driver sends samples -- different for every platform and device.

Motion now goes through SpaceMouseMotion::map(), which scales each
sample by the time since the previous one, and applies the user's
settings from a new "Mouvement" section of Configuration > Souris 3D:
pan and zoom speed, a dead zone, inverting each axis, and zooming by
push/pull (as before) or by twisting the cap. The defaults keep the
previous behaviour. Zoom is now exponential in the deflection, so the
factor stays positive however hard the cap is pulled (1 + z/1000 went
negative past z = -1000) and an equal push and pull cancel out. Sub-
pixel pan is carried over between samples instead of being rounded
away. The backend now reports all six axes.

tst_spacemousemotion covers the mapping without a device and is built
whether or not QET_ENABLE_SPACEMOUSE is on. The new behaviour was also
checked end to end with tools/spnav-shim (qelectrotech-docker): twist
with a dead zone of 10 ignores push/pull and small drift, and a twist of
60 gives the same frame as a push of 50 with the defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 09:56:04 +12:00
ispyisail 04e2279a7f Make the 3D mouse pan and zoom the element editor too
The 3D mouse only acted on the diagram editor: SpaceMouseListener
ignored every other window, so the element editor did not move at
all (reported by scorpio810 in PR #635 with a SpacePilot Pro).

applyMotion() now also handles a QETElementEditor, driving its
ElementView through the same scrollbar pan and a new
ElementView::zoom(factor), which keeps the wheel zoom's clamping.
The element editor's scene rect only covers what is on screen, so it
is grown before each pan sample, as its middle-button pan does;
without that the scrollbars have no range and the pan does nothing.

Verified under Xvfb with an LD_PRELOAD stand-in for libspnav feeding
recorded motion samples: element editor zooms 1.63x for ten z=50
samples and pans; diagram editor screenshots are byte-identical to
master's for the same input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 09:32:10 +12:00
Laurent Trinques 9987f05835 Merge pull request #1021 from ispyisail/feat/217-element-info-completer
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m2s
Fix bugtracker #217: suggest element information already used in the project
2026-09-24 23:19:48 +02:00
ispyisail a94f30b256 Fix bugtracker #217: suggest element information already used in the project
Each information field of the element properties now offers, as a
drop-down while typing, the values the other elements of the project
already carry for it: supplier, manufacturer, reference...

The values come from the project database's element_info table, not
from a walk over the folios. Spellings that differ only by case are
offered once, as most elements spell them, and the edited element's
own value is left out. The field key is checked against
elementInfoKeys() before it becomes part of the SQL text.

Browsing the list with live edit on applies each highlighted value,
as typing applies each keystroke; ChangeElementInformationCommand
merges them, so this still leaves one undo entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:28:41 +12:00
Laurent Trinques 73075a5154 Snap enable DQET_ENABLE_SPACEMOUSE=ON
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m31s
2026-09-24 17:20:38 +02:00
Laurent Trinques 9875de85d7 Merge pull request #635 from ispyisail/feature-3dmouse-spnav
3D mouse (SpaceMouse/SpacePilot) pan/zoom via libspnav — Linux phase (discussion #599)
2026-09-24 16:49:00 +02:00
Laurent Trinques 48883b320e Merge branch 'master' into feature-3dmouse-spnav 2026-09-24 15:45:33 +02:00
Andre Rummler d1256872c2 Fix transformation order. 2026-09-24 15:21:42 +02:00
Laurent Trinques b93d8d8baa Merge pull request #704 from arummler/master-feature-renum-elements-rebased
Autonumbering: renumbering of elements
2026-09-24 15:17:38 +02:00
Laurent Trinques ae41dd7686 Merge branch 'master' into master-feature-renum-elements-rebased 2026-09-24 15:17:17 +02:00
Kellermorph 800189debb Address review: restore hover entries for empty position text
- Call updateLabel() explicitly when the xref is created in itemChange for a master that must show its configured contact groups without slaves (same pattern as the PLC branch above).
- Register the hover/click hit rect of a contact independently of its position text again, as before this feature: only the drawing stays guarded by !str.isEmpty(), and the map insert is now keyed on elmt so free slots (nullptr) never enter the map.
- Revert is_power_ctc to the original element-type test (with a null guard): the Power-flag term was redundant for every caller that passes an element, so no linked contact changes classification.
- Clarify the label-order comment: single pole NO/NC are swapped, changeover labels are rotated per pole inside drawContact() (multi pole included), multi pole NO/NC groups keep the master order.
2026-09-24 15:11:31 +02:00
Andre Rummler 522e008c26 Fix rotation of inserted picture. 2026-09-24 13:37:06 +02:00
Laurent Trinques 7fbbb71aac Merge pull request #1018 from ispyisail/revive/785-info-flags-case-insensitive
Fix element flags written as True or 1 showing unticked
2026-09-24 13:08:28 +02:00
Laurent Trinques 232724bf82 Merge pull request #1017 from ispyisail/revive/752-rotate-texts-no-dialog-in-ctor
Move the rotate-texts dialog out of its undo command
2026-09-24 13:06:23 +02:00
Laurent Trinques 12f826e969 Merge pull request #1016 from ispyisail/revive/729-backup-prompt-drops-files
Fix projects opened during the start-up restore prompt being lost
2026-09-24 13:04:19 +02:00
Laurent Trinques 245aafb0d1 Merge pull request #1015 from ispyisail/revive/526-free-terminal-move
Fix the terminal strip's move button getting stuck disabled (#409)
2026-09-24 13:02:43 +02:00
Laurent Trinques 0d231ef548 Merge pull request #1014 from arummler/fix-explicit-file-open
Fix warning about reading XML without explicit file opening.
2026-09-24 13:00:47 +02:00
ispyisail 3513f692fd Read the remaining element flags through QET::infoFlagIsTrue()
5b0785fcc routed most reads of auto_num_locked, potential_isolating and
exclude_from_bom through QET::infoFlagIsTrue(), which accepts the same
spellings as the parts-list query (true/1/yes/on, trimmed, any case).
Four checkbox reads still compared against a literal lowercase "true":
the "exclude from parts list" box in the folio properties panel, and all
three flags in the element editor.

A value such as "True" or "1" from a hand-written .elmt/.qet was
therefore left out of the parts list, but shown unticked in both panels,
and pressing Apply there wrote back "false" and flipped the flag.

Revives the unconverted part of PR #785.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 22:34:42 +12:00
Laurent Trinques 5a27f3cf80 Merge pull request #1013 from ispyisail/fix/273-skip-open-backup
Fix bugtracker #273: skip crash-recovery backups of an unchanged project
2026-09-24 12:33:18 +02:00
Andre Rummler d3a9adbe44 Fix warning about reading XML without explicit file opening. This was implicit up to now and will go away in future Qt versions. 2026-09-24 12:32:47 +02:00
ispyisail 8ab4e24b61 Take the modal dialog out of RotateTextsCommand's constructor
RotateTextsCommand called QDialog::exec() from inside its constructor, so
the command could not be built without a human answering a dialog. That
made it untestable headlessly, undrivable from any script or test harness,
and it is why bugtracker #312 (PR #707) shipped with its save/reload
round-trip unverified -- the symptom could not be reproduced without a GUI.

The command now takes the angle as a parameter and does no asking. Two
statics carry the interactive half:

  hasSelectedTexts(diagram)  -- is there anything to rotate
  askRotation(rotation)      -- open the dialog, false if cancelled

The single call site in QETDiagramEditor asks first, then builds the
command, so the user-visible behaviour is unchanged: same dialog, same
title, same no-dialog-on-empty-selection. Keeping askRotation() in this
class also keeps the QObject tr() context, so existing translations of
"Orienter les textes sélectionnés" are not invalidated.

Also guards undo()/redo() against a null m_anim_group. When nothing is
selected the constructor calls setObsolete(true) without ever creating the
animation group, and QUndoStack::push() calls redo() before discarding an
obsolete command -- a latent null dereference on that path.

Verified headlessly, which was the point: driving the command through a
scratch --test-ops op on examples/741.qet (67 conductors), rotation
attributes written on save go 0 -> 67 with the #707 fix present and stay
at 0 with it reverted, while the reverted build instead writes userx on
all 67. That is bugtracker #312 reproduced and fixed under test for the
first time.
2026-09-24 22:30:20 +12:00
Laurent Trinques 2d2bf5ca2b Merge pull request #1012 from ispyisail/fix/340-ms-shell-dlg-pdf-font
Fix bugtracker #340: Windows texts in 'MS Shell Dlg 2' render as Arial
2026-09-24 12:27:50 +02:00
ispyisail 2f6ab8f63b Don't drop files handed to a starting instance while the backup prompts are up
QETApp's constructor ends with checkBackupFiles(), which opens modal
dialogs -- the "restore these files?" prompt, and the crash report offer.
Those dialogs run their own event loop, so the constructor does not return
until the user answers them.

main() still has work to do at that point. In particular this, a few lines
later:

    QObject::connect(&app, &SingleApplication::receivedMessage,
                     &qetapp, &QETApp::receiveMessage);

While the prompts are up that connection does not exist yet. A second
instance launched during the window -- double-clicking a project, or
xdg-open, while the first copy is still asking about restore files --
hands its file names over, SingleApplication accepts and delivers them,
and nothing is listening. The message is discarded and the second process
has already exited, so the file is simply lost with no error.

Deferring checkBackupFiles() to the event loop lets the constructor return
promptly. main() finishes wiring up, and the prompts appear immediately
afterwards exactly as before.

Verified with a stale restore file present, sending a project to the
running instance while the restore prompt is displayed: before, the file
was dropped and never appeared, even after answering the prompt; after, it
opens. The restore and backup prompts still appear and still work.

Note this is only observable together with the fix for bugtracker #248 --
before that, no file passed to a running instance was opened under any
circumstances.
2026-09-24 22:26:38 +12:00
ispyisail 2266497b0b Write the move button tooltips in French, the tr() source language
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 22:20:43 +12:00
Shane Ringrose dcb79b6553 fix(terminal-strip): free-terminal move button disabled and unresponsive
Three bugs stacked to produce the symptom in issue #409 (move button does
nothing):

1. selectionChanged() was declared in freeterminaleditor.h but had no body
   and was never connected to the selection model, so the move button had no
   awareness of whether a terminal was selected. The button could appear
   enabled with nothing selected, then silently return early in
   on_m_move_pb_clicked() at the real_t_vector.isEmpty() guard.

2. The dataChanged→setDisabledMove(true) connection was a one-way trap: any
   cell edit (including accidentally opening and closing a type/function
   combo, or toggling LED back to its current value) permanently disabled the
   move button until reload() was called. There was no tooltip explaining why
   the button was greyed out, so the user had no way to recover.

3. FreeTerminalModel::setData() for LED_CELL had no change guard, unlike
   LABEL_CELL which checks label != value. Clicking the LED combo while it
   was already at the same value still emitted dataChanged and triggered the
   disable.

Fix:
- Implement selectionChanged() to enable the move controls only when at
  least one row is selected AND there are no pending (yellow) edits.
- Connect it to both selectionModel::selectionChanged and model::dataChanged
  so the button state is always consistent with actual UI state.
- Route reload()'s re-enable through selectionChanged() instead of calling
  setDisabledMove(false) directly, so the selection state is respected
  immediately after a move.
- Add a tooltip to m_move_pb explaining the disabled state.
- Add the missing change guard to LED_CELL in setData().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-24 22:20:43 +12:00
ispyisail 745b4a936c Fix bugtracker #273: skip crash-recovery backups of an unchanged project
writeBackup() rebuilds the whole project's XML with toXml() on the GUI
thread before handing the file write to a worker thread. On a big project
that freezes the interface for seconds (bugtracker #273, and #329, where
the interval was raised from 2 to 20 minutes to make it rarer). It ran
right after every project was opened, and then every 20 minutes whether
or not anything had changed.

Only write a backup when something changed since the last one: the undo
stack moved, setModified(true) was called, or the embedded element or
title block collections changed. A project just opened from a file starts
clean, since the file is already what a crash would restore. New projects
and projects restored from a backup are backed up at once, as before.

Measured on examples/industrial.qet (50 folios), each backup blocks the
GUI thread for about 0.28 s on a fast machine; with a 2 s test interval,
the old code backed up on every tick, the new code only after a change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 21:57:42 +12:00
ispyisail 7ac3719c37 Fix bugtracker #340: map "MS Shell Dlg" aliases on Qt 6/Windows
"MS Shell Dlg 2" is a Windows font alias, not a font, and projects and
settings saved on Windows carry it. Qt 5's GDI backend let Windows resolve
it to Tahoma; Qt 6's DirectWrite backend does not know the alias and falls
back to Arial, so those texts render heavier on screen and in exported PDFs.

Register the substitutions Windows itself uses, before any application
object exists so the headless export and scripting paths get them too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 21:41:00 +12:00
Laurent Trinques ddfa234943 Merge pull request #1011 from Kellermorph/save-sheet-colour
Remember the sheet background colour between runs
2026-09-24 10:50:34 +02:00
Kellermorph 9af562fcbd Remember the sheet background colour between runs
Picking a sheet (folio) background colour in the diagram editor was lost
on every restart. Diagram::background_color is a static initialised to
white and PaletteGraphicsView's s_custom_bg a static bool, and neither
was ever written anywhere -- Diagram::toXml() carries no colour attribute
either -- so closing and reopening a project always came back on the
default and the choice had to be made again.

Store it in QSettings under diagrameditor/sheet_background_* as a pair of
values rather than one: the colour, and whether it was picked explicitly.
Both halves are needed. "#ffffff, follow the system" and "#ffffff, always
white" are the same colour and two behaviours -- the first is what the
views invert on a dark palette -- so keeping only the colour would
silently turn one into the other on the next start, which is the reported
problem one step removed.

The colour is written as HexRgb on purpose. The SVG export gives
Diagram::background_color an alpha of 0 to render a transparent
background and never puts it back, and that transient value must not be
persisted as a permanently transparent sheet.

Applied from main() after the headless export and scripting branch -- those
return before reaching it and must keep rendering on plain white, the rule
ProjectPrintWindow already enforces for printing -- and before QETApp is
constructed, since that constructor already loads the projects given on
the command line. The GUI export dialog is left alone: it renders through
drawBackground(), so what you see is what you export, as it already was
within a session.

Saved at the moment the colour is applied rather than at shutdown, so
neither the print window's temporary white nor the SVG export's alpha can
reach it. The button's constructor now mirrors the stored state instead of
always claiming "system colour", and the "recently used" list is stored
alongside it.

Covered by tst_sheetbackgroundsetting, which pins the custom flag and the
dropped alpha -- the two rules a single stored colour would lose.
2026-09-24 10:30:01 +02:00
Laurent Trinques c85276f61b Merge pull request #1009 from ispyisail/fix/1002-resize-handle-artefacts
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 6m31s
Fix green fragments left behind when moving or zooming an element (#1002)
2026-09-24 10:17:53 +02:00
Kellermorph c1cc9a5b98 Show all master-defined slaves in the contact comb behind a new option
Add a new cross reference setting per xref type (coil, protection,
commutator, PLC), labelled "Afficher tous les esclaves definis par le
maitre" and persisted as showallconfiguredslaves. When it is enabled,
the contacts display is selected and the master declares contact
groups, the contact comb draws every contact group of the master in the
master's own order, even when no slave is linked to it yet. Masters
without declared contact groups and the option turned off keep the
previous behaviour exactly: linked slaves only, sorted by position.

- XRefProperties: new property stored in the settings and in the
  project XML (attribute showallconfiguredslaves, absent means false so
  old files are unaffected), included in operator==.
- XRefPropertiesWidget: new checkbox placed after the terminal names
  one, enabled only while "Afficher en contacts" is selected; its
  enabled state is now also set explicitly when a type is loaded (a
  radio button that does not change emits no toggled()).
- For the PLC type the contacts/cross radios, the two display
  checkboxes and the cross options group are hidden: a PLC master is
  always drawn as its IO table, those settings have no effect there.
  Positioning and label settings, which the table really uses, stay.
- CrossRefItem: free slots draw the symbol of the group plus the
  terminal names the master defines (pairs swapped for a single pole
  NO/NC contact, labels of a changeover contact rotated one step
  counter-clockwise), without position text and without hover/click.
  Linked slaves keep drawing from their own data at their assigned
  group position; links without a group are appended at the end in
  position order.
- Xref lifecycle: the item is created and kept without linked slaves
  for snap-to-bottom (MasterElement::mustShowXrefWithoutSlave) and for
  snap-to-label (DynamicElementTextItem::updateXref and
  ElementTextItemGroup::updateXref, which now also run when the element
  lands on the scene and re-establish their project connection), so a
  freshly placed master shows its comb immediately instead of only
  after the next settings change. updateLabel() resets its geometry
  when the option is turned off again, so no stale ghost stays.
2026-09-24 09:59:21 +02:00