Commit Graph

9096 Commits

Author SHA1 Message Date
ispyisail 73250bd746 Do not rebuild the project database while destroying the project
Destroying a project cost more than loading it: on a 1000 folio project
--info reported its work done in 160 s but the process ran for 657 s, and
the difference was ~QETProject().

Timing each destructor puts 94 % of that teardown in
~QetGraphicsTableItem(), with the cost per table doubling as the project
grows (48 ms at 100 folios, 122 ms at 250). The database's own per-element
deletes are 1 % of it and linear; element and conductor teardown is linear.

A table destructor repairs the chain it belonged to, which relinks the
neighbouring tables, which assigns a model -- and one branch of
setPreviousTable() builds a fresh ProjectDBModel, whose copy constructor
calls setQuery(), which rebuilds the whole database. Destroying a 250 folio
project did that 12 times, for a project that is being thrown away.

So block the rebuild for the lifetime of the destructor, next to the
blockSignals(true) already there for the same reason. Nothing can observe
the result: the database is destroyed moments later as a member of the
project. Teardown drops about fivefold at every size measured -- 1.30 s to
0.26 s at 100 folios, 7.75 s to 1.73 s at 250, 19.92 s to 4.02 s at 400 --
and the number of full rebuilds in a run stops growing with project size.

Teardown is still superlinear, now dominated by
QetGraphicsTableItem::setUpColumnAndRowMinimumSize() measuring every cell of
the nomenclature each time a chain is relinked. That is left alone here.

--info stays byte identical on all 23 example projects, as do --export-bom,
--export-wires, --export-cables, --export-nets and --export-wiring.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6MRq2Ach1ogvnGcbuqNLr
2026-09-12 18:32:14 +12:00
ispyisail 956836458a Rebuild the project database once per load instead of once per table model
ProjectDBModel::setQuery() calls projectDataBase::updateDB(), which drops
and repopulates every table in the database. The rebuild does not depend on
the query, so each table model that queries the database while a project is
being read triggers another complete repopulate of the same content.
Opening a 100 folio project ran updateDB() 26 times, 9.9 s of a 15.7 s load.

Two changes, because the first alone is not enough:

setUpdateBlocked() lets a bulk operation suppress the rebuild and do it
once when it is done. readProjectXml() already wrapped the load in
blockSignals(true) "to avoid hundreds of unnecessary emitted signal", but
that suppresses only the signal, not the work it announces; this extends
the same intent to the work. Both early returns in readProjectXml() sit
before the block, so no path leaves the database permanently blocked.

Further rebuilds are triggered after readProjectXml() returns, where the
load phase timers cannot see them -- with only the block in place
updateDB() still ran 5 times on examples/industrial.qet. So the database
now also tracks whether anything has changed since the last rebuild, and
skips repopulating when nothing has. dataBaseUpdated() is still emitted in
that case: callers and models rely on it to refresh, and what they read
back is the same either way. Every method of the class that writes rows
marks the flag; from outside, the database is reachable only through
newQuery(), and all five call sites read.

