Commit Graph

838 Commits

Author SHA1 Message Date
Kellermorph dbe2bcb599 PLC terminal names: fix transfer to slaves and display in properties widgets 2026-08-21 12:10:50 +02:00
ispyisail a48124a27a Stop reporting wires as excluded that the list is now showing
excludedConductorCount() counted conductors whose terminals had no uuid,
which was the right rule when that was the reason they were dropped. It no
longer is: Terminal::stableUuid() derives an identity from the terminal's
geometry, so those conductors are in the table.

Left unchanged, the dialog would have told the user "671 conductors excluded"
on industrial.qet while listing all 671 of them -- a worse failure than the
one the count exists to prevent, because it undermines a list that is now
correct.

The count and the dialog's explanation both now describe the case that
actually remains: an endpoint attached to no element at all, which has no
identity to key on under any scheme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 21:10:28 +12:00
ispyisail e19d60ae55 Report the real wire count, and show current data
The summary line exists so that an empty wiring list is distinguishable
from one where every conductor was excluded, and it was reporting the wrong
number to do it. QSqlQueryModel fetches lazily, so rowCount() straight after
setQuery() returns the rows fetched so far -- 256 -- not the size of the
query. Measured with Qt's own QSQLITE driver: a 1000-row view reports 256
until the model is drained, then 1000. The test project quoted in slice 2
has 280 conductors, so this was already displaying 256 on our own data,
plausibly enough that nobody looked twice.

Drain the model before reading the count.

Also refresh the database before building the model. The dialog queries the
database rather than the diagrams, so anything not yet written through was
invisible here; with conductor text now updated on change that gap is
smaller, but a project loaded before this dialog was ever opened still
relies on the repopulate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 21:10:28 +12:00
ispyisail 5b8d05fc1e Add a wiring list dialog and an excluded-conductor count
Slice 4 of discussion #503, on top of slice 3 (#629): the smallest
surface that makes wiring_list_view visible, plus the diagnostic the
view needs to be honest about what it is missing.

Projet > "Liste de câblage (base de données)" opens a read-only table of
wiring_list_view, headed by a line stating how many conductors are
listed and, when non-zero, how many were excluded and why.

Deliberately not another exporter. QET already ships a wiring-list CSV
export (Projet > Exporter le plan de câblage, and --export-cables) which
walks the project XML; measured on the same projects it produces a row
per conductor and resolves labels correctly when the project has them.
Adding a second, competing CSV would be worse, not better -- the
database path's value is what it unlocks (terminal plans, BOM joins),
not replacing that export.

projectDataBase::excludedConductorCount() counts, from the live scene,
the conductors deliberately absent from the conductor table because a
terminal has no uuid. Counted from the scene precisely because the
database is where those conductors are not. Verified: 671 on
examples/industrial.qet (which has 1794 terminals and no terminal uuids
at all, so its list is empty and now says so), 0 on a project whose
elements do carry terminal uuids.

KNOWN GAP, not fixed here and the reason this is opened for discussion
rather than merge: after a save/reload the component columns are blank
for slave elements. populateElementTable()/populateElementInfoTable()
only insert Simple|Terminal|Master|Thumbnail, so slave elements -- relay
contacts, i.e. a large share of real wire endpoints -- have no row in
element_info for the view to read a label from. Measured on a two-slave-
contact project after reload: element rows 0, element_info rows 0,
terminal rows 2, conductor rows 1; the wire is listed (slice 3's LEFT
JOIN keeps it) but both component names are empty, where the existing
CSV export shows K1 -> K2 for the same file.

