9337 Commits

Author SHA1 Message Date
ispyisail bee25a4ca9 Merge pull request #661 from ispyisail/fix-cli-modal-dialog-hang
Fix command-line tools hanging forever on a modal message box
2026-09-11 07:10:15 +12:00
Laurent Trinques 6d09041dce Merge pull request #831 from ispyisail/feature/advisory-slave-limit
Make the slave limit advisory rather than a refusal
2026-09-10 14:50:56 +02:00
ispyisail 1a12d440b2 Merge pull request #834 from ispyisail/fix/qlabel-pixmap-deprecation
Use QLabel::pixmap()'s non-deprecated by-value form on Qt5
2026-09-10 16:51:55 +12:00
ispyisail 85f46f2b48 Use QLabel::pixmap()'s non-deprecated by-value form on Qt5
#824 read the pixmap through the pointer overload, which Qt 5.15
deprecates, so the fix it introduced compiled with two deprecation
warnings of its own. Qt 5.15 offers the by-value form behind
Qt::ReturnByValue, so both branches can take the same overload and the
difference reduces to the argument.

Equivalent: the pointer overload returns nullptr when no pixmap is set,
which the old expression turned into a null QPixmap; pixmap(
Qt::ReturnByValue) returns a null QPixmap directly. It also drops the
null check, so the Qt5 branch is now a single expression.

Verified both arms of the #if, since a preprocessor-branched change is
only half tested otherwise:

 - Qt 5.15.18: deprecation warnings for this file 2 -> 0, builds clean,
   binary runs
 - Qt 6.10.2: builds clean, 488/488, links
 - 22 example projects load and export with no crash or hang

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 16:50:49 +12:00
Laurent Trinques 9637420756 Update Links to Doygen documentation and qch file 2026-09-10 05:27:57 +02:00
Laurent Trinques 6c68c461b1 Set Doxyfile version to 0.200.1 2026-09-10 05:24:31 +02:00
Laurent Trinques c830101ba6 Update CMakeLists.txt set version to 0.200.1 2026-09-10 02:33:31 +02:00
Laurent Trinques a1c0907811 Set QT_VERSION_MAJOR to 6
https://github.com/qelectrotech/qelectrotech-source-mirror/pull/824#issuecomment-5610090520
2026-09-10 02:30:53 +02:00
ispyisail ffd829bb69 Make the slave limit advisory rather than a refusal
max_slaves records how many contacts a part is expected to carry. It was
enforced as a rule the drawing had to obey, which obstructs the way both
@scorpio810 and @IBSYSLevi described working in #819: draw the schematic
first, choose the physical hardware afterwards. A limit that refuses the
link forces the hardware decision up front, which is exactly what they
said gets in the way.

Two changes, both in the UI rather than in isFull(), which stays the
query it always was:

 - MasterPropertiesWidget::on_link_button_clicked() now says the limit
   is reached and asks whether to link anyway, defaulting to yes,
   instead of refusing outright.

 - LinkSingleElementWidget no longer removes a full master from the
   candidate list. That was the worse half: a master at its limit simply
   was not there, indistinguishable from one that does not exist, with
   nothing to say why. It now stays selectable and the user decides.

PLC masters are deliberately left alone. Their limit is the number of
declared IO slots, which is structural rather than advisory -- a link
past it would have no IO index to map to -- and PlcLinkWidget already
tells the user when it hides one, via m_hidden_masters_label.

Only coils that opt into a limit are affected: max_slaves defaults to
-1, and no project in examples/ sets it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 11:54:52 +12:00
Laurent Trinques 957bbe5edb macOS: drop obsolete lang1/ copy step
lang1/ was a leftover from the pre-Qt6 translation pipeline.
Qt6/CMake now produces all .qm files directly into lang/, which
is already copied above, making this step dead code.
2026-09-09 16:02:02 +02:00
Laurent Trinques ce0ba4a681 Merge pull request #829 from jp2images/fix-macos-bundle-identifier
Set a bundle identifier so macOS file dialogs work
2026-09-09 15:57:21 +02:00
Laurent Trinques 8295083f05 misc: align macOS bundle identifier with app's settings domain
CFBundleIdentifier was "org.qelectrotech", but Qt derives
"org.qelectrotech.QElectroTech" from setOrganizationDomain()
and setApplicationName() for the app's own preferences file
(~/Library/Preferences/org.qelectrotech.QElectroTech.plist).

