Drop 1:2.5: on a grid of 10 it steps by 4, which misses 10, so texts on
two elements 10 apart could never line up. Every remaining divisor is a
whole number, which tst_textgrid checks.
The divisor list and the snapping arithmetic move to the header-only
textgrid.h so the toolbar menu, the preferences page and the test share
them. The preferences page gets the same choice under Grille + Clavier;
QETApp::textGridChanged keeps every editor's toolbar button in step.
While element texts are dragged, the status bar names the text grid and
says to release Shift and hold Ctrl for free placement -- Ctrl+Shift
together is the pan shortcut.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
Texts snapped to the folio grid, so the first drag of an off-grid label
pulled it sideways by up to half a grid step (discussion #1020). Texts
now snap to a fraction of the folio grid, chosen from a "Textes 1:N"
button in the View toolbar and a menu under Affichage: Off, 1:1, 1:2,
1:2.5, 1:5, 1:10. Because the step divides the folio grid, texts on
different elements still line up. Ctrl still places a text freely.
1:1 is the default and matches the previous behaviour. The setting is
stored in QSettings; nothing changes in saved projects.
All five text movers switch together: element texts, text groups,
texts moved with a selection, conductor texts and independent texts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
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>
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>
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
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.
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
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
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>
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>
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>
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>
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>
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>
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>
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>
- 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.
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>