Closing that gap means widening a filter shared with the nomenclature
and summary views, which would change what those existing, shipped
features contain. That is a maintainer decision, not one to take
unilaterally inside an additive slice.
2026-08-21 21:10:27 +12:00
Laurent Trinques f172e0740b Merge pull request #760 from ispyisail/s6-shortcut-browsing
Shortcuts page: group by category and make search actually work
2026-08-21 08:38:46 +02:00
Laurent Trinques 5263d78dce Merge pull request #741 from Kellermorph/update-terminal-numbering
Extend terminal numbering dialog with letter numbering and strip selection
2026-08-21 08:37:48 +02:00
ispyisail e9ee8b600b Make the shortcut list browsable and searchable
Replace the flat QTableWidget with a QTreeWidget that groups actions under
one collapsible top-level node per category. Fix the search box so it also
matches the current key sequence (exactly), accepts multi-keyword queries
(AND, any word order) and is accent-insensitive, auto-expands matching
groups and shows an "N actions" count. Add a quick filter (all / bound /
unbound / conflicts) that combines with the text query. Conflict detection,
per-row reset, reset-all and persistence are preserved.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-17 12:30:23 +12:00
Kellermorph 5acf201067 fix 2026-08-14 09:43:39 +02:00
Laurent Trinques f83ed508e3 Merge pull request #705 from arummler/master-update-more-signal-slot
Continued signal/slot migration
2026-08-13 21:40:30 +02:00
plc-user 293dc92b41 Merge pull request #738 from ispyisail/fix/exclude-from-bom-ghost-row
Fix nameless "false" row in the element Informations panel
2026-08-13 17:56:40 +02:00
Andre Rummler 6d05bc2f21 Remove all Qt version checks and branches for <5.15.12 as such versions are no longer supported. 2026-08-13 16:20:30 +02:00
Andre Rummler 7ba295a339 Remove all Qt version checks and branches for <5.14.0 as such versions are no longer supported. 2026-08-13 15:45:15 +02:00
Kellermorph 458c9921c2 Extend terminal numbering dialog with letter numbering and strip selection 2026-08-13 13:51:13 +02:00
Andre Rummler deba8d0e4e Merge branch 'master' into master-update-more-signal-slot 2026-08-13 13:07:53 +02:00
ispyisail 61a509e5d8 Fix nameless "false" row in the element Informations panel
"exclude_from_bom" is listed in QETInformation::elementInfoKeys() so the
project database can build the element_info table column for it, but it
is not a free-text property: ElementInfoWidget already gives it its own
"Exclure de la nomenclature" check box.

Because buildInterface() creates one ElementInfoPartWidget per key in
that list, the key also got a second, generic edit row. And since
translatedInfoKey() has no case for it and falls through to
"return QString()", that row carries no label at all - an anonymous edit
line at the bottom of the panel. currentInfo() then writes
exclude_from_bom unconditionally, so as soon as the user edits anything
the nameless row fills with "true"/"false".

Drop the key from the list buildInterface() iterates. The check box
remains the only way to set it, currentInfo() still writes it exactly as
before, elementInfoKeys() is untouched so the database schema and
elementquerywidget are unaffected, and predefinedKeys() already excluded
it from the custom-property rows.

Reported by plc-user on #642.
2026-08-13 21:24:50 +12:00
Andre Rummler 8d08c3fd56 Replacing depreciated qAsConst with std::as_const 2026-08-12 12:29:56 +02:00
Levi Jetzer 5dbb9f28de Fix numeric field validation gaps found in review
Address feedback on the width/height/depth elementInformation fields:

- The regex accepted "." alone as a complete value ([0-9]* permits
  zero digits on both sides), meaning a field could be committed with
  a literal "." saved to the XML. Require at least one digit either
  before or after the separator.
- The same pattern was duplicated verbatim in elementinfopartwidget.cpp
  and elementpropertieseditorwidget.cpp's EditorDelegate. Factored into
  QETInformation::numericInfoPattern(), a single shared definition both
  call sites now use.
- Tightened the pattern to at most 2 decimal places (down from 4) to
  match the precision actually meaningful for these fields.

Not changed, by design:
- Storage stays a plain string, consistent with every other numeric
  elementInformation field (quantity etc.) in this codebase -- values
  round-trip through XML text regardless, so a long/micron
  representation wouldn't avoid the string<->number conversion, only
  relocate it.
