Commit Graph

769 Commits

Author SHA1 Message Date
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
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 48883b320e Merge branch 'master' into feature-3dmouse-spnav 2026-09-24 15:45:33 +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
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
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
ispyisail 6fad3390df Fix issue #1002: conductor-colour toolbar swatch skips the rest of the potential
ConductorColorToolButton::applyColor() only recoloured the selected
Conductor objects. A wire drawn across a junction is several separate
Conductor segments sharing one electrical potential, so selecting one
segment and picking a colour left the rest of the wire its old colour
-- visible only by falling back to F2/double-click, which already
expands to relatedPotentialConductors() for exactly this reason
(ConductorPropertiesDialog, and QetScriptApi::setConductorProperty).

Expand each selected conductor to its potential before building the
undo command, matching that existing pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 07:08:31 +12:00
Laurent Trinques 3d0e7a27a3 Merge pull request #998 from ispyisail/revive/785-case-insensitive-flags
Make auto_num_locked/potential_isolating case-insensitive
2026-09-23 18:38:38 +02:00
Kellermorph 4e361f93fe Let the PLC master IO table columns be resized, and remember the layout
The IO table of MasterPropertiesWidget -- the panel that opens when a
PLC master placed on a schematic is edited -- set every section to
QHeaderView::Stretch. Stretch spreads the sections evenly over the
widget and disables section dragging altogether, so the column
boundaries were permanently fixed: they could be neither widened nor
narrowed, and only the width of the whole panel had any effect.

Use QHeaderView::Interactive instead, with movable sections and a set
of default widths, so the columns can be dragged as they are everywhere
else in the application. The layout reached this way is written to
QSettings under masterpropertieswidget/plc-table-header-state on every
sectionResized and sectionMoved, and restored the next time the table
is built -- the same header-state trick the free and linked element
trees of this widget already use, except saved automatically rather
than only from the context menu.
2026-09-23 10:27:50 +02:00
ispyisail 5b0785fcce Make auto_num_locked/potential_isolating case-insensitive
Reviving the still-relevant part of #785, closed 2026-09-10 purely to
clear a review backlog, not on merit. Investigated fresh against
current master -- one of the original PR's three targets turned out
to already be fixed independently: element_nomenclature_view's SQL
predicate for exclude_from_bom already does
"COALESCE(LOWER(TRIM(ei.exclude_from_bom)), '') NOT IN ('true', '1',
'yes', 'on')" (projectDataBase::createElementNomenclatureView()).

auto_num_locked and potential_isolating had no equivalent: five call
sites across terminal.cpp, terminalnumberingdialog.cpp and
elementinfowidget.cpp compared the raw stored string against the
literal "true" with QString::operator==, silently treating "True",
"TRUE", a trailing space, or any value written by something other
than this app's own checkbox as off -- with no error and no visible
difference from the checkbox being genuinely unticked.

Added QET::infoFlagIsTrue(), matching the same accepted spellings
("true"/"1"/"yes"/"on", case-insensitive, trimmed) the SQL predicate
already uses, and switched all five call sites to it.

