Derive empty-slot labels from the group's declared terminals

Both issues from the review, preview of empty contact comb slots only:

- Slice the flat label list over the declared poles in drawAsContacts
  (terminals per pole from labels.size()/contactCount) instead of a
  fixed stride of 2 (3 for a switch) inside drawContact: terminalCount
  and contactCount are edited independently, so with 3 poles and the
  default terminal count of 2 the fixed stride starved every pole but
  the first. Available labels are now distributed across the poles.
- Map changeover labels of a slot to their own position always
  (common=stored[0] right, NC=stored[1] bottom-left, NO=stored[2]
  top-left, missing entries stay empty) instead of falling back to the
  raw stored order, which put the numbers on the wrong contact halves
  when the terminal count was below three.
- Element editor: changing the contact count now keeps the terminal
  count in step (same terminals per contact, type default 2/3 for
  inconsistent data, minimum 3 for a switch), so the mismatch cannot
  be created anymore; legacy mismatched data is handled by the new
  slicing.
- Drop the now redundant elmt check before is_power_ctc (it already
  includes it) - the dead null check from the review.
This commit is contained in:
Kellermorph
2026-09-25 11:24:35 +02:00
parent 800189debb
commit 5622308ba2
2 changed files with 62 additions and 31 deletions
@@ -588,6 +588,29 @@ void ElementPropertiesEditorWidget::populateSlaveGroupsTable()
contact_ct->setValue(group.contactCount);
ui->m_slave_groups_table->setCellWidget(i, 2, contact_ct);
// When the contact count changes, keep the terminal count in step
// with it, otherwise the two drift apart (both are edited
// independently): the stored terminals-per-contact ratio is kept,
// or the contact type default (2, 3 for a switch) is used when the
// stored values don't divide evenly (inconsistent legacy data).
const int old_contacts = group.contactCount;
const int old_terminals = group.terminalCount;
connect(contact_ct, QOverload<int>::of(&QSpinBox::valueChanged),
this, [this, i, old_contacts, old_terminals](int val) {
if (i < m_data.m_slave_contact_groups.size()) {
readSlaveGroupsFromTable();
auto &group = m_data.m_slave_contact_groups[i];
int per_pole = old_terminals / qMax(1, old_contacts);
if (per_pole < 1 || old_terminals % qMax(1, old_contacts) != 0)
per_pole = group.type == ElementData::SW ? 3 : 2;
else if (group.type == ElementData::SW && per_pole < 3)
per_pole = 3; //a switch needs common, NC and NO
group.contactCount = val;
group.terminalCount = val * per_pole;
populateSlaveGroupsTable();
}
});
// Terminal count
auto *terminal_ct = new QSpinBox(ui->m_slave_groups_table);
terminal_ct->setMinimum(1);
+39 -31
View File
@@ -854,21 +854,35 @@ void CrossRefItem::drawAsContacts(QPainter &painter)
//A single pole simple contact (NO or NC) reads its two
//numbers the other way round (checked against the
//diagram). Changeover contacts are not handled here:
//their labels are rotated one step counter-clockwise
//inside drawContact(), per pole, so multi pole
//changeovers work too. Groups with several NO/NC poles
//keep the order the master defines: a 3 pole power
//contact already reads correctly that way.
//their labels are mapped to the right contact half in
//drawContact(), per pole, so multi pole changeovers work
//too. Groups with several NO/NC poles keep the order the
//master defines: a 3 pole power contact already reads
//correctly that way.
if (poles == 1 && (option & NOC))
std::reverse(labels.begin(), labels.end());
//The declared terminals are distributed over the declared
//poles. terminalCount and contactCount are edited
//independently in the element editor, so the list can be
//shorter than two entries per pole (three for a switch):
//every pole then gets its share of what exists, instead
//of a fixed 2/3 stride starving all but the first poles.
const int per_pole = labels.size() / poles;
const int extra = labels.size() % poles;
int begin = 0;
for (int pole = 0; pole < poles; ++pole)
{
const int count = per_pole + (pole < extra ? 1 : 0);
const QStringList pole_labels = labels.mid(begin, count);
begin += count;
bounding_rect = bounding_rect.united(
drawContact(painter,
option,
nullptr,
pole,
labels));
pole_labels));
}
}
}
@@ -938,11 +952,11 @@ QRectF CrossRefItem::drawLinkedSlaveContacts(QPainter &painter, Element *elmt)
defines but no slave is linked to yet: no position text, no hover/click
support, and the terminal names then come from master_labels.
@param pole_index : which contact of the group is drawn (0 based), used
to pick the right pair/triplet of terminal names.
@param master_labels : the terminal names the master defines for this
contact group (ElementData::SlaveContactGroup::labels), used when elmt
is nullptr so an empty slot shows the numbers the master declares, the
same way a linked slave would show them.
to pick the right pair of terminal names of a linked multi-pole contact.
@param master_labels : the terminal names the master declares for this
pole (sliced from ElementData::SlaveContactGroup::labels by the caller),
used when elmt is nullptr so an empty slot shows the numbers the master
declares, the same way a linked slave would show them.
@return The bounding rect of the draw (contact + text)
*/
QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, int pole_index, const QStringList &master_labels)
@@ -981,34 +995,28 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in
}
} else if (!master_labels.isEmpty()) {
//Empty slot of the contact comb: the slave is missing but the
//master already declares the terminal names of the group, so the
//slot shows them instead of staying mute. The labels are a flat
//list covering every terminal of the group, in the order a linked
//slave would receive them: two per contact (or three for a switch).
//master already declares the terminal names, so the slot shows
//them instead of staying mute. master_labels contains exactly
//the terminals of this pole (the caller slices the declared
//terminal list over the declared poles), in the order a linked
//slave would receive them.
if (is_sw) {
//The labels are stored in terminal order (for a typical
//changeover contact: common, NC, NO, i.e. 11, 12, 14), while
//the symbol draws NC bottom-left, NO top-left and the common
//on the right: one step counter-clockwise puts every number
//beside its own contact.
const int base = pole_index * 3;
QStringList trio;
for (int k = 0; k < 3; ++k)
if (base + k < master_labels.size())
trio << master_labels.at(base + k);
if (trio.size() == 3)
terminal_names << trio.at(1) << trio.at(2) << trio.at(0);
else
terminal_names << trio; //incomplete set: keep stored order
//on the right: every stored entry goes to its own position.
//Entries the master doesn't declare (terminal count lower
//than three) simply stay empty instead of landing on the
//wrong contact half like the raw stored order would.
terminal_names << master_labels.value(1)
<< master_labels.value(2)
<< master_labels.value(0);
} else {
const int base = pole_index * 2;
for (int k = 0; k < 2; ++k)
if (base + k < master_labels.size())
terminal_names << master_labels.at(base + k);
terminal_names = master_labels;
}
}
if (elmt && is_power_ctc) {
if (is_power_ctc) {
// Sort terminals alphanumerically so names like "R1","R2"... or "1","2"...
// are ordered correctly. Extract trailing digits for numeric comparison;
// fall back to full string comparison when no digits are found.