Commit Graph

812 Commits

Author SHA1 Message Date
ispyisail 79a8637251 Add optional hops where wires cross (#436)
Where two wires cross without being connected, a project can now draw a
small arc (a hop) on one of them, instead of the user inserting a jump
symbol and splitting the wire. It is a project setting, off by default:
Project properties > Général > "Croisements de conducteurs", with no
hops, hops on horizontal wires, or hops on vertical wires. Choosing the
orientation rather than following drawing order keeps a project
consistent.

Only the drawing changes: no element is added and no wire is split. A
wire that ends or bends on another one is a junction and never hops,
and a crossing too close to the end of a segment for the arc to fit is
drawn plainly. PDF, SVG and image export and printing show the hops,
since they paint through Conductor::paint(); DXF export, which writes
the segments itself, does not.

The setting is saved as <wire_crossings hop="..."/> under the project
root, next to <usage>, and only when hops are on, so a project that
never used them saves exactly as before.

The geometry is in wirehops.cpp, free of any graphics item, and tested
on its own. Conductor::paintedPath() feeds it the conductors of the
folio from a snapshot of their scene points, rebuilt only when a
conductor changes shape, moves, or enters or leaves a folio; each hop
path is cached the same way. Asking the scene for the conductors in a
rect instead strokes every candidate's shape, which made a 366-wire
folio render 0.6 s slower; with the snapshot the difference is within
measurement noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:18:12 +13:00
ispyisail 0cf7975be3 Fix bugtracker #344: folio "one text per potential" applies at once
Ticking or unticking "Afficher un texte de potentiel par folio" in
Folio > Properties only assigned Diagram::defaultConductorProperties.
Nothing recalculated the conductor texts, so texts hidden by the option
stayed hidden after it was turned off (and duplicates stayed visible after
it was turned on) until each wire was edited or the project was reopened.
That made the option look broken, which is part of what #344 reports.

When the option changes, reset each conductor's text visibility from its
own properties, then run calculateTextItemPosition() on every conductor of
the folio so the option is applied again from scratch.

Checked in the GUI on examples/industrial.qet folio 4 (26 wires with text,
19 potentials), master vs this commit: unticking now shows the hidden
texts immediately, ticking hides the duplicates immediately; master's
drawing does not change in either direction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:03:18 +13:00
Kellermorph 48aac69391 Merge upstream/master into prefix-button
Only one conflict: the include block of
sources/ui/configpage/generalconfigurationpage.cpp, where upstream adds
editor/terminalnamecheck.h next to the two includes of the prefix
editor. All three are kept. The file list in
cmake/qet_compilation_vars.cmake merged itself this time.
2026-09-30 11:10:02 +02:00
Kellermorph 1d339a8b27 Settings: only write the prefix rows that were edited
Review findings on the prefix editor:

- OK without touching anything changed the file: every row was written
  back, so an explicit <prefix/>, which reads as an empty field and
  cancels the inheritance, was dropped and that folder started
  inheriting its parent's prefix again. A row is now written only when
  it was edited or no longer holds what the file has, and the two kinds
  of empty field look different: hasPrefix() tells an explicit
  <prefix/> from a missing one, which is what the field hint shows.
- load() copied a broken file aside before the user had chosen
  anything, so every "Corriger le fichier" left a .bak behind while the
  message said nothing had been modified. The copy is now made by
  save(), right before the file is replaced: repairing or cancelling
  leaves nothing behind, and a copy that cannot be made stops the write
  rather than destroying the only copy.
- The button followed the combo box only for "Parcourir...": put back
  on "Par defaut" it fell back to the previously saved path instead of
  the default one. The directory of the entry being displayed is now
  used, "Par defaut" being dataDir()/elements/.
2026-09-30 11:01:31 +02:00
ispyisail bdc1d5878d Position X/Y of a symbol text is the point chosen by its alignment
The text properties showed and edited the top-left corner of a text,
whatever its alignment, and changing the alignment did not move the
text. A right-aligned label next to a symbol therefore needed a
different X for every text length to keep the same gap (#1155).

Position X/Y is now the anchor point: the point of the text chosen by
the alignment (right edge for "right", middle for "centre"...). Typing a
position puts that point there, and changing the alignment moves the
text so that the new anchor point is on the same X/Y. It is the same
point that already stays fixed when the text changes.

Applies to the element text properties on a folio and to the text
field properties in the symbol editor. The saved x/y is still the
top-left corner, so projects and symbols load and save unchanged and
look the same; only the numbers shown for texts that are not top-left
aligned change.

The geometry is in textanchor.h, shared by DiagramTextItem and
PartDynamicTextField, and tested in tst_textanchor.

Not changed: static texts of the symbol editor (PartText), whose
position is tied to the font baseline; texts inside a text group, which
have no position or alignment of their own; rotation still pivots on
the top-left corner (or the centre), not on the anchor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:12:43 +13:00
ispyisail 696783d350 Merge master into feature/terminal-name-check
Resolve the tests/qttest/CMakeLists.txt conflict with tst_configprofile
by keeping both tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:04:37 +13:00
ispyisail 8b82e348f2 Choose a conductor colour by double-clicking it
In the F2 conductor colour dialog and the toolbar's "Other colour..."
dialog, a double-click on a basic or custom colour swatch now chooses it
and closes the dialog, as OK does (forum topic 3190). Qt's dialog only
selects the swatch on a double-click.

ColorDialogDoubleClick filters the dialog's QWellArray swatch grids,
found by class name once the dialog is shown, because Qt builds them
only then when a native dialog was offered but not shown. Where the
platform's native colour dialog is used instead (macOS, some Linux
desktops) there are no swatches to watch and nothing changes. The
toolbar dialog is now an instance rather than QColorDialog::getColor(),
so the filter can be installed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 13:47:45 +13:00
ispyisail a3f10b5959 Merge remote-tracking branch 'upstream/master' into revive/654-rotating-backups
# Conflicts:
#	sources/qetapp.cpp
2026-09-30 11:44:49 +13:00
Kellermorph 03bffa96c7 Merge remote-tracking branch 'upstream/master' into prefix-button
Conflicts in cmake/qet_compilation_vars.cmake only, where upstream added
terminaluuids.cpp/.h next to the qetlabelsfile.cpp/.h this branch adds :
both are kept, qetlabelsfile first to stay alphabetical.
2026-09-30 00:25:14 +02:00
Kellermorph 6ea626fec7 Settings: configure the folder prefixes of the user collection
The prefixes of a collection's folders live in a qet_labels.xml that so
far could only be edited by hand, and a hand-edited file is easy to
break: one extra </category> and the file stops being well formed, which
the lookup answers with "no prefix at all". Labels then degrade
silently, without any error anywhere - the file is simply ignored. Add
a way to edit it from the settings.

- "Configurer les préfixes…" next to the user collection path opens
  PrefixConfigurationDialog, which lists every folder of that
  collection, subfolders included, one line edit each, with "Tout
  déplier"/"Tout replier" and OK/Abbrechen. It writes on OK only :
  cancelling leaves the collection exactly as it was, and an emptied
  field drops the <prefix> again so the folder goes back to inheriting
  its parent's.
- QetLabelsFile owns the reading, scanning, structure building and
  writing of the file. prefixFromLabelFile() moves there unchanged from
  assignvariables.cpp as prefixForPath(), so the lookup used by label
  assignment and the one used by the dialog are one piece of code.
- Entries whose folder no longer exists on disk are offered as
  Conserver or Supprimer and only applied on OK.
- An unparsable file is copied to qet_labels.xml.bak first, and the
  dialog then reports the line and column of the syntax error, with
  "Corriger le fichier" as the default choice - such a file may be one
  forgotten tag away from being valid - and rebuilds the whole
  structure only when "Reconstruire" is picked. A broken file that
  cannot be backed up is refused rather than overwritten.

The scan reads non-hidden directories recursively in name order,
without following symlinks. A collection without any subfolder has
nothing to configure and says so instead of opening an empty dialog.

Tests: a standalone harness kept outside the tree (84 checks :
structure, inheritance, explicit empty prefix, orphan handling, broken
file, wrong root element, broken file whose backup cannot be written,
reload after save) and the dialog driven offscreen (29 checks,
including "reject creates no file"). ctest 27/28, the failing
tst_menubarkeyboard being the headless F10 test, unrelated to this.
The new strings are French source strings like the rest of the code;
the .ts files are left to the translation update.
2026-09-29 23:52:22 +02:00
ispyisail 1dd56df048 Keep three crash-recovery snapshots instead of overwriting one
Discussion #598, reviving PR #654. The crash-recovery backup written
every 20 minutes went to a single file. If the project was already in a
bad state when a backup ran, that bad state replaced the only recovery
copy.

QETProject now writes the backups in turn to three KAutoSaveFile slots
(BackupGenerations), so one bad write only replaces the oldest
snapshot. Scope is crash recovery only; the opt-in autosave is
unchanged.

After a crash, the recovery prompt groups the snapshots by project and
offers one row per project with a list to pick the snapshot to reopen,
newest selected by default. The snapshots not picked are deleted.

Ported onto current master: writeBackup() keeps the "skip if nothing
changed" check (bugtracker #273) and offerBackupFiles() keeps its place
after the stale-file filter and before the crash report (#901).

Tested with the backup interval shortened to 4 s (test build only):
after three changes, master holds one recovery file, overwritten each
time; this branch holds three, with 4, 5 and 6 folios. After killing
QET, the prompt lists the project; picking the oldest snapshot reopens
4 folios, the default reopens 6. Twice each. ctest 34/34 with and
without KDE Frameworks; the carried-over KAutoSaveFile test passes in
the nokde build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:45:25 +13:00
ispyisail a28e834185 Remember dialog size and position across sessions
QETElementEditor, QETDiagramEditor and QETTemplateEditor already
persist their geometry via QSettings; none of the modal QDialog
subclasses did, so any dialog resized to see more of its content
(Search and Replace, Diagram properties, Export, ...) is back to its
default size the next time it's opened.

Adds QET::trackDialogGeometry(), one call at the end of each dialog
constructor (after any default resize()), following the same
restoreGeometry()/saveGeometry() pattern as the three editors above,
stored under [dialoggeometry] and keyed by class name by default. An
explicit key is used for PropertiesEditorDialog, a single class
templated over several unrelated wrapped editors, so they don't all
fight over one saved size.

Covers the 36 dialogs of the original change (#691) plus seven added
since: RenumberElementsDialog, MaterialEntryDialog, AiAssistantDialog,
DuplicateOffsetDialog, ImageTransparentColorDialog, PdfPagesDialog and
WiringListDialog.

Deliberately not touched: BackupDialog and ImageCropDialog, which
setFixedSize() themselves, and MaterialSelectionDialog, which already
restores its own size.

A size saved while the dialog sat partly off-screen is restored on
screen: restoreGeometry() moves it back inside the available screen
(checked: saved at 1502,972 on a 1600x1000 screen, reopened at 600,317).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:31:12 +13:00
ispyisail 576f085ce8 Drop hardcoded light-gray background on disabled PLC-IO type combo
masterpropertieswidget.cpp set an inline "background-color: #f0f0f0"
stylesheet on the disabled type combo box in the PLC master I/O
table. Qt already renders disabled widgets correctly without help;
the hardcoded light color just breaks under a dark palette, showing
a bright box in an otherwise dark dialog. Dropping the stylesheet
lets it render like every other disabled widget in the app.
2026-09-30 09:44:56 +13:00
ispyisail bd4d942d71 Add a terminal name check when saving an element
Discussion #1157. IEC 61666 §4.1 requires each terminal to be
identified unambiguously within its object, so two terminals of one
element must not share a name.

On save, the element editor now:
- refuses to save when two terminals share a name, lists the names
  ("N ×3") and selects those terminals;
- warns, and still saves, when a terminal has no name. Folio reports,
  conductor definitions and thumbnails are skipped.

Names are compared after trimming surrounding spaces, case sensitive.

Settings > General > Editor has a new checkbox, on by default, that
turns both checks off (elementeditor/check-terminal-names).

--check-elements applies the same rule: repeated names are a FAIL,
missing names a WARN. It ignores the setting, since it is an explicit
check. On the shipped collection this reports 82 FAILs, the elements
that repeat a terminal name today.

The rule lives in the header-only editor/terminalnamecheck.h, shared
by both, and is unit tested by tst_terminalnamecheck.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 07:24:55 +13:00
ispyisail ddc645174f Connect an AI assistant: say when Python is missing
The configuration names "python" or "python3", or the Python the Windows
installer can add. When none of those can run, the assistant fails with
an error far from the cause, so the dialog now says so in bold, with what
to do: tick "Python pour l'assistant IA" in the installer or install
Python (Windows), or install Python 3 from the system's packages.

Windows 10 and 11 put a python.exe in WindowsApps that only opens the
Microsoft Store, so a PATH search alone reports Python where there is
none. A result there is reported as possibly that shortcut. Backslashes
are converted explicitly, not with QDir::fromNativeSeparators(), which
leaves them alone off Windows and so could not be tested here.

tst_aiassistantsetup: 17 cases (8 new: the bundled Python is taken
without looking on PATH; none, python.org, the Store shortcut with either
separator, and a Linux folder named WindowsApps). Each of the 4 new rules
was removed in turn and the test failed. ctest 32/32. In the GUI, the
same install layout shows the warning with no python3 on PATH and no
warning with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:17:16 +13:00
ispyisail c76ef10e0b Add Help > Connect an AI assistant
A dialog showing the configuration an AI assistant needs to start
QElectroTech's MCP server (misc/qet-mcp), with this installation's paths
filled in: pick the assistant (Claude Desktop, Claude Code, GitHub
Copilot in VS Code, Cursor, Gemini CLI, Codex CLI, LM Studio), the
drawings folder the assistant may use, and whether it may edit (off by
default). It says where the text goes, warns that text inside a project
from someone else can steer an assistant, and copies the text. It writes
nothing and starts nothing.

AiAssistantSetup finds the server beside the running program, in the two
places the packages put it (<root>/mcp beside <root>/bin on Windows,
<prefix>/share/qelectrotech/mcp beside <prefix>/bin otherwise), and the
Python the Windows installer can add. When there is none, the dialog says
the server is not installed with this version and links the guide.
The configuration is written with QJsonDocument, so Windows paths are
escaped correctly; Codex CLI gets TOML.

tst_aiassistantsetup (9 cases): both layouts, no server, the bundled
Python on Windows only, valid JSON for every JSON client with paths that
parse back unchanged, the "servers" key for VS Code, "type" only where
wanted, editing off unless allowed, escaped TOML. Each of 8 rules was
removed in turn and the test failed. ctest 32/32.

Checked in the GUI (Xvfb): Help menu opens it; from a build tree it
reports the server missing and disables Copy; from an install layout it
fills the paths, and the text Copy put on the clipboard, used unchanged
as a Claude Code configuration, ran a real qet_project_info call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:29:50 +13:00
Laurent Trinques 041e0fc2a6 Merge pull request #1123 from arummler/cpp-standard-raise
Fix small depreciations in C++20
2026-09-29 01:01:14 +02:00
Laurent Trinques 3df6d450fe Merge pull request #1121 from ispyisail/fix/titleblock-custom-vars-1113
Fix #1113: title-block variables shown by name and missing in Replace
2026-09-28 23:28:52 +02:00
Andre Rummler 574d6dcd56 Fix captor operator [=] which needs to be written explciitly [this] in C++20. 2026-09-28 23:01:42 +02:00
ispyisail 08bce54a2f Fix #1113: custom title-block variables in Find/Replace and unset placeholders
Two problems with a custom variable newly added to a title-block template:

1. Find/Replace > Folio > Custom showed an empty table, so the user had to
   know and type each variable name. It now lists every custom variable the
   folios already carry plus those their templates use, with empty values.
   An empty value now means "leave unchanged", like every field of the main
   tab; only the variables actually filled in are written to the folios.

2. The title block showed the variable's own name ("%doc-type") until the
   folio's properties were opened, because interpreteVariables() only
   replaces names present in the context. Placeholders in the template text
   that no key resolves now render blank, as auto-added unset ones already
   did since #973. Only the template's own text is considered, so a value
   that contains "%something" is never touched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:59:21 +13:00
Kellermorph 8b54ea400b Fix material list reading, selection and article fields
Follow-up to #1088, covering its review comments and one behaviour
change found while testing the feature:

- MaterialList::load() names the columns from the machine key line when
  the file has one, instead of from the translated label line above it.
  A catalogue written in another language fills the element fields
  again, and saving no longer replaces the key line with labels.
- The entry picked after "New entry" is found by comparing the columns
  one by one instead of comparing the two maps as a whole: the entry
  form leaves the empty columns out, so the record never matched the
  line that had just been written and the search was cleared for
  nothing. The search is now only given up when it really hides the new
  line.
- Applying a catalogue entry pushes an undo command only when the live
  edit is on. In the properties window, where there is none, the fields
  wait for "Apply", so "Cancel" gives the element its own values back
  instead of leaving the picked part in place.
- Cells a file holds past the header are kept in MaterialRecord::extra
  and written back, so appending an article never shortens a line.
- An empty cell of a column describing the article itself (MaterialList::
  isArticleBound: description, designation, manufacturer, order number,
  supplier, model, ratings, dimensions, auxiliary block) clears the
  field, so an element never keeps the manufacturer of the part picked
  before. An empty cell of any other column (function, comment, notes,
  plant, location, quantity, unity), and any column the file does not
  hold at all, leaves the field alone.
- Comments left where the review asked for them: the corner button
  lookup, the ';' separator fallback, the search filter cost.
2026-09-28 22:38:41 +02:00
Laurent Trinques 40f0235c71 Merge pull request #1088 from Kellermorph/materiallist
Add a material list lookup for the element article fields
2026-09-27 21:43:52 +02:00
Kellermorph d5ae15f568 Add a material list lookup for the element article fields
The element description dialog only offered typing, so every article of
a catalogue had to be copied in by hand into the Artikelbeschreibung
row of each block, over and over.

Settings > General now keeps the path of a CSV material list (Créer...
writes a template with the header lines), and the Artikelbeschreibung
row of every block of the Bauteil dialog shows a "..." button opening a
searchable listing of that file. "Appliquer" fills the fields of that
block only -- the main block gets everything but label and formula, an
auxiliary block gets description, number, manufacturer, references,
supplier, quantity, unit and additional info -- and a cell that is
empty in the file never erases a value already in the element. The
symbol editor is left alone.

- sources/materiallist/: CSV reading and writing (UTF-8 BOM, ';', every
  field quoted, atomic write through QSaveFile), two header lines
  (translated labels then the canonical English keys), the selection
  window and the new entry form.
- The selection window reads the file again every time it opens, keeps
  its size, column widths, column order and sort between openings,
  shows every column of the file, scrolls all four directions with the
  wheel (Shift + wheel moves the columns) and puts back the order of
  the file when the corner above the row numbers or a row number is
  clicked.
- German translations for the new strings in lang/qet_de.ts.
2026-09-27 21:18:54 +02:00
ispyisail ad88505f87 Fix the build without KDE Frameworks (conductor properties panel)
conductorpropertieseditorwidget.cpp, from the #500 work, included
<KColorButton> unconditionally, so a build with BUILD_WITH_KF=OFF stopped
at "KColorButton: No such file or directory". Include the nokde stand-in
there instead, as dynamictextfieldeditor.h and terminaleditor.h do. It
has the same changed() signal the file connects to.

Checked on Ubuntu 22.04 (Qt 6.2.4, BUILD_WITH_KF=OFF): the file fails on
master and compiles with this change; together with #1085 the whole tree
builds and links. Builds with KDE Frameworks compile the same line as
before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-28 07:59:41 +13:00
ispyisail f62a44827b Add a setting to show collection search results as a tree
#1051 replaced the filtered tree with a ranked flat list. A new checkbox
in Settings > General, "Afficher les résultats de recherche sous forme de
liste triée" (elementscollection/search-flat-list, default on), keeps the
ranked list; unticked restores the pre-#1051 filtered tree search.

The Insert picker and shortcut bar call rankedSearch() directly and are
unaffected. Down/Enter from the search field only apply to the flat list.

Requested by scorpio810 on #1051.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-28 05:43:11 +13:00
Laurent Trinques 0b4f480dfe Merge pull request #502 from ispyisail/proto-conductor-selection-properties
Add wire properties to the Selection properties panel, as an option (#500)
2026-09-27 09:11:22 +02:00
Laurent Trinques 05260de87b Merge pull request #1060 from ispyisail/feature/shortcut-bar-symbols
Add pinned elements to the shortcut bar, with a width you can set
2026-09-27 09:09:44 +02:00
Laurent Trinques 7dc1d345b0 Merge pull request #1057 from ispyisail/feature/mouse-gestures
Add right-drag mouse gestures to run commands
2026-09-27 09:02:44 +02:00
Laurent Trinques a3dc8d3ad4 Merge pull request #1055 from ispyisail/feature/context-toolbar
Add a command bar beside the selection after a click
2026-09-27 09:00:29 +02:00
Laurent Trinques b1fe1ef7a7 Merge pull request #1054 from ispyisail/feature/shortcut-bar-customise
Add customising the shortcut bar in place
2026-09-27 08:59:55 +02:00
Laurent Trinques 9584fc4b7c Merge pull request #1053 from ispyisail/feature/shortcut-bar
Add a shortcut bar that opens at the cursor with S
2026-09-27 08:59:32 +02:00
Laurent Trinques 30c97612a2 Merge pull request #1042 from ispyisail/feature/place-without-drag
Add double-click placement from the collection, and A to repeat it
2026-09-27 08:40:42 +02:00
ispyisail 5120feb417 Add an optional label to pictures (#349)
A picture can now carry a caption, set from its properties panel
("Libellé"). It is drawn centred under the picture at the folio's
normal text size whatever the picture's scale, turns with it, and
moves, copies and prints with it because the picture itself paints it.
Clicking the caption selects the picture.

Saved as a "label" attribute on <image>, written only when non-empty:
a project without labels saves byte-for-byte as before, and older
versions open a labelled project and simply ignore the caption.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-27 16:12:51 +13:00
ispyisail ff795258c4 Conductor dock: turn it on in the preferences, off by default (#500)
Showing a selected conductor's properties in the selection-properties
dock is now a preference, on the General page under Appearance:
"Afficher les propriétés d'un conducteur sélectionné dans le panneau
Propriétés de la sélection". It is off by default, so selecting a
conductor does what it did before unless the user turns it on.

This replaces the View-menu toggle, which defaulted to on; that entry
is dropped in the merge with master. Same setting key
(diagrameditor/conductor_properties_panel).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:13:55 +13:00
ispyisail 0cb424bd06 Merge remote-tracking branch 'upstream/master' into proto-conductor-selection-properties
# Conflicts:
#	cmake/qet_compilation_vars.cmake
#	lang/qet_en.ts
#	sources/qetdiagrameditor.cpp
2026-09-27 09:07:41 +13:00
ispyisail 5bd55801e9 Pin elements to the shortcut bar
The bar's customising window gets an element search next to the command
list. Type part of a name, then drag a hit onto the bar, double-click
it, or press Enter to pin the best one. A pinned element shows as its
icon among the commands, and clicking it places the element, as the
picker does.

Pinned elements are saved in the same list as the commands, by
collection path (common://, custom://, company://), so the row keeps
the user's order. Elements embedded in a project are not offered: their
path names the project as loaded now. Elements are offered for the
empty-folio bar only; with something selected the bar is for acting on
it. Once any element is pinned there, the palette folder grid under the
bar is hidden, and comes back while typing a search.

Dragging a pinned element off the bar onto the commands, or a double
click, removes it. The Preferences page shows pinned elements by name
and icon, and removing one there drops it instead of listing it as a
command. The customising window is now kept on screen, since it is
taller than before.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 03:16:56 +13:00
ispyisail 6763103d57 Merge master into place-without-drag (#1042)
# Conflicts:
#	sources/diagramview.h
#	sources/qetdiagrameditor.cpp
2026-09-26 22:39:52 +12:00
ispyisail b57c37ca62 Run commands with right-drag mouse gestures
Hold the right button on a folio and drag: a ring appears around the
point where the button went down, showing up to eight commands, and the
one in the mouse's direction is highlighted with its name underneath.
Releasing runs it; releasing near the centre cancels. These are
SolidWorks' mouse gestures.

The commands are the shortcut bar's for the selection, placed clockwise
from the top, so customising the bar customises the ring. As with the
context menu, a right press selects what is under the mouse first.

A plain right click still opens the context menu, now on release on
every platform. The view tracks the right button and opens the menu
itself; the platform's own right-click event (sent on press on X11, on
release on Windows) is ignored while gestures are on, so the menu is
never opened twice. The keyboard menu is unchanged.

The view keeps out of the way while a tool or a placement is running,
since a right click cancels or finishes those, and while a text is
being edited. The new General option "Gestes de la souris avec le
bouton droit" (diagrameditor/mouse_gestures, on by default) turns it
off and restores the previous right button exactly.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
(cherry picked from commit a625142f36e619bb4d4f3551b6086f2dd8eabc86)
2026-09-26 21:40:46 +12:00
ispyisail b80d6e7cd3 Show the selection's commands beside the cursor after a click
After a click that selects something on a folio, a small row of commands
appears just above and to the right of the cursor, as the SolidWorks
context toolbar does. It fades as the mouse moves away and is gone past
200 pixels; a click elsewhere, the wheel, a key press or an emptied
selection hide it too. Clicking a command leaves it up, so rotate can be
clicked again.

The commands are the shortcut bar's for that selection -- elements or
conductors -- at most eight, so customising the bar customises this as
well. It is a child of the view's viewport, never a window, and never
takes the focus.

It is not shown after a drag (moving items, a rubber band), while
placing or drawing (Diagram::eventInterfaceIsRunning()), on
a read-only folio, or when switched off with the new General option
"Afficher les commandes près de la sélection" (diagrameditor/
context_toolbar, on by default).

ShortcutBarSettings::contextFor() now decides the context for both the
bar and this toolbar.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
(cherry picked from commit 78f3c1a2bc5c3cf1f7beae4bd60aefec20847d6d)
2026-09-26 21:40:32 +12:00
ispyisail 6d42556d9f Customise the shortcut bar in place
Right-click the shortcut bar, or click the "…" button at its end, and it
turns into a small window holding two lists: the bar's commands, left
to right, and every other command. Drag a command onto the bar, off
it, or to another place on it; a double click moves it to the other
list. Terminé saves and shows the bar again where it was, with the
result; Annuler, Esc or closing the window leaves it as it was.
"Valeurs par défaut" puts back the defaults for this context.

A Qt::Popup closes on a press outside it and holds the mouse grab, so
the bar is re-shown as a Qt::Tool window for the time of the edit.
Only QSettings is written, through ShortcutBarSettings, never the
project's undo stack.

The popup now looks the commands up itself (popUpShortcutBar(pos,
context)) instead of being handed actions, since it has to rebuild
them after an edit. ShortcutBarSettings::availableIds() lists what can
go on the bar, shared with the configuration page. An empty bar still
shows its "…" button, so it can be filled again.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
(cherry picked from commit 4145d4bec1d403126b2a6587105aea47092db8a2)
2026-09-26 21:40:19 +12:00
ispyisail 2da101ca90 Add a shortcut bar that opens at the cursor with S
Pressing S on a folio opens the element picker at the cursor with a row
of commands above it, chosen by what is selected, like the SolidWorks
shortcut bar:

  nothing selected     insert last element, element picker, text, line,
                       rectangle, terminal strip plan, paste, folio
                       properties
  elements selected    rotate, rotate texts, edit, copy, cut, delete
  only conductors      reset path, edit, delete

Each row is a list of ShortcutManager ids, so any registered command can
go on it and the bar carries no command list of its own. The lists are
in QSettings (diagrameditor/shortcut_bar/<context>); a context the user
has not changed follows the defaults. A disabled command keeps its
place, greyed, so a row looks the same each time. Clicking a button
closes the bar and triggers the action.

A new configuration page, "Barre de raccourcis", edits the three lists:
add, remove and reorder any diagram editor command.

To make that possible:
- ShortcutManager::action(id, owner) returns the action a given window
  registered under an id, since each editor window registers its own.
- The add-item actions (text, image, shapes, terminal strip plan) are
  registered as diagrameditor.add_<kind>, with no default key. They also
  appear in the Shortcuts page and can now be bound.

Discussion #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
(cherry picked from commit 53c1e213282b9f7226ca1c09e81703113236bb88)
2026-09-26 21:40:19 +12:00
ispyisail 92cbc8fd2a Merge master into stage 2 (#1042) as the base of the stage PRs 2026-09-26 21:40:18 +12:00
Laurent Trinques 132c564aef Merge pull request #1040 from ispyisail/feature/text-grid
Add a finer snap grid for dragged texts
2026-09-26 08:21:48 +02:00
ispyisail e6291eb8e8 Place an element from the collection without dragging it
DiagramEventAddElement is already a good placement mode: the element
follows the cursor on the grid, a left click drops it, Space rotates it,
and it stays loaded for a run of the same symbol. Its only caller was
DiagramView::handleElementDrop(), so it could be reached only by
finishing a drag. Double-clicking a symbol in the Collections dock
opened the element editor instead.

- DiagramView::startElementPlacement() is split out of
  handleElementDrop(). defaultPlacementPos() uses the cursor when it is
  over the view and the centre of the visible area otherwise.

- ElementsCollectionWidget emits insertElementRequested() on double
  click, or Enter on the highlighted item. The host decides which view
  receives it, so the widget can later be reused outside the editor.

- When there is nowhere to place it (no folio open, read-only project),
  the element editor opens, as a double click did before.

- "Insérer le dernier élément" (Édition menu, default key A) places the
  last element again. DiagramView reports every placement it starts, so
  an element dropped by drag counts too. Macros are not remembered.

A rather than Space: Space rotates the pending element inside placement
mode and is bound three more times in this editor. The key is a
ShortcutManager default and can be changed in the Shortcuts page.

Double click placing is a behaviour change, so it has a preference,
"elementscollection/double-click-inserts" (default true), shown in
Configuration as an opt-out: "Double-cliquer dans la collection ouvre
l'éditeur d'élément au lieu de l'insérer".

Discussions #676 and #1033.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 11:53:34 +12:00
ispyisail 95f3ea2f6d Share the text grid choices; add a preference, a status hint and a unit test
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
2026-09-26 10:22:50 +12:00
ispyisail 2807771200 Jump to a cell on another folio from Ctrl+G, typed as 3-B13
The popup now also takes a folio position before the cell, the way
cross references and folio reports write it by default (%f-%l%c): 3-B13
offers "Folio 3 (<title>), case B13", and Enter shows that folio and
zooms onto the cell. Case and a leading "p" are ignored, and the
separator is optional: 3-b13, 3b13, P3B13 and p3-b13 all work. A plain
B13 still means the current folio, and P3 alone is still row P,
column 3: a folio always needs a cell after it. A folio position past
the last folio, or a cell outside that folio's border, offers nothing.

The zoom on another folio is queued, so it runs once the tab switch
has been handled. Checked on the ATS example: jumping to 1-C5 from
folio 3 gives the same view, pixel for pixel, as C5 typed on folio 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 08:36:53 +12:00
ispyisail ce0e7e6867 Add jumping to a folio cell such as B13 from Ctrl+G (#1034)
Typing a cell reference into the "Atteindre un élément" popup now offers
"Case B13"; Enter zooms the view onto that cell with one cell of margin.
The cell is read the way the border labels it: row letter(s) then column
number, honouring the "columns start at 0" setting and multi-letter rows
(AA, AB...). Cells outside the folio are not offered.

BorderTitleBlock::cellRect() is the reverse of convertPosition(); a
round trip over every cell of a 30x23 folio, under both column-numbering
settings, returned the same cell for all 1380.

When an element is labelled exactly like the cell (K1), the element stays
first so Enter keeps its old meaning; the cell is listed after it.

DiagramView::zoomToRect() re-centres from a queued call: zooming in makes
the scroll bars appear, and the viewport resize that follows is anchored
under the mouse (setResizeAnchor(AnchorUnderMouse)), which otherwise
scrolls the view away from the cell straight after the zoom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
2026-09-26 07:37:38 +12:00
ispyisail c3c264cd78 Merge pull request #1010 from Kellermorph/full-contact-comb
Full contact comb
2026-09-26 07:12:57 +12: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
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