Verified the exact comparison logic in isolation, outside any QET
build: 15 cases including "True", "TRUE", padded whitespace, "1",
"yes", "on", and their false counterparts -- all correctly
discriminated. Qt 6.10.2, ctest 13/13.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 16:47:07 +12:00
Laurent Trinques 4bcf5834e4 Merge pull request #984 from ispyisail/feature/scripting-opt-in
Scripting off by default, with a prompt and a setting to turn it on
2026-09-23 02:40:18 +02:00
ispyisail fa213d90d9 Add Ctrl+D: duplicate the selection, offset by a configured grid step (#991)
Split from #913's second suggestion. There was no shortcut for the common
"duplicate with offset" convention; the nearest existing feature,
"Collage multiple", is a different workflow (a dialog for repeating a
paste in a grid pattern, not a one-shot duplicate).

Ctrl+D copies the selection and places it immediately, offset by a
configured spacing and direction -- no interactive follow-the-cursor
step, unlike Ctrl+V. The first press (or after the setting is explicitly
reopened) shows DuplicateOffsetDialog: spacing in grid steps, direction
up/down/left/right. Every later press reuses whatever was confirmed then,
silently, so a row of copies is one key held down and tapped, not a
dialog every time -- unattended, repeatable stamping is the actual point
of a duplicate shortcut, which a dialog or an interactive placement step
on every press would defeat. A separate "Configurer la duplication..."
entry reopens the dialog on demand to change the setting later. Cancel
leaves the diagram untouched -- verified, not assumed: qet_diff against
the saved file shows 0 added.

Chaining ("keep tapping to lay out a row") needs no special handling:
QET already reselects whatever a paste just added
(PasteDiagramCommand::redo()), so the next Ctrl+D naturally continues
from the copy just placed rather than the original.

The offset is applied by hand rather than by asking paste()/fromXml() to
place the copy at a target position. Both of those feed the position
through Diagram::snapToGrid(), which reads
QApplication::keyboardModifiers() and rounds to the nearest PIXEL instead
of the grid whenever Ctrl is held -- and Ctrl is always held here, this
action's own shortcut being Ctrl+D. Measured before settling on this:
routing the offset through paste() first produced copies off-grid on both
axes, by an amount that tracked the selection's own bounding-box geometry
rather than being a fixed error -- caught by qet-mcp's qet_elements
against the saved file, not by eye. fromXml() is instead called with no
position argument at all (leaves every item at its source coordinates,
landing the copy on top of the originals -- (0,0) is not a position, this
is "keep the source coordinates"), and the offset is added directly with
setPos(). A plain addition cannot be off by a rounding rule that never
runs.

Conductors are not in the hand-translated set: fromXml() itself does not
reposition them either -- they load after elements are already in their
final place and take their geometry from their terminals, which have
already moved with the elements that own them. Verified this holds: drew
a conductor by hand between two elements (drag, not click-click),
selected both, Ctrl+D, and the new conductor correctly joins the two new
elements via qet_conductors -- not the originals, not a mix.

Verified end-to-end on a built binary via qet-mcp, not by eye:

  before          L2 (303,207)  L9 (512,196)     -- deliberately off-grid
  spacing=2, down    (303,227)     (512,216)      -- +0,+20 exactly
  same again, 2nd    (303,247)     (512,236)      -- +0,+20 again, chained

Both elements land exactly the configured offset from their immediate
source regardless of the selection's own alignment. Qt 6.10.2, ctest
11/11.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 11:30:30 +12:00
ispyisail 8334a9a27f Scripting is off until asked for, and says how to turn it on
A script reaches the whole project and, through the export calls, the
filesystem. That is a capability most people installing an electrical CAD
program never asked for, and leaving it on by default hands it to them
anyway. So QET_HAS_SCRIPTING builds now ship with it switched off.

QetSettings::scriptingEnabled() is the single answer, read by all three
places that need it, with QET_ENABLE_SCRIPTING=1 overriding the stored
value. The override is not decoration: a CI job or a batch run has no
dialog to tick, and a machine whose HOME is created fresh for each run has
nowhere to keep the setting either. It beats a stored "false" on purpose,
so a box unticked once cannot lock a build server out of --run for good.
Only the exact value "1" counts.

  --run          refuses with exit 3 and a message naming both ways in.
  Projet > Exécuter un script...  asks once, and turns the setting on if
                 the answer is yes. Asking beats grey: a disabled menu
                 entry says something exists and nothing about how to have
                 it, and this is the pattern people already know from
                 macro security in office software.
  Configurer QElectroTech > Général > Projets  has the checkbox, for
                 turning it back off. While the environment forces
                 scripting on, the box is disabled and says why, and
                 applyConf() then leaves the stored value alone rather
                 than quietly overwriting it.

runOnProject() checks as well, after both callers have. It is the one
function that actually evaluates JavaScript, so it is the one place a
future caller cannot forget to ask; the callers check first only to give a
better answer than it can.

Verified on the built binary, all four states, with an isolated HOME:

  stored   env  result
  absent    -   refused, exit 3
  true      -   script runs, exit 0
  false     -   refused, exit 3
  false     1   script runs, exit 0

tst_scriptingsetting covers the same matrix hermetically, in its own
QSettings scope, and was mutation-checked: flipping the default to true
turns defaultsToOff() red.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 04:06:33 +12:00
ispyisail bf7f49f334 Merge remote-tracking branch 'upstream/master' into review-635-fix
# Conflicts:
#	CMakeLists.txt
#	cmake/developer_options.cmake
#	cmake/qet_compilation_vars.cmake
#	sources/qetapp.cpp
2026-09-22 23:10:34 +12:00
Kellermorph 5d033bf1b2 Add custom application color picker in global settings
Add a KColorButton next to the 'Utiliser les couleurs du système'
checkbox in the Apparence settings tab:

- When the checkbox is active (default), system colors are used
  and the color button is disabled
- When the checkbox is inactive, the color button becomes active
  and lets the user pick any color for the entire application
- useCustomPalette() builds a full QPalette from the chosen color
  with proper light/dark text contrast, button shading, and icon
  theme switching
- The chosen color is persisted in QSettings as
  'customapplicationcolor' and restored on next startup

Files changed:
- sources/ui/configpage/generalconfigurationpage.ui: HBoxLayout
  with checkbox + KColorButton, customwidget declaration
- sources/ui/configpage/generalconfigurationpage.h: new slot
- sources/ui/configpage/generalconfigurationpage.cpp: load/save
  custom color, enable/disable logic, toggled slot
- sources/qetapp.h: useCustomPalette() declaration
- sources/qetapp.cpp: useCustomPalette() implementation,
  startup restore of custom color
2026-09-21 22:05:31 +02:00
Kellermorph ebab58e4b3 Add diagram background color picker with adaptive border/titleblock
Replace the white/grey toggle (m_grey_background) with a full color
picker widget (DiagramBgColorToolButton) in the Affichage toolbar,
matching the existing ConductorColorToolButton UX:

- Preset colors: White, Off-white, Light grey, Grey, Dark grey, Black
- Recently used colors section
- "Autre couleur..." opens QColorDialog for any custom color
- "Couleur système" restores the default dark-mode inverted background

New features:
- Diagram::m_custom_background_color flag: when the user picks a
  custom color, PaletteGraphicsView skips lightness inversion so the
  chosen color is displayed as-is
- Border and titleblock text/lines automatically switch between black
  and white based on Diagram::background_color.lightness(), so a dark
  background always shows a visible light border and titleblock content
- "Couleur système" restores Qt::white + re-enables inversion

Files changed:
- New: sources/ui/diagrambgcolorbutton.h/.cpp
- sources/diagram.h/.cpp: added static m_custom_background_color flag
- sources/palettegraphicsview.cpp: skip inversion when custom bg active
- sources/bordertitleblock.cpp: adaptive border pen color
- sources/titleblocktemplate.cpp: adaptive ink color for cell borders/text
- sources/qetdiagrameditor.h/.cpp: replace toggle with new widget
- cmake/qet_compilation_vars.cmake: register new source files
2026-09-20 22:58:08 +02:00
Laurent Trinques 1bec2700bd Merge pull request #924 from Kellermorph/auto-numbering-new-project
Add global auto-numbering rules to QElectroTech settings
2026-09-20 20:07:16 +02:00
Jeff Patterson ac78c51b70 Give every configuration page an icon at page size
The settings and project dialogs list their pages with 64 or 128 pixel
icons, and two pages had theirs at 22 pixels only: the terminal-strip
page and the shortcuts page, which borrowed configure-toolbars. Both get
a 128 pixel icon drawn in the style of the other page icons, the
shortcuts page under its own name, configure-shortcuts. The SVG sources
sit beside the PNGs.

On a dark palette the Printing and Export pages were small too: their
128 pixel icons exist in the light theme only, and Qt inherits by name,
not by size, so the dark theme's small copies were scaled up instead.
make_icon_themes.py now aliases the light files of the sizes a dark name
lacks, when they read on the dark window at 3:1.

A test asks the theme for every page icon at 128 pixels, on both
palettes.

Fixes #960
2026-09-20 10:44:09 -05:00
Kellermorph 7fa1ff35b8 Add global auto-numbering rules to QElectroTech settings
Add a 'Numérotation auto' tab to the global settings page (Settings >
Nouveau projet) where users can define default auto-numbering rules
for Conducteurs, Eléments, and Folios. These rules are automatically
transferred to every new project created.

Changes:
- Add NumerotationContext::saveToSettings()/loadFromSettings() static
  helpers for persisting named numerotation contexts via QSettings
- Add 'Numérotation auto' tab to NewDiagramPage with three sub-tabs
  using SelectAutonumW widgets (same UI as project properties)
- Add save/remove/persist slots for conductor, element, and folio
  contexts with immediate QSettings persistence on every change
- NewDiagramPage::applyConf() saves autonum settings when editing
  global defaults (no project)
- QETProject constructor loads global autonum settings from QSettings
  for new empty projects
2026-09-19 13:47:28 +02:00
ispyisail e42ebdc861 Add a conductor colour button to the Schéma toolbar (#461)
An electrician on the forum draws 400 V and 24 V circuits in the same
folio and wants the colour to be one click away
(qelectrotech.org/forum, viewtopic pid=23296). Today the quickest route
is F2, which opens a colour dialog and refuses to act unless exactly one
conductor is selected, so colouring a run means one conductor, one
dialog, at a time.

This adds a swatch button beside the auto-conductor actions. Picking a
colour does two things:

  - recolours every conductor currently selected, as ONE undo step;
  - becomes the colour of the next conductor drawn, through the
    LastUsedStyle mechanism #888 already added and Conductor's
    constructor already reads.

Either half is useful alone: with nothing selected it just sets the pen
for what comes next.

The menu lists the colours the trade names -- the three phases, neutral,
earth, and the ones used for control and extra-low-voltage circuits --
then any custom colours picked this session, then the full colour
dialog. A colour already in the standard list is not repeated under
"recently used".

Nothing is written to the project or to QSettings. That is deliberate:
it is the same session-scoped "what did I just use" idea as
LastUsedStyle, so it adds no persisted state and no file-format change.
Named presets stored per project -- what #461 actually asks for -- are a
larger feature that needs a maintainer decision first; the question is
still open on that issue since 21 June.

Verified on a virtual display against examples/Habitat-Schemas_developpes.qet,
reading colours back from the saved project rather than the screen:

  select all on folio 1, pick Rouge
      21 conductors {none:1, #ff5500:2, #ff0000:6, #00aa00:5, #0000ff:7}
      -> all 21 #ff0000
  one Ctrl+Z
      -> back to the original five-colour mix, exactly
  pick Marron with nothing selected, then draw a conductor
      -> the new conductor is #7b3f00

ctest 12/12, Qt 6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CaKympWT3owLotCpEN2CFj
2026-09-19 09:02:58 +12:00
Jeff Patterson 8ef130a59d Add a "qet" icon theme and load icons by name
QET had no icon theme: the 446 entries of the icon table and the 116
iconsets in .ui files each named a resource path, so an icon could only
ever be one file, and a variant for another palette or a vector source
had nowhere to go (GitHub #466, #690, #870). This adds the theme layout
without changing a single pixel; a dark variant comes in a follow-up.

The theme "qet" follows the freedesktop layout Qt's icon loader
understands. misc/make_icon_themes.py generates ico/icon-themes.qrc,
which aliases the existing ico/<size>/<name>.png files into
themes/qet/<size>/<name>.png, and ico/themes/qet/index.theme. No file
moves. The four table entries that paired a 16 pixel file with a 22
pixel file of another name (ConductorSettings, DiagramAdd,
DiagramDelete, DialogInformation) get the 22 pixel file aliased under
the 16 pixel name.

QETApp::initIconTheme() registers the theme before initIcons() and makes
it current on every platform, so a desktop icon theme cannot replace
QET's icons. Icons are then looked up by name: QIcon::fromTheme() in
qeticons.cpp and in the few places that built a QIcon from a resource
path directly, and theme="..." on the iconsets in .ui files, with the
resource path kept as fallback. Flags, color swatches, application and
MIME icons stay on their paths.

One entry does not go through the theme. The elements panel draws the
project root with ProjectFileGP in the 50 pixel slot it reserves for
element previews, and the name "project" also carries the 128 pixel
file the configuration dialog uses. On a 2x display Qt's loader picks
that file for a 50 pixel request and fills the slot. ProjectFileGP
loads the 16 and 22 pixel files directly, as before.

tests/qttest/tst_qeticons: every name in the theme resolves, the four
aliases resolve at 22 pixels, a Fusion tool button shows its icon at
3:1 with disabled weaker than enabled, and the project root icon stays
at 22 pixels or less when asked for 50 at 2x while the configuration
dialog still gets its 128 pixel file. The rendering helpers shared
with tst_qetpalette moved to tests/qttest/inkcontrast.h.
2026-09-17 05:41:50 -05:00
Laurent Trinques c3b0e6725e Merge pull request #896 from ispyisail/feature/886-custom-sql-reports
Add read-only enforcement, preview and import/export to custom SQL reports
2026-09-17 12:38:17 +02:00
Laurent Trinques 3f8e6418d0 Merge pull request #881 from Kellermorph/checkbox-master-slave
Add Hide full masters checkbox to slave linking widget
2026-09-17 12:30:42 +02:00
ispyisail 12ecf6b28f Add read-only enforcement, preview, import/export to custom SQL reports
Refs discussion #886: a saved-report manager built on custom SQL and
nomenclature.json. Most of what was asked for already existed --
Projet > Exporter au format CSV already builds/saves/reuses named
SELECT queries via ElementQueryWidget and nomenclature.json, and
Projet > Ajouter une nomenclature already inserts one into a folio.
This fills the three real gaps.

- projectDataBase::isReadOnlySelect() rejects anything that isn't a
  single SELECT/WITH statement. Checked in newQuery() itself, the one
  choke point every query path already goes through -- including a
  query loaded from a saved nomenclature/summary table's <query>
  element on project open, not just the dialog's own custom-SQL box.
  ElementQueryWidget shows the same check live as you type, and
  BOMExportDialog surfaces it before running or exporting anything.
- BOMExportDialog gains a Preview button + table (QSqlQueryModel),
  so a report can be checked on screen before committing to a CSV file.
- ElementQueryWidget gains Importer.../Exporter... buttons that
  read/write nomenclature.json's saved reports as a JSON file, so a
  report can be handed to a colleague or another install. Import asks
  before overwriting a locally-saved report of the same name.

Verified: Qt 6.10.2, builds clean, ctest 8/8. Drove the real dialog
through Xvfb: typed "DROP TABLE element" into the custom-SQL box and
got the inline warning immediately, then confirmed Preview also
refuses it with a "Requête refusée" dialog rather than running it.
Preview against the real default query returned live column headers
and a row. Export opens a save dialog without crashing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 10:09:14 +12:00
Andre Rummler ce8884394a Remove all code switches for Qt<6 with one exception: caching in titlebordertemplate. Function for caching not called since Qt4; might profit from complete removal.
Another exception: one non-converted code path in projectprintwindow.cpp to be followed-up.

One issue found in the Qt6 code path of diagramview.cpp which has been fixed.
2026-09-17 00:07:46 +02:00
Laurent Trinques c71839a291 Merge pull request #869 from Kellermorph/fix-crossref-textfield
Clear stale xref data from PLC slave elementInformations on unlink and position change
2026-09-16 21:28:10 +02:00
Kellermorph a7f406cd44 Merge remote-tracking branch 'origin/master' into fix-crossref-textfield 2026-09-16 11:49:55 +02:00
Kellermorph dad6206386 Address review feedback: parentGroup guard, ELMT_XREF constant, indentation
- Add !parentGroup() guard to stale xref removal in updateXref()
  to prevent deleting xref data that ElementTextItemGroup::updateXref()
  just stored for a grouped text item.
- Replace QStringLiteral("xref") with QETInformation::ELMT_XREF
  in linkelementcommand.cpp and masterelement.cpp.
- Fix indentation in plclinkwidget.cpp.
2026-09-16 11:49:49 +02:00
Jeff Patterson b208a4f131 Show the text color as a swatch and keep the terminal plan preview on white
The Selection properties "texts" tab painted the color value's own text in
that color, so the default black was unreadable on a dark palette. Show a
swatch in the cell instead and leave the text in the palette's color.

The terminal plan preview draws black ink with a white brush, like the
printed page it previews, on whatever background the view inherits from
the palette. Give the view a white background.
2026-09-16 04:39:31 -05:00
Kellermorph e6ffc8b622 Address all review feedback 2026-09-16 11:37:27 +02:00
ispyisail 2eed3acd3d Merge pull request #723 from IBSYSLevi/feature/cabinet-layout
Added width/height/depth properties to elements
2026-09-16 18:51:34 +12:00
Kellermorph c2f405c32d Add Hide full masters checkbox to slave linking widget 2026-09-15 21:48:47 +02:00
Kellermorph 9bfe18807f Fix undo not restoring PLC slave fields and stale connections on position change
Two additional bugs found after the initial xref cleanup:

1. Undo after unlink did not restore PLC variables (PLC_TYPE,
   PLC_ADDRESS, etc.) because PlcLinkWidget::on_m_unlink_pb_clicked()
   never stored the current group index in the LinkElementCommand.
   makeLink() only populates PLC fields when group_idx >= 0, so
   the slave was re-linked but appeared empty. Fixed by reading the
   group index from the master via groupIndexForElement() and calling
   setGroupIndex() before unlinking.

2. m_update_slave_Xref_connection was only cleared inside the
   if(m_slave_Xref_item) block in the updateXref() cleanup path.
   For AlignHCenter (Text field) position, no m_slave_Xref_item
   is ever created — the connections are stored in
   m_update_slave_Xref_connection but never cleaned up on unlink.
   On re-link, the old stale entries prevented new connections from
   being established. Fixed by clearing the list unconditionally
   in the cleanup path.
2026-09-14 16:19:37 +02:00
Kellermorph 3c1c29f6c3 connect searchfield 2026-09-13 21:32:34 +02:00
Kellermorph cafc5bcf6d Add Hide linked elements checkbox to PLC link widget 2026-09-13 20:36:59 +02:00
ispyisail a6fd42ac5f Import automatic numbering rules from another project
Forum #3186 / issue #850: a user who has built up conductor and element
numbering rules in one project has no way to reuse them in the next one.
The only answer today is to open both .qet files in a text editor and copy
the XML across by hand.

Adds an "Import from another project..." button to the auto-numbering page
of the project properties dialog. It offers every numbering found in the
chosen file, per category, with names that already exist here unticked by
default and a "replace same-named numberings" option for when that is what
the user wants.

The source file is parsed as plain XML rather than opened as a QETProject.
Opening it would run the whole load path, including the modal dialog raised
for a file written by a different version of QElectroTech -- a dialog the
user has no reason to see, since nothing but the <newdiagrams> block is
being read.

Two supporting changes:

  - readValuesFromProject() clears the three combo boxes before filling
    them. It only ran once before; it now runs again after an import, and
    without the clear every name appeared twice.

  - FolioAutonumberingW::setContext() likewise replaces its list instead
    of appending to it. It has a single caller, the line above.

This deliberately does not attempt the project-template feature also raised
on the forum thread. That needs decisions about where templates live and
what else they carry, and is better settled in a discussion first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:56:25 +12:00
ispyisail f9d063bd38 Merge pull request #821 from enesgursoy6110/fix/shortcut-conflict-scope
Scope shortcut conflicts to overlapping editors
2026-09-13 00:00:54 +12:00
ispyisail a0acf878a2 Revert "Merge pull request #759 from ispyisail/fix-shortcut-conflict-scope"
This reverts merge commit 3d5799773, restoring shortcutsconfigpage.cpp to
its state before it.

#759 and #821 fix the same issue (#757). #821 was opened on 8 September and
is the better fix; #759 was merged on 12 September without checking whether a
PR for it already existed, and its merge is what left #821 conflicting with
master. Reverting is the way to let the right change land.

#759 keys conflict detection on the row's category, which is a tr() string.
#821 keys on the shortcut ID prefix, which is stable and untranslated, and
encodes the overlaps the category cannot express: main-window actions are
live while any editor is open, and the depth.* actions are installed into
both the diagram and the element editor.

Checked against the registry rather than by reading -- 94 registered actions
plus the four depth.* ones registered through QObject::tr. On the shipped
defaults the two behave identically: all 24 shared sequences are legitimate
cross-editor duplicates and neither flags them. They diverge on shortcuts a
user assigns, where #759 misses four classes of real conflict that #821
catches: a diagram or element editor action given the main window's F1, and
a diagram or element action given a depth.* sequence.

The reason #759 looked adequate is that the scope prefix currently maps
one-to-one onto the translated category for all seven scopes, so same-scope
detection comes out the same either way. It fails only where scopes overlap,
which is the case #821 exists to handle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 23:51:41 +12:00