Commit Graph

9413 Commits

Author SHA1 Message Date
ispyisail 36fb048a80 Fix bugtracker #973: unset title-block custom variable shows its own name
BorderTitleBlock::updateDiagramContextForTitleBlock() skips merging a
page-level custom variable into the title block's render context whenever
its value is empty -- added by PR #572 to fix #531, where an empty
page-level value was shadowing a real project-level one of the same name.

But skipping the merge removes the key from the context entirely, and
TitleBlockTemplate::interpreteVariables() only replaces "%name"/"%{name}"
when "name" is an actual key in that context -- anything absent is left as
its own literal placeholder text. Folio Properties auto-adds every one of
a template's custom variables to the Custom tab with an empty value (#271/
#495) precisely so the user only has to fill in what's missing; until they
do, that variable now renders as e.g. "%label1" instead of blank.

Reproduced two ways: a synthetic fixture, and examples/2612_ats_singlephase.qet
itself, which already carries three such auto-added-but-unset properties
("label1", "label2", "label3") and renders all three literally on current
master.

Fix: skip the empty page-level value only when a project-level one already
exists to show through (preserving #531's guarantee); otherwise still merge
it in empty, so the placeholder resolves to blank rather than falling out
of the context altogether.

Verified against the shipped example (--export-png, before/after crop of
the rendered title block): "%label1"/"%label2"/"%label3" now blank. A
variable never added to the Custom tab at all ("%client", also present in
the same example) is unaffected -- nothing was ever configured for it, and
that is a separate, narrower case. Qt 6.10.2, ctest 11/11.

A related but distinct issue -- DiagramContext::toXml() trims a stored
value before saving, so an all-whitespace value is written as empty --
explains a second symptom from the same report (a single-space "workaround"
value vanishing after the project is reopened) but touches every
context-backed property, not just title blocks, and is left for a separate
fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 09:08:21 +12:00
Laurent Trinques 6fc7e4a090 Merge pull request #965 from elevatormind/no-sqlite
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m54s
Remove SQLite library dependency while retaining project db export
2026-09-22 18:51:00 +02:00
Laurent Trinques 1212f48c6d Merge pull request #979 from Kellermorph/colour-programm
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m48s
Add custom application color picker in global settings
2026-09-22 13:49:42 +02:00
Laurent Trinques e01f46c5b0 Merge pull request #981 from ispyisail/fix-974-report-link-colour
Fix report-link colour/style mismatch detection (#974)
2026-09-22 13:38:40 +02:00
ispyisail 3cec02b3f3 Fix report-link colour/style mismatch detection (bugtracker #974)
LinkElementCommand::redo() already had a check meant to catch exactly
this -- two report-linked conductors whose properties disagree -- and
ask the user which to keep via PotentialSelectorDialog. It never
worked: it built ONE combined list from three unrelated fields
(tension_protocol, wire_color, wire_section) and tested that whole
list for string equality, so a tension-protocol value could never
equal a wire-colour value even when every field individually matched
across every conductor. Worse, "wire_color"/"wire_section" are
ConductorProperties::m_wire_color/m_wire_section, a separate free-text
documentation pair that says nothing about how the wire is actually
drawn -- that's "color"/"style" -- so the one field #974 is actually
about was never compared at all.

Fixed by comparing each relevant field (text/num, function, tension
protocol, colour, line style) separately. Downloaded the reporter's
actual project, confirmed the mismatched wire reads color="#0000ff" on
one side of a "Folio suivant"/"Folio precedent" link and
color="#55aa00" on the other, with the link's other four conductors
matching correctly (ruling out a rendering artifact) -- see PR #980's
checkContinuity() extension, which now flags this class of mismatch on
sight.

Extracted the comparison into its own static
reportLinkNeedsPotentialChoice(), for the same reason
ConductorCreator::needsPotentialChoice() already exists as its own
method: a caller with nobody there to answer a modal dialog needs to
check first and decline, and the condition must not drift away from
the one redo() actually applies.

Fixing the comparison surfaced a real, previously-latent hang in this
session's own qet.linkElements(): PotentialSelectorDialog::exec() is a
plain QDialog::exec(), not routed through QET::QetMessageBox, so
headless --run has nobody to answer it. Measured directly -- hung
until killed with the property-comparison fix alone, clean refusal
after adding the guard. linkElements() now calls
reportLinkNeedsPotentialChoice() before constructing the command and
declines with a clear reason, the same choice addConductor() already
makes about ConductorCreator's own equivalent dialog.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 23:00:05 +12:00
Laurent Trinques 56f60be60a CI: try to fix 404 error on downlad page 2026-09-22 10:16:49 +02:00
Laurent Trinques 852f581206 Merge pull request #971 from arummler/fix-translation-syntax-02
Fix translation syntax follow-up
2026-09-22 09:57:30 +02:00
Laurent Trinques 3b626a7d1a CI: try to fix 404 error on downlad page 2026-09-22 09:50:14 +02:00
Andre Rummler 069a75d44b Use pugixml target in test removing the old directly including approach. Fix a static variable which is now missing as the correspondign source file is not used by the particular test. 2026-09-22 09:08:03 +02:00
Andre Rummler e1f887a036 Standarize pugixml header path to recommended variant with target INTERFACE. 2026-09-22 09:07:27 +02:00
Laurent Trinques c5edd0a54b CI: try to fix 404 error on downlad page
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m11s
2026-09-22 08:19:23 +02:00
Laurent Trinques 3a13287ba9 CI: delete auto-doxygen.yml 2026-09-22 08:05:53 +02:00
Andre Rummler 24776bfcf6 Avoid setting globally QLocale which was introduced in a recent MR as it will change number formats, etc. which is not necessarily what a user switching language wants. Beside that
small fix of a trasnlation comment.
2026-09-22 08:03:07 +02:00
Andre Rummler 3202c145e8 Fix French origin strings (language improvement). 2026-09-22 08:03:07 +02:00
Laurent Trinques 4888ae87f3 Merge pull request #978 from Kellermorph/feature/refresh-all
fix: update composite text %{label} when folio changes
2026-09-22 07:53:36 +02:00
Laurent Trinques 59344eb565 Merge pull request #977 from ispyisail/fix/591-text-resize-handles
Fix #591's resize handles: unreachable via plain click, wrong position
2026-09-22 07:51:32 +02: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 8985babfe7 fix: update composite text %{label} when folio changes
DynamicElementTextItem::updateLabel() resolved %{label} in composite
text using the stale value from elementInformations()["label"], which
is only set once at load time and never updated when the folio/page
number changes.

Use element->actualLabel() instead, which resolves the label formula
(including %F, %f, %id) against the current folio at call time.
2026-09-21 21:45:03 +02:00
ispyisail 29d16c3337 Fix #591's resize handles: reachable only via Shift/right-click, wrong position
Two bugs reported by @arummler on #591 after merge:

  "It works but to select the text field one has to right click on
  it...I think there are competing handlers or something."
  "the drag elements should be on the border of the box. In the
  moment they appear directly left and right from the text."

Both reproduced headlessly (scripts/qet-gui-dialog.sh) against a fresh
build of current master and root-caused before touching anything.

Selection: DynamicElementTextItem::mousePressEvent() forwards a plain
click (no Shift) straight to parentElement()->mousePressEvent(), by
design and pre-existing -- it's what lets dragging a symbol by its own
label move the whole symbol rather than just the label. That's correct
and untouched here. But it means a plain click leaves the *parent*
selected, not the text, and #591's handles were wired only to the
text's own ItemSelectedHasChanged -- so they were only reachable via
Shift+click or a right-click's context menu (which happens to select
the item under the cursor for its own context menu, unrelated to the
Shift path), neither of which anyone reaches for to resize a text.
Confirmed with screenshots at each step, including that Shift+click
already reached the existing (if misplaced) handles correctly.

Fix: DynamicElementTextItem::refreshResizeHandlesVisibility() shows the
handles when either the text itself or its parent element is selected,
and Element gets an itemChange() override (it had none) that calls it
on each of its own texts when the element's own selection changes. Both
sides driven from itemChange(), Qt's own hook for exactly this and the
same one already used for the text's own selection.

First attempt drove this from paint() instead, since the PR's own
updateResizeHandlesPos() already runs there. That crashed reproducibly
(SIGABRT) on deselecting a text: paint() runs while QGraphicsScene
iterates its item list to draw it, and addResizeHandles()/
removeResizeHandles() mutate that list via QGraphicsScene::addItem()/
removeItem(), which cannot safely happen mid-iteration. Caught it with
the same headless repro before it went anywhere near a PR, moved the
logic to itemChange(), and re-ran the full sequence -- select, resize,
undo, deselect, twice through -- clean.

Position: updateResizeHandlesPos() placed the handles on frameRect(),
which is a box sized to the text's natural (idealWidth()) content and
then re-centred inside boundingRect() -- it does not grow with
textWidth(). Once a text has been widened, frameRect() stays tight
around the glyphs while boundingRect() -- the box QGraphicsView actually
outlines as the selection, and the box a user drags relative to -- grows
around it, leaving the handles stranded well inside the visible
selection border. Fix: position them on boundingRect() instead, which
does track textWidth(); confirmed by widening a text and checking the
handle lands exactly on the new edge rather than partway across it.

Verified headlessly end to end on the original report's own element
("motor off" on grafcet.qet, folio 1): a single plain left-click (no
Shift, no right-click) now shows both handles at the true box border;
dragging resizes correctly and the handle tracks the growing edge;
Ctrl+Z restores the -1 auto-width sentinel and the handles stay at the
reverted position; clicking away removes them; repeated twice with no
crash. Qt 6.10.2, ctest 12/12, no new warnings in either changed file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 07:30:39 +12:00
Laurent Trinques 61f5e5e502 Merge pull request #972 from arummler/fix-pugixml-linking
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m42s
fix PUGIXML linking
2026-09-21 19:36:28 +02:00
Laurent Trinques 77bc9ed8e4 Merge pull request #976 from arummler/untrack-qch
Stop tracking *.qch via LFS.
2026-09-21 19:35:48 +02:00
Andre Rummler 9b26d5dc6a Stop tracking *.qch via LFS. 2026-09-21 17:57:59 +02:00
Andre Rummler b76d8ce8a1 Fix: remove PUGIXML files from compilation fileset. This is redundant using the target approach and leads sometimes to failures. 2026-09-21 13:29:40 +02:00
Laurent Trinques d7052e396b Merge pull request #591 from ispyisail/feature-dynamictext-drag-resize
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m32s
Add drag-to-resize for dynamic element text width (#577 phase 1)
2026-09-21 12:19:22 +02:00
Laurent Trinques e123c55754 Merge pull request #969 from ispyisail/feature/qet-mcp-server
Add misc/qet-mcp: a Model Context Protocol server over QET projects
2026-09-21 12:00:34 +02:00
Laurent Trinques 268aba60eb Merge pull request #970 from ispyisail/feature/scripting-draw-api
Scripting API: draw, cross-reference and query a project
2026-09-21 11:57:48 +02:00
Laurent Trinques 313f9533a8 Merge pull request #967 from Kellermorph/fix-conductor-style
fix: inherit conductor line style (pen style) when linking cross-references
2026-09-21 11:55:05 +02:00
Laurent Trinques 3d1cb671c1 Merge pull request #966 from Kellermorph/place-makro-fix
fix: correct macro placement position mismatch
2026-09-21 09:21:34 +02:00
Laurent Trinques c0fc093b4e Merge pull request #968 from Kellermorph/background-drawing-selection
Add diagram background color picker with adaptive border/titleblock
2026-09-21 09:20:31 +02:00
ispyisail 46c35d2297 Let a script query the project database
Every structural question this API could answer, it answered by walking
live objects. The project builds a SQLite database that already knows
most of them, and nothing outside the application could reach it.

  tables()      what is queryable, tables and views
  query(sql)    rows, one object per row
  queryError()  why the last one returned nothing

This is not a new door. QElectroTech already ships a "Requête SQL
personnalisée" box in the element-query dialog where a user types
arbitrary SQL, guarded by projectDataBase::isReadOnlySelect(); query()
goes through projectDataBase::newQuery(), which applies that same rule
and returns the same rejection message. A script gets what a user
already has, and neither can write: DELETE, UPDATE and a chained
"SELECT 1; DROP TABLE" are all refused before reaching SQLite.

An empty result and a failure are told apart. query() returns no rows
for both, so queryError() carries the reason -- a refusal, or SQLite's
own message for a bad column -- and is empty when the query simply
matched nothing. Conflating those is how a silent typo in a column name
becomes "there are no such elements".

No updateDB() before querying, and that is a measured decision rather
than an omission. A script that has just edited something is the
expected caller, so a stale cache was the obvious hazard; but
projectDataBase maintains itself incrementally through addElement(),
elementInfoChanged(), addConductor() and the rest, which the undo
commands behind every edit already call. Tested both ways on the cases
most likely to go stale -- an element added and labelled, a conductor
property changed -- each queried immediately afterwards through both the
table and the view. Identical counts with the rebuild and without it,
and updateDB() repopulates every table, so calling it per query would
have been real cost for no benefit. The comment says so, so it is not
added back on the assumption it must be needed.

The views are the surface to depend on: element_nomenclature_view,
project_summary_view and wiring_list_view exist to be queried. The
tables are how the cache is arranged today and a column may move --
which is why tables() lists both and the header says which is which.

Verified against examples/industrial.qet, the largest shipped project:
618 elements counted, the busiest wire numbers ranked (0VDC 93 times,
24V2 64), and duplicate element labels found by GROUP BY ... HAVING --
V6 seven times, V5 six -- which is a design-rule question no tool here
could previously ask. Qt 6.10.2, ctest matches master.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 19:14:53 +12:00
ispyisail 2adc58c1c0 Let a script add the text and shapes a folio carries
The drawing furniture beside the circuit: a free-standing note, a line,
a rectangle, an ellipse, a polygon.

  texts()  addText()  setTextContent()  setTextColor()
           setTextRotation()  deleteText()
  shapes() addShape()  deleteShape()

Added with the same AddGraphicsObjectCommand the corresponding GUI tools
use, and changed through the plainText/color/rotation properties those
items already publish, so a script's note undoes like a hand-placed one.

These are addressed by index into a listing sorted by position, reading
order, because they have no better identity: unlike an element they carry
no uuid, and unlike a conductor they have no terminal to be named by.
Position is what they have and it persists, so the ordering survives a
save and reload -- verified by listing before and after, including a
rotated text whose bounding box moves. It does not survive adding or
deleting one: indexes after that point shift the way a list's do, which
is why texts() and shapes() exist rather than a caller keeping a handle.

The sort is on sceneBoundingRect(), not pos(). A QetShapeItem keeps its
geometry in its line/rect/polygon and leaves pos() at the origin, so
sorting on pos() put three shapes drawn in three different places all at
(0, 0) and made every shape index refer to whichever the set yielded
first -- which is what the first version of this did, and the test that
caught it was asking for three shapes and getting index 0 three times.

Path is deliberately not offered: it is built by successive clicks and
has no two-point form to give here.

Verified headlessly: three texts added bottom-up and listed in reading
order, edited, recoloured, rotated, one deleted; three shapes added,
listed with their real geometry, the middle one deleted and the right one
gone; unknown shape name, invalid colour and out-of-range index all
decline with a reason. Saved, reloaded, both listings identical.

Qt 6.10.2, build clean, ctest matches master, qet-lint clean on the
generated project, qet-coherence-check clean on the example corpus.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 19:00:04 +12:00
ispyisail 82262c5980 Let a script number a conductor and link a cross-reference
Two gaps left over from the drawing verbs. A script could create a
conductor but not say what it was -- no number, colour, section or
formula -- and could not link a master to its slave, although
LinkElementCommand has been there all along and nothing bound it.

  conductors()            what is on this folio, and how to address it
  conductorProperty()
  setConductorProperty()  num, formula, function, bus, cable,
                          tension_protocol, conductor_color,
                          conductor_section, color, text_color
  elementLinkType()       simple / master / slave / next_report / ...
  linkedElements()
  linkElements()          two folio indices: a master and its slave are
                          normally on different folios
  unlinkElement()

The property names are the ones the .qet file uses for the same fields,
so what a script sets is what a reader of the file sees rather than a
third spelling invented here.

A property is applied to every conductor of the same electrical
potential, not to the one conductor named. That is the rule the
application already follows -- SearchAndReplaceWorker pushes one
QPropertyUndoCommand per conductor of relatedPotentialConductors()
inside a macro -- because a wire number describes a potential, not one
drawn segment; setting it on one and leaving the rest of the potential
disagreeing would produce a file no GUI action could have produced.

Linking asks LinkElementCommand::isLinkable() rather than re-deriving
its rules, so a script cannot make a link the GUI would refuse: master
to master, a PLC master to a non-PLC slave, a next-report to another
next-report, or anything to an already-taken target.

A conductor is addressed as "the conductor on terminal i of element U".
It has no identity of its own to use instead: conductors carry no
persisted uuid, and the terminal1/terminal2 ids in the file are
folio-scoped integers QElectroTech renumbers on every save. Since the
change is potential-wide, any terminal of the potential names it equally
well, so in practice a potential is addressed from one of its leaves; a
terminal carrying several conductors names none of them and is refused
rather than guessed at.

Verified headlessly. Conductor: num, section and colour set from one end
of a potential and read back from the other, saved and reloaded, present
in the XML. Propagation shown to discriminate, which took two tries --
the first attempt wired A.0-B.0 and B.1-C.0 and saw no propagation,
correctly, because a coil's two terminals are opposite ends of the coil
and not one potential. Wiring a real hub at A.0 instead, a number set
via the B leaf appears on the C conductor too, in memory and in the
saved file. Cross-reference: a master on one folio linked to a slave on
another, linkedElements() agreeing from both ends, surviving save and
reload with link_uuid written on both folios; master-to-master,
self-link, unlink and relink all behave. Unknown property, invalid
colour, bare terminal and ambiguous terminal all decline with a reason.

Qt 6.10.2, build clean, ctest matches master, qet-coherence-check clean
on the example corpus, qet-lint clean on the generated projects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 18:48:40 +12:00
ispyisail 6dcb6a2a8f Key a conductor by something that survives a save
qet_diff keyed each conductor on its raw terminal1/terminal2 pair, with
a comment claiming that pair was "stable within a folio". It is stable
within a folio; it is not stable across a save. QElectroTech reassigns
those folio-scoped integer ids on every write, in whatever order it
serialises the elements, so one untouched conductor of ArduinoLCD.qet
goes from terminal1="1" terminal2="16" to terminal1="34" terminal2="15".

Diffing a project against a re-saved copy of itself therefore reported
29 of its 47 conductors as removed and 29 as added, with nothing
changed. That is the main thing this tool is for, so the conductor half
of the answer was noise in exactly the case it was wanted.

The format has two addressing schemes and a file can hold both at once.
Older conductors use the integer ids with no element1/element2; current
ones use terminal uuids from the .elmt definition plus element1/element2
naming the placed instances. A terminal uuid alone is not an identity --
it belongs to the definition, so two coils of one type share it and a
conductor between them keys as a self-loop -- so an end is identified by
the (instance, terminal) pair, taken from the conductor where it carries
one and resolved through the folio's elements where it does not.

Where an element predates persisted uuids there is nothing stable to key
on. Keying those on terminal geometry alone collapsed nine distinct
conductors of schema_indus.qet onto a single key, which is worse than
the instability it was meant to fix, so such ends stay unresolved, keep
a "#"-marked key, and the diff reports unstable_keys and says in words
that added/removed may not mean what they look like.

Measured over the 24 shipped example projects, 3190 conductors: 0
colliding keys, against 8 for the geometry-only key. On a re-saved but
otherwise untouched project: 0 added, 0 removed, against 29 and 29
before this change. A project with two conductors genuinely added still
reports exactly two added and none removed, so the check still
discriminates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 18:38:46 +12:00
ispyisail c740cdf1ac Let a script wire, label and rotate, not only place
The scripting API (bugtracker #162) could place an element and move it,
and could count conductors but not make one. So a script could put a
coil and a motor on a folio and had no way to connect them, which is
most of what drawing is. This adds the missing verbs:

  addConductor()      wire terminal i of one element to terminal j of another
  rotateElement()
  setElementInfo()    any information key
  setElementLabel()   the label key, by name, since it is the one people want
  addFolio()
  setFolioTitle()
  elementUuids()      what is on this folio
  elementName()
  elementTerminals()  which terminal index is which, before wiring it

Each goes through the command the GUI already uses, so a script's edits
undo like manual ones and reach the project database the same way:
ConductorCreator (the drag-a-rectangle-over-terminals path, which is
what makes a new conductor inherit an existing potential's properties
and join auto-numbering), ChangeElementInformationCommand,
QETProject::addNewDiagram(), ChangeTitleBlockCommand. rotateElement()
pushes the same QPropertyUndoCommand on "rotation" that
RotateSelectionCommand pushes for an Element, rather than
RotateSelectionCommand itself, which works on the diagram's selection
and would mean rewriting the user's selection to rotate one element.

Terminals are addressed by index, not uuid. Terminal::uuid() is a
property of the catalog .elmt definition: empty for most of the
installed base, and where present, identical across every instance of
that element -- two coils of the same type placed side by side have
byte-identical terminal uuids, so a uuid cannot say which coil's A1 is
meant. elementTerminals() exists so a script can see the indexing
instead of guessing it.

The one real hazard is that ConductorCreator asks the user which
potential to inherit from when the two terminals sit on two different
existing ones, and it asks with a plain modal QDialog that
QET::QetMessageBox's non-interactive mode does not cover -- so under
headless --run there is nobody to answer and the call never returns.
Measured: with the check removed, that one call hangs until killed;
with it, it declines in 0.4 s. addConductor() therefore refuses that
case, the same way and for the same reason addElement() already refuses
the import-conflict dialog.

To make that check without duplicating the condition, existingPotential()
becomes static over an explicit terminal list and ConductorCreator gains
a public needsPotentialChoice() predicate. Behaviour of the GUI path is
unchanged; setUpPropertieToUse() passes m_terminals_list to the same code
it called before.

Verified headlessly against a copy of examples/ArduinoLCD.qet: new folio
titled, two coils placed, wired, labelled, an info key set and the
element rotated; saved, reloaded, and the conductor, label, title and
rotation (persisted as orientation="1") all read back. Re-saving the
result is byte-identical. qet-lint clean on the generated project;
qet-coherence-check clean on it and on the 24-project example corpus,
and shown to report 9 findings on a deliberately broken copy of the same
file, so the clean result discriminates. Qt 6.10.2, ctest identical to
master (the 61 failures are the vendored KDE ECM suite, present on both).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 17:57:29 +12:00
elevatormind 78caeaec48 Merge branch 'qelectrotech:master' into no-sqlite 2026-09-21 07:34:32 +02:00
ispyisail aab0a18506 Add misc/qet-mcp: a Model Context Protocol server over QET projects
A small stdio MCP server that lets an assistant read a project, ask what
an edit actually changed, and sweep a corpus. Standard library only --
Python 3.9+, no third-party dependencies, and the MCP SDK is not
required. Nothing in the build or the application refers to it; it sits
in misc/ beside make_icon_themes.py and is inert unless run.

It exists because verifying a change by screenshot is unreliable, and
that unreliability produced two wrong conclusions in a single review
session. A drag of a multi-element selection looked like it had left the
symbols behind and detached their labels; diffing the saved file showed
all four elements had moved by an identical (0,-80) and no label had
moved at all. An Apply button looked like it did nothing; it was
disabled because a required field was empty. Both times the pixels
misled and the file told the truth, so these tools read the file.

Seven tools: qet_project_info, qet_elements, qet_conductors, qet_diff,
qet_scan, qet_element_info and qet_export. Only qet_export launches
QElectroTech; everything else parses the .qet or .elmt directly, which
needs no display and cannot be confused by a dialog.

Two behaviours of QElectroTech are carried inside the tool rather than
left for the caller to rediscover. SingleApplication keys its socket on
applicationFilePath(), so a second launch of the same path forwards its
request to a running instance and returns that process's answer with no
error; qet_export therefore copies the binary to a unique temporary
path, gives it a private HOME and runs it offscreen. A symlink would not
do, because applicationFilePath() resolves it back. And the CLI matches
its export flags by exact string (cli_export.cpp:828) with the project
and output as positional arguments (:862, :882), so --export-bom=out.csv
is not recognised as an export at all and the run starts the interface
and hangs headless; the tool uses the positional form.

Worth recording for anyone extending this: the project database would be
a better query surface than the XML, but it is not reachable from
outside the application. projectDataBase::newQuery() and
isReadOnlySelect() are C++-internal and the JavaScript scripting API
exposes no SQL binding. A --query CLI verb, or a scripting binding,
would let this expose the guarded read-only SELECT surface instead.

Verified against the shipped examples: qet_scan reports 3190 conductors
across the 24 example projects with no cable value, and qet_diff
reproduces the four-element move above from the two saved files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 17:33:17 +12: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
Kellermorph 45ca53c7a8 fix: inherit conductor line style (pen style) when linking cross-references
ApplyForEqualAttributes() was missing the 'style' attribute, causing
dashed/dash-dotted line styles to be lost when potentials are merged
via folio reports. Only color and other properties were copied.

Add style copy in single-element case and equality check in
multi-element case, matching the existing pattern for other attributes.
2026-09-20 21:56:43 +02:00
Kellermorph 2efce1752d fix: correct macro placement position mismatch
Fix macro elements jumping to upper-left corner instead of being placed
at the correct drop position.

Root cause: The preview offset used itemsBoundingRect() (all items
including children), while Diagram::fromXml() computed its translation
offset from top-level items only. This mismatch caused fromXml to
translate elements to the wrong position.

Changes:
- Compute top-level-only bounding rect in dummy diagram constructor
  to get the correct m_items_top_left reference point
- Pass final_pos + m_items_top_left to fromXml() so the internal
  translation yields the intended final position
- Add braces around single-statement for-loop in fromXml
- Remove empty else block leftovers from debug cleanup
2026-09-20 21:24:38 +02:00
Magnus Hellströmer 27dea3ffab refactor: use VACUUM INTO for database export
Remove the native SQLite backup API and direct SQLite library
dependency.
2026-09-20 20:49:02 +02:00
Laurent Trinques c256e2dd1a Merge pull request #958 from bhangart/fix/pin-fetchcontent-dependencies
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m37s
Fix/pin fetchcontent dependencies
2026-09-20 20:13:37 +02:00
Laurent Trinques 8e3a7a6ab6 Merge pull request #927 from ispyisail/fix/923-snap-element-text
Snap a device's text to the grid when it is dragged (#923)
2026-09-20 20:10:13 +02:00
Laurent Trinques 740271c993 Merge pull request #938 from arummler/fix-translation-syntax
Fix various singular/plural cosntructions
2026-09-20 20:08:53 +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
Laurent Trinques 268e9c7bd9 Merge pull request #959 from jp2images/fix-dark-canvas-item-updates
Keep the dark canvas on QGraphicsView's own update path
2026-09-20 20:02:50 +02:00
Laurent Trinques 264a0f677f Merge pull request #964 from jp2images/remove-stray-sbom-files
Remove two files that slipped into #944
2026-09-20 20:02:27 +02:00
Laurent Trinques 9ccfb5e116 Merge pull request #963 from jp2images/fix-hover-tint-light-face
Darken the hover ink on a light button face
2026-09-20 19:45:28 +02:00
Jeff Patterson 04463f0954 Darken the hover ink on a light button face
QETStyle::hoverColor() lightened the highlight color until it read at
3.5:1 against the Light role. On a dark face that is the way to go; on a
light face lightening only fades the ink, so with a pale platform accent
that QET keeps (macOS's green selection color, black selection text) the
loop ran to white and every hovered line-art icon vanished. The ink now
moves away from the face, darker on a light face, lighter on a dark one,
and falls back to the button text color if twenty steps are not enough.

The hover test gets a row with that accent on each palette, and a new
test sweeps accents across hues and lightness on both palettes and
requires the hover ink to read at 3:1 on the face.

Fixes #962
2026-09-20 12:45:07 -05:00
Laurent Trinques 3245919c19 Merge pull request #961 from jp2images/fix-small-page-icons
Give every configuration page an icon at page size
2026-09-20 19:44:00 +02:00
Jeff Patterson 55ad2eccbf Remove two files that slipped into #944
sources/qetsbom.cpp and sources/qetsbom.h were untracked local files
that a directory-wide add swept into the rebuilt #944 commit. Nothing
references them; the build does not compile them.
2026-09-20 12:43:57 -05:00