Align the two so the shipped bundle and the CMake target (see
CMakeLists.txt MACOSX_BUNDLE_GUI_IDENTIFIER) use the same
identifier regardless of build path.

Note: this changes the bundle's LaunchServices identity, so
users may need to redo "Open With QElectroTech" file
associations once after updating.
2026-09-09 15:54:28 +02:00
Jeff Patterson a2441a6f81 Set a bundle identifier so macOS file dialogs work
CMakeLists.txt marks the macOS target as MACOSX_BUNDLE but never sets
MACOSX_BUNDLE_GUI_IDENTIFIER, so CMake's default Info.plist template
substitutes an empty string for CFBundleIdentifier.

An .app with an empty identifier is never registered by LaunchServices
(`lsappinfo info` reports bundleID="" and bundle path=[NULL]). AppKit
runs the open/save panel in an XPC service keyed on the client's bundle
identifier: the service is spawned on each request but presents no
window, so QFileDialog::getOpenFileName() and getSaveFileName() return
an empty string without a panel ever appearing. In QET this means
File > Open and File > Save as silently do nothing -- openProject()
receives an empty path and returns at its `if (filepath.isEmpty())`
guard. Every macOS CMake build has been affected since the target
became a bundle.

Fill in the identifier along with the other bundle metadata CMake's
template expects. org.qelectrotech.QElectroTech is the identifier Qt
already derives from setOrganizationDomain("qelectrotech.org") and
setApplicationName("QElectroTech") for the app's own preferences file,
so the bundle now agrees with what the app writes at runtime.

Verified on macOS 27 with Qt 6.11: before the change File > Open and
File > Save as present nothing; after it both panels open normally. No
code signing step is needed -- the linker's ad-hoc signature still
reports the executable name as its identifier, and the panels work
regardless once the plist is correct.
2026-09-09 05:16:50 -05:00
Laurent Trinques c1f9af8544 Merge pull request #822 from enesgursoy6110/fix/report-link-picker
Show folio identity first in report link picker
2026-09-09 07:49:42 +02:00
Laurent Trinques 033c2f93a8 Merge pull request #827 from ispyisail/feature/per-type-contact-budget
Show used against declared capacity where a master declares contact groups
2026-09-09 07:29:39 +02:00
Laurent Trinques 83623a0fa6 Merge pull request #826 from ispyisail/feature/general-tab-contact-count
Show the NO/NC/SW contact breakdown in a master's General tab
2026-09-09 07:29:07 +02:00
Laurent Trinques c85f80bbcf Merge pull request #825 from ispyisail/fix/contact-usage-counting-v2
Count master contacts in one place, and count contacts not elements
2026-09-09 07:28:15 +02:00
ispyisail c6995a0e7e Take the slot count from the contact groups when an element declares them
An element can declare contact groups and a max_slaves that disagree with
each other, and nothing reconciles them.

The element editor keeps the two in step: max_slaves sizes the contact
group table, one row per slot. Nothing does so on load, so a hand
written or generated file can carry five groups and max_slaves=2. That
loads without complaint, isFull() then caps linking at two, and
ContactGroupSelectionDialog still offers all five groups -- so the user
is shown groups that cannot be linked to, with nothing to explain why.

When groups are declared they are the slots: a slave occupies exactly
one, and the selection dialog offers exactly these. So take the limit
from the group count, which is also the number the user can see.
max_slaves stays as the fallback for the elements that declare no
groups, which today is every element in the standard collection.

No element in the collection declares contact groups, so this changes
nothing for existing projects.

Verified with two purpose-built fixtures, since no real element
exercises either path: a coil declaring five groups with max_slaves=2
now takes the limit from the groups, and a coil with max_slaves and no
groups still takes the fallback path unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 14:41:11 +12:00
ispyisail f18eda845b Merge branch 'feature/general-tab-contact-count' into feature/per-type-contact-budget
# Conflicts:
#	sources/ui/elementpropertieswidget.cpp
2026-09-09 11:47:26 +12:00
ispyisail 73e9473db4 Keep the slave total in slots, and label the breakdown as contacts
Two units were being stacked in the same block. The line above reports
max_slaves, which is a number of slots, so reporting the line below in
contacts made a coil with one 4 pole slave read "maximum 4 / used 4"
while three slots were still free.