- No decimal-comma normalization needed: with the fixed pattern, only
  digits and "." are ever accepted at the keystroke level, so an
  alternate separator can't enter the field in the first place.
2026-08-12 08:43:42 +02:00
Levi Jetzer c6e7c5d371 Fixed issue of taking wrong variable (key instead of key_) in elementinfopartwidget.cpp for regex checker and helping info (tooltip, placeholder) 2026-08-11 18:28:29 +02:00
Levi Jetzer 7347322221 Replace QDoubleValidator with QRegularExpressionValidator on width/height/depth fields
The selection properties dock (elementinfopartwidget.cpp) and the
element editor's information tree (elementpropertieseditorwidget.cpp's
EditorDelegate) both restrict the width/height/depth fields added for
cabinet layout support to numeric input via a validator.

QDoubleValidator follows the system/UI locale for its decimal
separator, which meant a comma was accepted as an intermediate state
and left the field impossible to leave in some contexts, even though
it was never a valid final value.

Switch both to a QRegularExpressionValidator matching
^[0-9]*\.?[0-9]{0,4}$: "." is a literal character in the pattern, not
locale-dependent, and [0-9] (rather than \d) excludes non-ASCII
digits. This guarantees a value entered this way can always be read
back with QString::toDouble() without locale handling. The check for
which keys are numeric (QETInformation::isNumericInfoKey) is shared
between both call sites; the validator setup itself stays local to
each, since QETInformation intentionally has no Qt Widgets dependency.