Repeating the rebuild was wasteful rather than wrong -- each
populate*Table() begins with a DELETE -- so this changes no output.
Verified byte identical --info on all 23 example projects, and identical
--export-bom, --export-wires, --export-cables, --export-nets and
--export-wiring on industrial.qet. Its load drops from 5.51 s to 5.31 s
(median of 6).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6MRq2Ach1ogvnGcbuqNLr
2026-09-12 18:32:14 +12:00
Laurent Trinques 2c88f78f69 Add new Shelly example Single-Phase And Three-Phase Designs 2026-09-12 01:13:30 +02:00
Laurent Trinques f85ee19fc0 Merge pull request #830 from enesgursoy6110/feature/smart-device-bom
Add smart device metadata to the existing BOM export
2026-09-11 23:25:04 +02:00
Laurent Trinques 2f415117f7 Merge pull request #837 from ispyisail/fix/prefix-nesting-and-multitree
Fix element prefix lookup: nesting-aware matching, multi-tree common collections (#671 items 2, 5)
2026-09-11 23:24:42 +02:00
ispyisail 7c0e867226 Rewrite element-prefix lookup for nesting and multi-tree common collections
The remaining two defects from bugtracker #671's original analysis,
which #686 knowingly didn't cover (see that PR's review thread and the
comment on the now-closed #672).

## #671 item 5: the XML matching ignored nesting

prefixFromLabelFile() was a flat token scan: it matched any <category
name="..."> whose name equalled the next path segment, with no check
that the match was actually a *child* of the previous match. It gave
correct results on the shipped 10_electric/qet_labels.xml only because
that file's document order happens to line up with its hierarchy --
any file with a same-named category at the wrong nesting depth would
silently return the wrong prefix.

Reproduced with a synthetic file where a top-level sibling category
happens to share a name with what should be an unmatched grandchild:
the old (already re-verified-fixed-for-whitespace) lookup returns a
prefix from a completely unrelated branch of the document; this
rewrite correctly reports "not found".

Fixed by replacing the QXmlStreamReader token walk with a QDomDocument
walk that only ever considers a matched node's direct <category>
children (firstChildElement()/nextSiblingElement(), scoped to that
node), which cannot cross into a same-named sibling subtree. This also
makes the whitespace-dependence fixed in #686 moot for the same
reason: DOM parsing doesn't distinguish pretty-printed from minified
input to begin with.

The inheritance rule ("if a directory has no prefix, use its parent's,
and so on") and the empty-<prefix/>-overrides-inheritance behaviour
#686 added both carry over unchanged: a category's own <prefix> child,
even an empty one, always overrides whatever a shallower ancestor
already provided; a category with no <prefix> child at all leaves the
inherited value untouched.

## #671 item 2: common-collection trees other than 10_electric

The lookup only ever consulted commonElementsDir()/10_electric --
literally: `if (current_location.fileName() == "10_electric")`. The
common collection ships four other top-level trees (20_logic,
30_hydraulic, 50_pneumatic, 60_energy); none of them could carry a
qet_labels.xml at all, because nothing ever looked for one.

Generalised to commonElementsDir()/<tree>/qet_labels.xml for whichever
top-level tree the element's path actually walks up to, tried first,
then custom, then company -- each of the latter two tried against both
a from-root layout (matching a custom/company file organised as a
mirror of the common collection, tree name included) and a
tree-relative one (matching a file scoped to just one tree), so
existing custom files keep working either way. This is the same
multi-candidate structure #686 already established for custom-then-
company; it now also covers which common-collection tree to check.

## Testing

Same constraint as #686: no working full build in this sandbox
(missing generated headers/deps), so the exact functions as committed
were extracted into a standalone Qt6 harness and run against the real
shipped 10_electric/qet_labels.xml (pretty-printed and minified),
a synthetic empty-prefix-override file, and the nesting-trap file
above -- 9/9, including the three cases #686 already fixed (direct
prefix, inherited prefix, not-found) staying correct, confirming this
rewrite doesn't regress that work.

Not exercised here (needs a real running QETApp / ElementsLocation,
which the standalone harness can't stand up): the elementPrefixForLocation()
candidate-list wiring itself -- collection_root computation, the
from-root/tree-relative dual lookup, and the common-then-custom-then-
company ordering. That code is mechanical and was reviewed carefully
by hand, but it has not been run.
2026-09-12 06:32:07 +12:00
ispyisail 0b7197118a Fix three follow-on defects in the prefix lookup this PR just refactored
Requested by @scorpio810 in review: an empty <prefix/> in the custom
collection should cancel a company-collection prefix, not fall through
to it. QXmlStreamReader::readElementText() returns a null QString for an
empty element, and the caller's isNull() check treats that the same as
"not found" -- distinguish the two so an explicit override actually
overrides. Verified in a standalone harness against a synthetic
override file, pretty-printed and minified.

Two more while in the same function, both from the original bugtracker
#671 analysis that this PR only partially addressed:

- QString path[10] with an unbounded index becomes a QStringList. The
  deepest category in the shipped collection already needs 9 of the 10
  slots; a custom collection can nest deeper, and overflow was writing
  QString objects past the end of a stack array (#671 item 3).
- The common-collection lookup still concatenated
  commonElementsDir() + "10_electric/qet_labels.xml" directly.
  commonElementsDir() returns the configured path verbatim with no
  guaranteed trailing separator, so relocating the collection to a path
  without one silently mangles this into one word and the file is never
  found -- the single most-reported cause of "prefixes don't work"
  (#671 item 1, forum #2178/#2651). QDir::filePath() joins correctly
  either way; applied to all three lookups (common, custom, company).

Also fixes a defect not in that original analysis: the token-matching
loop in prefixFromLabelFile() advanced twice per matched element --
once explicitly after a match, once more unconditionally at the bottom
of the loop -- which only produced the right result because a
pretty-printed file inserts a whitespace Characters token between
adjacent elements for the second advance to land on. A minified
qet_labels.xml has no such token, so the second advance skips clean
over the very element being searched for and the lookup silently finds
nothing -- reproduced against the real shipped 10_electric/qet_labels.xml
(returns "" instead of "K" for a plain coil, on every case tested, not
just the inheritance one). A single `continue` after a handled match
removes the double advance.

Testing: extracted the exact functions as committed into a standalone
Qt6 harness (outside the full QET build, which needs a dependency
fetch this sandbox doesn't have) and ran them against the real shipped
qet_labels.xml, pretty-printed and minified, covering a direct prefix,
inherited-from-ancestor prefix, not-found, and the explicit-empty-
override case -- 8/8, matching between formats, no regressions in the
pretty-printed results. The QDir::filePath() fix was verified
separately against both a trailing-slash and no-trailing-slash base
path. Not yet built inside the actual application (pugixml and other
generated headers aren't available standalone); the algorithm itself,
which is where all four defects lived, is what was under test.
2026-09-12 06:22:26 +12:00
enesgursoy6110 77f8e26ffc Address smart device BOM review feedback 2026-09-11 20:12:09 +03:00
enesgursoy6110 16dbc5c404 Add ungrouped device BOM CSV export 2026-09-11 20:11:28 +03:00
enesgursoy6110 73f7ea8f50 Add smart device metadata to element information 2026-09-11 20:11:19 +03:00
Laurent Trinques 99e9ce5aca QETApp: fall back to English when the QET translation is empty
A .qm compiled from a 0%-translated .ts (fi, no, rs, sk, sl, sr) loads
successfully but contains no messages, so setLanguage() treated the
language as loaded and never fell back to qet_en: users got the French
source strings instead of English. Treat an empty translator as not
loaded.

Also log the QET and Qt .qm files actually loaded in the startup
diagnostics (MachineInfo), to make translation reports easier to triage.
2026-09-11 16:28:11 +02:00
Laurent Trinques bc06f7d444 macOS: follow symlinks when looking for Qt translations
Homebrew's share/qt/translations is a symlink to the qttranslations keg;
find without -L did not descend into it and found no qtbase_*.qm.
2026-09-11 12:01:15 +02:00
Laurent Trinques 2620d34b59 macOS: look for Qt translations in the qttranslations Homebrew formula 2026-09-11 11:49:58 +02:00
Laurent Trinques 4b675dda1b CI/Windows: bundle Qt's own translations (qtbase) as lang/qt_XX.qm
windeployqt runs with --no-translations, so standard buttons (OK/Cancel)
and dialogs stayed in English. Copy each qtbase_XX.qm from the MSYS2 Qt
translations into files/lang/qt_XX.qm, where QETApp::setLanguage() looks,
with aliases for QET languages Qt only ships with a region (pt, zh).
2026-09-11 11:40:10 +02:00
Laurent Trinques e982778526 macOS: bundle Qt's own translations (qtbase) as lang/qt_XX.qm
Standard buttons (OK/Cancel) and dialogs are translated by qtbase_XX.qm,
which macdeployqt does not deploy. QETApp::setLanguage() falls back to
lang/qt_XX.qm, so copy each qtbase_XX.qm there, with aliases for QET
languages Qt only ships with a region (pt -> pt_PT, zh -> zh_CN).
2026-09-11 11:35:17 +02:00
Laurent Trinques 10e5cd00f4 macOS: rewrite absolute Homebrew refs and install ids left by macdeployqt
macdeployqt kept /opt/homebrew paths (e.g. libbrotlicommon's install id).
Rewrite them to @rpath/libX.dylib, copying the library into Frameworks
if needed, over 3 passes to handle chained dependencies.
2026-09-11 11:01:05 +02:00
Laurent Trinques 6bc53277ec macOS: bundle Homebrew @rpath deps macdeployqt cannot resolve
Recent Homebrew bottles (brotli, webp, sharpyuv) reference their deps as
@rpath/libX.dylib, which macdeployqt skips. Copy them from /opt/homebrew/lib
into Contents/Frameworks after macdeployqt, and abort if any @rpath or
/opt/homebrew reference remains unresolved. Drop the ineffective -libpath.
2026-09-11 10:50:48 +02:00
Laurent Trinques 7d4cbc30f1 cmake: add qet_ko.ts to TS_FILES
qet_ko.qm was tracked in git but qet_ko.ts was never listed in TS_FILES,
so Korean was no longer built after #751 removed the tracked .qm files.
2026-09-11 10:45:24 +02:00
Laurent Trinques 2fcf3540dd macOS: pass -libpath=/opt/homebrew/lib to macdeployqt, abort on unresolved rpath
Homebrew libs (brotli, webp, sharpyuv) now reference their deps via @rpath,
which macdeployqt could not resolve, leaving them out of the bundle.
2026-09-11 10:23:17 +02:00
Laurent Trinques a45a984b32 CI/Windows: use find instead of compgen -G to locate .qm files
$GITHUB_WORKSPACE is a Windows path under MSYS2 (D:\a\...); compgen -G
treats its backslashes as escapes, so the generated .qm were not found.
2026-09-11 10:00:35 +02:00
Laurent Trinques 1f647fa229 CI/Windows-build: copy generated .qm from build/lang after #751 2026-09-11 09:40:35 +02:00
Laurent Trinques c4d2f0c4fb MacQetDeploy_arm64_cmake.sh: take .qm from build/lang (#751), fail if translations are missing 2026-09-11 09:37:44 +02:00
Laurent Trinques ac47f52eb0 Merge pull request #751 from arummler/translation_file_cleanup
Compiled translation file proposal
2026-09-11 09:29:16 +02:00
Andre Rummler d2e75b4195 Removing the qm translation files as by default they are rebuild by CMake anyhow. Changing the output directory which pointed to the source directory. That avoids
polluting the source directory during out-of-source builds and it is standard that these files should be located inside the build directory during the build step.
2026-09-11 08:24:43 +02:00
Laurent Trinques bbc995c91e Merge pull request #835 from ispyisail/fix/wiring-list-numeric-order
Wiring list: numeric wire-number order, UTF-8 BOM and atomic write
2026-09-11 08:01:58 +02:00
ispyisail 2322e6fd12 Write the wiring list CSV atomically, with a UTF-8 byte order mark
Second of @scorpio810's review notes on #630:

  exportWiring() follows the existing CLI exporters (QTextStream, plain
  QFile). On Qt6 the output is UTF-8, so encoding is fine. Once #830 is
  in, it could optionally reuse BomExport::writeCsv() to get a BOM,
  which Excel needs to detect UTF-8 when opening the file directly, and
  an atomic write.

Done directly rather than waiting on #830, since neither half depends on
it and both are small.

The bytes were already UTF-8; what was missing is the mark that tells
Excel so. Opening a .csv without one, Excel falls back to the local
8-bit codepage and mangles any accented element label -- the common case
for this project's users.

QSaveFile replaces QFile so a failure part-way through leaves the
previous file intact instead of a truncated one. QSaveFile is already the
codebase's pattern for this (QET::writeToFile, qet.cpp:664).

Verified on perceuse.qet: output now starts ef bb bf, the header follows
intact, all 156 rows are preserved, and the file parses as utf-8-sig.
Pointing the exporter at a missing project leaves an existing target file
untouched, where before it would have been truncated.

Left the other CLI exporters alone. They share the same pattern, but
changing exportBom() would add a BOM to output that existing scripts
already consume, which is a behaviour change outside the scope of this
review note.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 12:52:19 +12:00
ispyisail b5722c3f2a Sort the wiring list by wire number as a number, not as text
Follows @scorpio810's review note on merging #630:

  ORDER BY diagram_position, wire_number sorts wire numbers as text,
  so "10" comes before "9".

Confirmed against the corpus: perceuse.qet put 111 before 12, and
affuteuse_250h.qet put 45 before 5. industrial.qet happened to look
correct only because its wire numbers are all the same width.

Wire numbers are free text and are not always numeric -- perceuse.qet
also carries an unresolved "%sequ_1" -- so the ordering has to cope with
both. Numeric values come first, ordered by value; anything else follows,
ordered as text. The trailing wire_number keeps ties stable.

Fixed in both places the query appears: the CLI exporter and the wiring
list dialog. They had the same ORDER BY, so fixing only one would have
made the dialog and --export-wiring disagree about the order of the same
data.

Verified on perceuse, affuteuse_250h, industrial and tremie_vibrante:
zero out-of-order numeric pairs afterwards, row counts unchanged, and
"%sequ_1" now sorts after the numbers rather than among them. Folio 3 of
perceuse.qet reads 0 1 2 3 4 4 5 5 6 6 7 7 12 12 where it previously
interleaved 111 before 12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 12:43:07 +12:00
Laurent Trinques 2926ca7306 Doxyfile fix version name 2026-09-10 23:31:52 +02:00
Laurent Trinques c7893c8229 Merge pull request #630 from ispyisail/feature-wiring-list-export
Wiring list dialog + excluded-conductor count (discussion #503, slice 4)
2026-09-10 22:45:22 +02:00
Laurent Trinques 412bc7f71f Merge pull request #629 from ispyisail/feature-wiring-list-view
Add wiring_list_view: from-to wiring list over the conductor tables (discussion #503, slice 3)
2026-09-10 22:44:27 +02:00
Laurent Trinques c147e6562d snap: add libcups2-dev to build-packages
Qt6 PrintSupport records Cups::Cups as a third-party dependency
(qprint_p.h includes <cups/ppd.h>), so find_package(Qt6 PrintSupport)
runs FindCups at configure time and fails without the CUPS headers.
Build-time only, nothing is staged.
2026-09-10 21:49:02 +02:00
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 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