The total goes back to counting linked elements, matching the unit of
the line above it and restoring the original behaviour of that line.

The per-type breakdown keeps the pole multiplier, because that is the
question it answers -- how many contacts an auxiliary block must
provide -- and is now prefixed "Contacts :" so the two units are not
mistaken for each other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 11:46:34 +12:00
ispyisail 7307128fc3 Merge branch 'fix/contact-usage-counting-v2' into feature/general-tab-contact-count 2026-09-09 11:45:49 +12:00
ispyisail 799ff5573f Revert the isFull() change: max_slaves counts slots, not contacts
The earlier commit changed MasterElement::isFull() to compare the
contacts in use against max_slaves. That was wrong, and this restores
the original comparison against the number of linked elements.

max_slaves is a number of slots, not of contacts:

 - it sizes the contact group table in the element editor, one row per
   slot (ElementPropertiesEditorWidget::populateSlaveGroupsTable)
 - a group must match the slave's own contact count before it can be
   chosen, so a 4 pole slave needs a group declaring 4 and occupies
   that single group (ContactGroupSelectionDialog)
 - each slave stores exactly one group index
   (Element::setGroupIndexForElement)

So a coil declaring 4 slots accepts 4 slaves, whatever their pole
count. Counting contacts made one 4 pole slave fill a 4 slot coil on
its own and refuse three further links that should have been allowed.

ContactUsage stays, and its per-type tally is still what the General
tab needs: how many contacts an auxiliary block must provide is a
different question from how many slots are occupied, and only the
former wants the pole multiplier. The header now says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 11:45:36 +12:00
ispyisail 6f2c66afef Show used against declared capacity where a master declares contact groups
Second half of #819: where a coil declares what contacts it provides, the
General tab now reports each type as used against declared rather than as
a bare count.

    NO : 3/4, NC : 1/2, inverseurs : 0/1, autres : 0/0

MasterElement::contactCapacity() sums contactCount over the element's
SlaveContactGroup list, per type, reusing the same ContactUsage tally the
used count is built on. The mapping from ElementData::SlaveState onto the
tally's own type is factored into one helper so the used count and the
declared capacity cannot classify a contact differently.

Falls back to the plain count from the previous commit when an element
declares no groups, which is every element in the standard collection
today -- nothing in the corpus declares slaveContactGroups, so this
changes no existing display.

A type used beyond what is declared reads as e.g. "1/0". That is
deliberate: it says this contact does not fit the part.

Display only. Whether a declared capacity should also feed
MasterElement::isFull() is the open question in #819 and is not touched
here.

Verified end to end against a purpose-built fixture, since no existing
element exercises this path: a coil declaring two NO groups of two, one
NC group of two and one changeover group of one parses and reports
NO=4 NC=2 SW=1 other=0 total=7, matching the declaration exactly.
tst_contactusage gains a case covering capacity summed across groups
(10 cases, all passing).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 10:49:16 +12:00
ispyisail c39c7414cf Show the NO/NC/SW contact breakdown in a master's General tab
Requested in #819: after drawing a schematic you need to know how many
NO, NC and changeover contacts a coil ended up using, so you can pick an
auxiliary block that satisfies it. Until now the General tab reported
only a single total, and counting the contacts by type meant counting
rows on the cross reference by hand.

Three changes to that block:

 - the used count now counts contacts rather than linked elements. The
   label already said "contacts" while the value was
   linkedElements().count(), so a slave standing for several contacts
   was under-reported. It reads MasterElement::contactUsage(), the
   same count isFull() uses.

 - a breakdown line is added below it, printed only when the master
   actually has contacts to break down.

 - a declared limit of -1 means "no limit set" rather than a real
   limit, so it is printed as such instead of showing "-1", which
   reads as a bad value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 10:36:25 +12:00
ispyisail c33f250910 Count master contacts in one place, and count contacts not elements
MasterElement::isFull() decided whether a coil had room left with

    connected_elements.size() >= max_slaves

which counts linked *elements*. A slave stands for as many contacts as
its "number" kind information declares, so a 4 pole contact consumed a
single contact from the coil's budget instead of four. 36 elements in
the standard collection declare a number between 2 and 4, so this is
reachable, not theoretical.