Also adds a placeholder ("ex. 80.5") and tooltip explaining the
expected format.
2026-08-11 18:01:10 +02:00
plc-user 550e085b82 Merge pull request #693 from ispyisail/fix/color-editor-crash-bug323
Fix crash changing dynamic text color and confirming with Enter (bugtracker #323)

Works like charm: 
- color is updated immediately
- no additional errors or warnings 
- no crash anymore!
2026-08-10 21:24:04 +02:00
ispyisail b0b5345e15 Commit font/color edits immediately instead of waiting for an unrelated click
plc-user on PR #693: the crash is fixed, but the color/font field and
the on-diagram text no longer update until you leave the properties
list and click in the diagram -- previously it updated as soon as you
clicked OK.

That's a side effect of the crash fix itself. The old, crashing code
returned a *live* QColorDialog as the item view's editor; clicking its
OK button called accept()/hide() on it, and hiding the active editor
happens to trip the base delegate's own focus-lost commit path -- so
the value applied immediately, racily, as a side effect of the same
mechanism that crashed on Enter. The fix (commit 4bd9b6b21) replaced
that with running the dialog synchronously inside createEditor() and
returning an inert placeholder with the result stashed in a property.
Correct for the crash, but it also removed that accidental commit
trigger: the placeholder never had focus to lose, so nothing tells
the view to read the value back until some unrelated interaction
(clicking away) incidentally triggers it.

Fix: explicitly emit commitData()/closeEditor() for the resolved
editor, deferred via QTimer::singleShot(0, ...) since the view only
registers createEditor()'s return value as "the active editor" after
createEditor() itself returns -- emitting synchronously, before
returning, would target a widget the view doesn't know about yet.
Applied to both font and color, since both share the exact same
"resolve synchronously in createEditor(), return an inert
placeholder" shape and thus the exact same gap; font just hadn't been
reported.

Verified with the same standalone harness from the crash fix (real
QTreeView + DynamicTextItemDelegate + QAbstractItemView::edit()),
this time deliberately *not* sending the synthetic Enter keypress the
crash-fix verification needed: clicks the dialog's real OK button,
lets the event loop run, and confirms the picked color lands in the
model on its own. Also reconfirmed the crash fix itself still holds
(clean exit, no synthetic-Enter needed either way now) and did a full
Release build (504/504) with no new warnings.
2026-08-10 20:30:05 +12:00
Andre Rummler c25c400c93 Some of the newly guarded (Qt5 path) signal/slot connects could be still converted to method pointers. Mostly for completeness as they will go away anyhow soon.
Remaining: richtext editor.
2026-08-10 09:08:57 +02:00
Laurent Trinques cdf5cad1e5 Merge pull request #698 from arummler/master-modernize-signal-slot
Migrate string based signal/slot to method pointer
2026-08-09 21:08:28 +02:00
plc-user 3a3f23b7a8 swap position of ComboBox and static text in config-page 2026-08-09 20:55:01 +02:00
plc-user 1f7107a7b6 Merge pull request #703 from Kellermorph/fix-plc-warnings
Compile-warnings have been fixed as requested.
2026-08-09 20:36:56 +02:00
Kellermorph 5e02111600 Fix-PLC-Warnings 2026-08-09 18:27:20 +02:00
Andre Rummler c7ed3229d0 Migrating more SLOT() macros. 2026-08-09 12:30:15 +02:00
Andre Rummler 9f283322a2 Merge branch 'master' into master-modernize-signal-slot 2026-08-09 11:30:27 +02:00
Andre Rummler 201bd4c5f6 Migration of signal/slot to method pointer continued. Mostly simple cases. 2026-08-09 01:34:22 +02:00
Andre Rummler 5adf61936b Merge branch 'master' into master-modernize-signal-slot 2026-08-08 23:13:42 +02:00
Andre Rummler 293e61abeb Modernizes the signal/slot connect and solves the disambiguities of the remaining currentIndexChanged(int) connects via qOverload<int>, since
QComboBox::currentIndexChanged(QString) still exists pre-Qt6.

TitleBlockTemplateLocationChooser: collections_ -> updateTemplates() (a virtual method; pointer-to-member dispatch still resolves to the
TitleBlockTemplateLocationSaver override at runtime as expected)
TitleBlockTemplateLocationSaver: templates_ -> updateNewName()
TitleBlockPropertiesWidget: m_tbt_cb -> changeCurrentTitleBlockTemplate(int)
XRefPropertiesWidget: m_type_cb -> typeChanged(), m_snap_to_cb ->enableOffsetSB(int), both connect (constructor) and disconnect(destructor)
2026-08-08 18:37:53 +02:00
Andre Rummler bfee5b1cdb Merge branch 'master' into master-fix-slot 2026-08-08 15:16:48 +02:00
Laurent Trinques 3eafe840f1 Merge pull request #656 from ispyisail/feature-last-used-style
Remember last-used shape/text style for new items this session
2026-08-08 14:22:52 +02:00
ispyisail 5ba08284f5 Fix "use current date" preset being lost unless the Folio tab is active on save
Bugtracker #308: the "current date" preset for a project's default
title block doesn't persist. A later comment on the report pinpointed
it exactly: the setting falls back to "No date" unless the folio tab
remains active when saving settings, and the same happens in Project
Properties.

TitleBlockPropertiesWidget::properties() (and its near-duplicate
sibling propertiesAutoNum(), copy-pasted with the same bug) reads the
date radio buttons like this:

    else if (ui->m_current_date_rb->isVisible() && ui->m_current_date_rb->isChecked()) {
        prop.useDate = TitleBlockProperties::CurrentDate;
        ...

Both the New Project settings page and Project Properties embed this
widget as one page of a QTabWidget (NewDiagramPage, in
configpage/configpages.cpp). QWidget::isVisible() depends on the
whole ancestor chain being visible, not just the widget's own state --
switch to any other tab before clicking OK/Apply and this radio
button's isVisible() goes false even though it's still checked
underneath, silently falling through all three branches. The function
returns a default-constructed TitleBlockProperties for the date
fields (useDate = UseDateValue, date = QDate(), i.e. "no date"),
matching exactly what was reported.

Fix: use isHidden() instead, which reflects only this widget's own
explicit state and mirrors the read side's own check in
setProperties()/initDialog() just above it in the same file -- that
side already uses isHidden(), not isVisible(), for the identical
"is the current-date option even offered here" question.

Verified directly: a standalone Qt program constructing the real
NewDiagramPage, checking "current date", switching the tab widget
away from Folio to Conducteur (reproducing the report's exact
trigger), then calling applyConf() and reading back the QSettings
value. Against the original code this saves date="null"; with the
fix, date="now" -- the same scenario, same tab switch, only the one
line differs. Also confirmed a full Release build (504/504, CMake/
Ninja, Qt 5.15.18) with no new warnings.
2026-08-08 23:02:07 +12:00
ispyisail 4bd9b6b212 Fix crash changing a dynamic text's color and confirming with Enter
Bugtracker #323: crash changing a label's color, but only when
confirmed via Enter -- clicking the dialog's own OK button with the
mouse doesn't crash. Reported on Windows 11 and Debian, with
"QObject::installEventFilter(): Cannot filter events for objects in
a different thread" immediately before the segfault.

Root cause: DynamicTextItemDelegate::createEditor()'s color case
constructed a QColorDialog and returned it directly as the item
view's editor widget for the color cell -- unlike every other case
in this same function, which returns a small inline widget
(QSpinBox, QComboBox, or, for the adjacent font case, a plain
placeholder). A QColorDialog is not designed to be used this way: it
is not one of the objectNames this delegate's own eventFilter()
special-cases, so Enter is handled by the base
QStyledItemDelegate::eventFilter() as an ordinary "commit and
destroy this small editor" trigger -- racing the dialog's own
internal OK-button accept/close path, which on Windows can hand off
to the native color picker. Clicking OK with the mouse doesn't go
through the same key-press path, which is why only Enter crashed.

Verified structurally: an embedded QColorDialog editor is a *child*
widget of the view's viewport rather than a proper top-level dialog
(confirmed with a standalone Qt program driving the real delegate
through QAbstractItemView::edit() -- searching QApplication's
top-level widgets never found it, only a search of the viewport's
children did), which is the same "used as something it isn't"
pattern, just observed a different way.

Fix: mirror the font case immediately above -- resolve the color via
the static, blocking QColorDialog::getColor() inside createEditor(),
and hand back a plain QWidget with the result stashed in two
properties (mirroring the font case's "ok" property) for
setModelData() to read. By the time the view processes any commit
trigger, the "editor" is an inert placeholder with no dialog state
left to race.

Verified end-to-end with the same standalone program: creates the
model item, triggers editing, finds the real (top-level, this time)
QColorDialog, clicks its actual OK button, confirms the color lands
on the placeholder's properties, sends the editor a synthetic Enter
keypress (the exact trigger from the bug report), and confirms the
final committed value in the model matches the picked color. Also
confirmed a full Release build (333/333) with no new warnings.
2026-08-08 21:06:05 +12:00
Andre Rummler 62ad49a6d3 Update old fashioned SIGNAL/SLOT to point-to-member. Only simple and clear cases. 2026-08-08 00:32:31 +02:00
plc-user b6e43f78d0 fix whitespace 2026-08-07 22:09:06 +02:00
Laurent Trinques 55fdae918b Merge pull request #642 from ispyisail/feature-custom-element-properties
Add user-defined custom properties on elements (discussion #611)
2026-08-07 12:12:31 +02:00
Levi Jetzer 97709bff6a Save the default cross-reference properties to QSettings
NewDiagramPage::applyConf() writes every other default to QSettings —
border, title block, conductors, folio reports and the guides — but the
cross-reference branch only fetched the properties into a local hash and
then dropped it on the floor. hash_xrp was never used.

The result: changing the cross-reference defaults under Settings > New
project has no effect. Nothing is written, no defaultxref* key ever
appears in the configuration file, and XRefProperties::defaultProperties()
keeps handing out the hardcoded fallbacks for every new project.

Write each of the four types (coil, protection, commutator, plc) with the
"diagrameditor/defaultxref" + key prefix that defaultProperties() already
reads back.
2026-08-07 11:42:20 +02:00
Kellermorph 4f9474a05f PLC Fix scroll sync, data persistence, font defaults, copy/paste, and layout fixes 2026-08-06 07:33:39 +02:00
Andre Rummler fb51489436 Fix of previous commit: use textActivated signal instead. 2026-08-05 09:39:15 +02:00
Andre Rummler 1216855cfa Fix broken auto-numbering conductor/element/folio selection in project properties
connect() used the string-based currentIndexChanged(QString) signal, which was removed from QComboBox in Qt6. Selecting a conductor/element/folio auto-numbering context in the
combo box in the project properties dialog never updated the other fields.

Switch to currentTextChanged with the modern pointer-to-member connect() syntax, which also catches signal/slot mismatches at compile time. This is still backward compatible with Qt5 (as long as this is needed).
2026-08-04 23:10:18 +02:00
ispyisail 29ba7c9636 Remember last-used shape/text style for new items this session
Drawing tools on the diagram canvas always started new shapes and free
text from a fixed hardcoded default (Qt's plain QPen()/QBrush(), and
the static Preferences font) -- changing a shape's color or a text's
font had zero effect on what the next new item of that type got, even
within the same editing session.

Add LastUsedStyle: a small in-memory, session-scoped static helper
(no QSettings, no persistence across restarts -- this is a live "what
did I just use" value, not a new app-wide default). Write side hooks
capture the value right where the properties editors already apply a
change (ShapeGraphicsItemPropertiesWidget::associatedUndo(), both the
live-edit dock path and the modal editProperty() dialog path; and
IndiTextPropertiesWidget::on_m_font_pb_clicked() right after the font
dialog returns). Read side hooks apply the stored value, if any, to a
newly created item: DiagramEventAddShape::mousePressEvent for shapes,
IndependentTextItem's constructor for free text (falling back to the
existing QETApp::indiTextsItemFont() Preferences default otherwise).

Doesn't touch the element/symbol editor's own drawing tools (a
separate subsystem) or add last-used text color (no color control
exists in the text properties UI to originate it from yet).
2026-08-04 10:32:25 +12:00
ispyisail 6b9cb0a220 Add user-defined custom properties on elements
ElementInfoWidget's fixed ~40 predefined ELMT_* keys had no way for a
user to add a genuinely new element-info key, even though DiagramContext
already stores/round-trips arbitrary keys generically via toXml()/fromXml().

Adds an "Ajouter une propriété personnalisée" button that appends a
CustomElementInfoPartWidget row (both key and value user-editable,
unlike the fixed ElementInfoPartWidget rows bound to one predefined
key). The typed key is validated live against the existing
DiagramContext::isKeyAcceptable() and flagged with a red border when
it doesn't match, instead of silently dropping it. Any key already
present on the element that isn't one of the predefined/special keys
is re-displayed as a custom row on next selection.

Implements the scope proposed in discussion #611.
2026-08-03 10:28:26 +12:00
ispyisail 83b9f32bd0 Add device-button-to-action bindings, for any 3D mouse backend
Discussion #599's own scope explicitly deferred this ("Related, not
proposed here... a natural follow-up once basic pan/zoom motion works").
Basic pan/zoom now works (previous commits on this branch), so this adds
it -- generically, for whichever SpaceMouseBackend is in use, not tied to
libspnav specifically, matching the seam the previous commit built.

## Reuses ShortcutManager instead of inventing a second action registry

ShortcutManager is already an app-wide registry of every named, rebindable
action -- undo, redo, rotate selection, cut/copy/paste, autonum configure,
and dozens more -- each carried by a live QAction or QAbstractButton. A
device button binding to one of *those* ids, rather than to a bespoke
QET-3D-mouse-only action list, means the discussion's own examples
(rotate/mirror/undo) are available for free, and any action added to the
app in the future is automatically bindable too.

Added ShortcutManager::trigger(id): find the first still-alive target for
an id and call QAction::trigger() or QAbstractButton::click(), whichever
it is. Deliberately not disambiguated by which window is currently active,
unlike SpaceMouseListener's own pan/zoom dispatch -- a target's owning
top-level window isn't reliably discoverable from a bare QAction. Correct
in the overwhelming common case of one open editor window; documented in
the header as a known simplification, not silently assumed correct.

## The binding itself: SpaceMouseButtonMap

A thin QSettings-backed button-number -> action-id map, unbound by default
for every button on every device -- nothing happens on any button press
until the user opens Configuration > Souris 3D and binds something,
matching this whole feature's "silent until asked for" default.

## Backend side: SpaceMouseBackend::buttonPressed(int)

Added to the platform interface alongside the existing motion() signal.
SpnavBackend now handles SPNAV_EVENT_BUTTON (previously explicitly
ignored) and emits on press only -- release is not reported, since nothing
downstream has a use for it. A future non-spnav backend implements the
same signal and gets button support for free through
SpaceMouseListener::applyButton(), without that logic being duplicated or
re-verified per backend -- the same reasoning the previous commit's seam
was built around.

## Configuration UI: SpaceMouseConfigPage

Modelled directly on the existing ShortcutsConfigPage -- same QTableWidget
shape, same "persist on applyConf(), not live" contract -- one row per
binding: button number (spin box, unbounded, since button count and
numbering genuinely vary from 2 to 30+ across real devices and this could
not be checked against hardware) and action (combo box populated from
ShortcutManager::instance().allShortcuts(), the exact same live registry
the Shortcuts page itself lists). Only added to the Configuration dialog
when QET_SPACEMOUSE_SUPPORT is compiled in.

## Verified, including the one thing that doesn't need hardware to prove

Rebuilt from scratch both ways: option off adds zero new object code
(confirmed via a forced rebuild of the one unconditionally-changed file,
shortcutmanager.cpp, which alone picked up new warning-free code); option
on compiles all four new/changed files warning-free and links clean.

The backend's button *detection* (SPNAV_EVENT_BUTTON -> buttonPressed
signal) still cannot be verified without a real device or daemon -- same
limitation as the motion path from the previous commits, stated plainly
rather than glossed over.

What *is* fully verified, because none of it needs hardware:
 - SpaceMouseButtonMap: unbound by default, set/read-back, clearing via an
   empty id, enumeration -- all confirmed via a standalone harness linked
   against the real compiled objects.
 - ShortcutManager::trigger(): registered a real QAction, confirmed
   trigger() fires it exactly once and returns true; confirmed it returns
   false (not a crash) for an unknown id.
 - SpnavBackend: constructs safely with no daemon present (isAvailable()
   false, as it must be), and both its motion and buttonPressed signals
   are correctly wired per Qt's own metaobject data (QSignalSpy).
 - The configuration page end-to-end, via a real Xvfb session: opened
   Configuration > Souris 3D, confirmed the action combo box lists the
   live, real ShortcutManager registry (undo, rotate, cut/copy/paste,
   dozens more -- not a mock), added rows, edited the button number,
   removed rows, selected "Éditeur de schémas — Pivoter" (Rotate -- the
   discussion's own example) for button 3, clicked OK, and confirmed via
   the actual settings file that it persisted exactly as
   "buttons\3=diagrameditor.rotate_selection". Reopened the dialog and
   confirmed it read back correctly. This is a full, real round trip
   through the UI, not a claim.
2026-08-02 22:11:30 +12:00
Laurent Trinques 2d99ba67f1 Merge pull request #588 from Kellermorph/Fix-PLC-Manager
Fix-PLC-Manager
2026-08-01 15:30:10 +02:00
Kellermorph e91bab14fe Update 2026-08-01 14:26:39 +02:00
Laurent Trinques a4d75c4d8c Merge pull request #586 from ispyisail/feature-jump-to-element
Add Ctrl+G "jump to element" quick-open popup
2026-08-01 10:58:33 +02:00
Kellermorph 4c1f82f814 Fix-PLC-Manager 2026-07-31 21:02:07 +02:00
ispyisail 5275fb44fe Add configurable shortcuts: ShortcutManager registry + Shortcuts config page (#574)
Implements the first pillar of #574: a "Shortcuts" preferences page letting
users rebind, search and reset every keyboard shortcut in the app.

What it does
- New ShortcutManager singleton: every one of the ~95 setShortcut()/
  setShortcuts() call sites across qet.cpp, qetmainwindow.cpp,
  elementspanelwidget.cpp, autonumberingdockwidget.cpp, richtexteditor.cpp,
  qetdiagrameditor.cpp, qettemplateeditor.cpp and qetelementeditor.cpp now
  calls registerAction(target, id, category, default_sequence) instead,
  which applies the user's saved override (or the default) and remembers
  the target for later editing.
- New ShortcutsConfigPage, added to the existing "Configurer QElectroTech"
  dialog: a filterable table of every registered shortcut, grouped by
  category, each with a QKeySequenceEdit and a per-row reset button, plus a
  "reset all" button. Bindings are only persisted (via
  ShortcutManager::setSequence()) when the dialog is accepted.
- Conflict detection: rows whose currently-edited sequence collides with
  another row are highlighted with a tooltip naming the conflicting action.
- Overrides are stored under a "shortcuts/" QSettings group, one key per
  id, keyed to match the id (not persisted at all when equal to the
  hardcoded default), so a future QET version can safely raise a default
  for anyone who never customized it.

Design notes
- Targets are handled generically via QObject rather than QAction, since one
  call site (autonumberingdockwidget's "Configurer" button) is a
  QPushButton, not a QAction. Both declare an identical "shortcut"
  QKeySequence Q_PROPERTY, so registerAction() reads/writes it through the
  property system instead of needing a separate code path.
- Several live targets can share one id at once -- QET allows multiple
  windows of the same kind (diagram editor, element editor...) open
  simultaneously, each constructing its own QAction with the same id.
  setSequence() updates every live target for that id in one call, so a
  rebind takes effect in all open windows immediately, without restart.
- A shortcut's description is captured from its target's text() the first
  time that id is registered, then cached -- so the config page stays
  correct even after the owning window is closed. One consequence: a
  shortcut belonging to an on-demand window (element editor, title block
  editor, rich text editor) only appears in the list once that window has
  been opened at least once in the current session, since nothing has
  registered its id yet otherwise.

Testing
Full CMake build (qmake CONFIG+=no_kf5, Qt 5.15) compiles clean with zero
errors and zero new warnings. Verified end-to-end in a real running session
(Xvfb + xdotool):
- The Shortcuts page appears in Configure QElectroTech with the right icon,
  lists every always-registered shortcut with correct category/action name/
  current binding.
- The filter box correctly narrows the list, and correctly returns nothing
  for an action whose owning window hasn't been constructed yet this
  session (confirming the on-demand-registration behavior above is working
  as designed, not silently broken).
- Conflict detection correctly flagged a real pre-existing same-key overlap
  between "Supprimer" (delete selection, Del) and "Supprimer ce folio"
  (delete diagram from panel, Del) -- both highlighted with explanatory
  tooltips.
- Rebound "Manuel en ligne" to Ctrl+Shift+M, clicked OK: persisted under
  [shortcuts] in QElectroTech.conf, and the Aide menu's entry showed the new
  binding immediately, no restart needed.
- Reopened the dialog: the rebind was still shown. Clicked its per-row
  reset button, then OK: the settings key was removed entirely (not stored
  as "F1"), correctly falling back to the hardcoded default.

Retrofitting the Tab/Shift+Tab, select-all (#585) and Ctrl+G jump-to-element
(#586) shortcuts through this registry is left for a follow-up once those
PRs land, to avoid re-merging still-open branches into this one.

Developed with assistance from Claude (Anthropic).
2026-08-01 01:35:51 +12:00