Add ContactUsage, a header-only tally holding the two rules that are
easy to get wrong:

 - a slave counts once per contact it declares, not once per element
 - a changeover is counted once, as sw, and never as one NO plus one
   NC. CrossRefItem::NOElements() and NCElements() both return
   changeovers, so a count built by adding those two lists together
   reports one changeover as two contacts.

The upcoming per-type displays (the used count in the element's General
tab, and the per-type budget on the cross reference) need exactly this
count, so it lives in one place rather than being written out three
times, and isFull() now reads it too.

The header carries no graphics dependency, so the counting rules are
unit tested on their own in tests/qttest/tst_contactusage.cpp,
following the same pattern as diagramsortkeys.h.

Verified: all 9 unit tests pass, and both rules were mutation checked
(counting elements instead of contacts fails 2 tests, counting a
changeover as both NO and NC fails 3). The 23 example projects still
load and export without crash or hang, and qet-lint reports no
regressions against its baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 10:08:00 +12:00
ispyisail c265f0206c Fix Qt5 build: QLabel::pixmap() returns a pointer, not a value
ClickableImageLabel::mousePressEvent() calls pixmap().isNull() and
pixmap().width(). That is the Qt6 signature; in Qt5 QLabel::pixmap()
returns const QPixmap * and the code does not compile:

  error: request for member 'isNull' in '...QLabel::pixmap()',
  which is of pointer type 'const QPixmap*'

CMakeLists.txt defaults QT_VERSION_MAJOR to 5 when it is not specified,
so a default configuration of master has not built since 6b577ee75.

Read the pixmap once into a local, guarded the way the rest of the
codebase handles this split, which also drops four repeated pixmap()
calls in the same expression.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 09:52:06 +12:00
enesgursoy6110 cfa1e3ed27 Scope shortcut conflict detection by editor 2026-09-08 21:02:46 +03:00
enesgursoy6110 36dd1624d2 Show folio identity first in report link picker 2026-09-08 21:02:46 +03:00
Laurent Trinques a583b3c43c Merge pull request #688 from IBSYSLevi/fix/saving-coil-master
Fix: saving coil master
2026-09-08 08:35:00 +02:00
Laurent Trinques 1738c3ad6c Merge pull request #771 from Kellermorph/spinbox-crossref
Add configurable distance between label and slave XRef
2026-09-08 08:20:33 +02:00
Laurent Trinques 82026d8f7c Merge pull request #818 from qelectrotech/revert-804-feature/terminal-potential-grouping
Revert "Feature: Terminal potential grouping"
2026-09-07 15:47:08 +02:00
Laurent Trinques 1793dcb9a8 Revert "Feature: Terminal potential grouping" 2026-09-07 15:44:12 +02:00
Laurent Trinques cc7aaf3538 Merge pull request #804 from IBSYSLevi/feature/terminal-potential-grouping
Feature: Terminal potential grouping
2026-09-07 15:08:00 +02:00
Laurent Trinques a6285c7ef7 Merge pull request #815 from ispyisail/test/diagramsortkeys-unit-test
Add unit test locking in Diagram::toXml() coordinate sort order
2026-09-07 14:31:26 +02:00
Laurent Trinques f87381a910 Merge pull request #817 from arummler/feature-graphics-part-2
Fixes and embelishment for some of the graphics and image features
2026-09-07 14:29:24 +02:00
ispyisail bd5cff4211 Add a unit test locking in Diagram::toXml()'s coordinate sort order
Follow-up to the positionKey() fix merged directly in #779
(b2f4ef5d2): per review request, add a small regression test so this
class of bug (fixed-precision "%.4f" formatting compares out of
numeric order once the integer part's digit count differs) can't
silently reappear.

positionKey()/coordinateKey() move out of diagram.cpp's anonymous
namespace into a small header-only diagramsortkeys.h so the test can
link against the exact same code Diagram::toXml() uses, instead of
duplicating the algorithm. Behavior is unchanged.

tst_diagramsortkeys covers: single- vs double-digit, double- vs
triple-digit, negative-vs-negative, negative-vs-positive, and
negative-vs-zero coordinate pairs, plus sub-precision deltas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 20:54:18 +12:00
Andre Rummler 296f142036 Adding icons to graphs and image operations. 2026-09-07 10:44:30 +02:00
Andre Rummler 4d991d0419 Conversion of ellipses and rounded rectangles to bezier curves instead of polylines. 2026-09-07 10:44:30 +02:00
Andre Rummler a85bd6a49b Fixing undo operations of graphs and images. Changing undo names. 2026-09-07 10:44:30 +02:00
Andre Rummler ef8e41a5c3 Fix transparency export (not possible for jpeg). 2026-09-07 10:44:30 +02:00
Andre Rummler 6f80da66d5 Connected to previous image handling updates: images were not exported at all to dxf. In this
update instead of the image a placeholder box is drawn and a warning is given during export.

Current dxf version is extremely old and does not support any image embedding. Future possible
update is the introduction of a newer dxf version export option which would allow to embed a link to an external
image file.
2026-09-07 10:44:30 +02:00
Andre Rummler a065867502 Fix issue that made it impossible to remove all transparency colours defined in a previously opened dialog. On the last color the OK button was greyed out. 2026-09-07 10:44:29 +02:00
Laurent Trinques b98d41357a Merge pull request #814 from ispyisail/fix/diagram-toxml-deterministic-order
Fix: positionKey() didn't sort coordinates numerically
2026-09-07 10:30:48 +02:00
ispyisail b2f4ef5d25 Fix: positionKey() didn't sort coordinates numerically
Plain fixed-precision formatting ("%.4f") produces strings that don't
compare in numeric order once the integer part has a different digit
count -- e.g. "15.0000" sorts before "5.0000" as text, even though
15 > 5. That silently broke the determinism goal of this branch for
any diagram with coordinates spanning more than one digit width.

Shift into a non-negative range and zero-pad to a fixed width instead,
so the formatted string sorts the same way the number does, including
negative values.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 20:13:05 +12:00
Laurent Trinques bd8470afa8 Merge pull request #787 from ispyisail/fix/removediagram-cascade-delete
Cascade-delete a diagram's element/terminal/conductor rows on removal
2026-09-07 10:05:27 +02:00
ispyisail 7c5f8c6a4b Fix: roll back removeDiagram cascade delete on query failure
Each cascade delete (element_info/terminal/conductor/element) only
logged its error and fell through to the next query regardless, so a
mid-cascade failure (e.g. a locked DB) still let the diagram row get
deleted while its child rows survived -- the same inconsistency this
branch set out to fix, just via a different failure path. Wrap the
cascade in a transaction and roll back + bail on the first failed
exec(), matching the existing transaction pattern in updateDB().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 20:02:38 +12:00
Laurent Trinques 6b27043f33 Merge pull request #779 from ispyisail/fix/diagram-toxml-deterministic-order
Make Diagram::toXml() serialize elements and conductors deterministically
2026-09-07 09:38:28 +02:00
Laurent Trinques ed5a510592 Merge pull request #778 from ispyisail/fix/qpropertyundocommand-undo-symmetry
Give QPropertyUndoCommand::undo() its own first-time grace period
2026-09-07 01:09:44 +02:00
Laurent Trinques 3abf187688 Merge pull request #813 from jp2images/fix-element-editor-mirror-in-place
Element editor: mirror and flip the selection in place instead of across the origin
2026-09-06 14:38:15 +02:00
Jeff Patterson 825a1a594d Fix element editor Mirror and Flip to reflect the selection in place
Mirror (M) and Flip (F) in the element editor reflected the selected
parts across the element origin: every part's mirror() and flip()
negated the scene x or y coordinate. A part drawn to the right of the
origin landed the same distance to the left, so the selection jumped
to the other side of the canvas instead of turning around.

MirrorElementsCommand and FlipElementsCommand now compute the united
scene bounding rectangle of the selected items and reflect across the
vertical or horizontal line through its center. The center is snapped
to the nearest half of the diagram grid, so points that were on the
grid stay on the grid after the reflection. Terminals in particular
keep their grid alignment.

Each part's mirror() and flip() takes the axis coordinate as a
parameter with a default of 0, so the previous behavior remains
available to any other caller. The command stores the axis when it is
created and undo reapplies the same reflection, which is its own
inverse, so the existing undo path is unchanged.

Parts covered: PartArc, PartDynamicTextField, PartEllipse, PartLine,
PartPolygon, PartRectangle, PartTerminal, PartText.

Fixes #812
2026-09-06 06:48:45 -05:00