mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-24 10:44:13 +02:00
Compare commits
13 Commits
a583b3c43c
...
c1f9af8544
| Author | SHA1 | Date | |
|---|---|---|---|
| c1f9af8544 | |||
| 033c2f93a8 | |||
| 83623a0fa6 | |||
| c85f80bbcf | |||
| f18eda845b | |||
| 73e9473db4 | |||
| 7307128fc3 | |||
| 799ff5573f | |||
| 6f2c66afef | |||
| c39c7414cf | |||
| c33f250910 | |||
| c265f0206c | |||
| 36dd1624d2 |
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CONTACTUSAGE_H
|
||||
#define CONTACTUSAGE_H
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
/**
|
||||
@brief The ContactUsage struct
|
||||
How many slave contacts a master element currently uses, broken down
|
||||
by contact type.
|
||||
|
||||
Header-only and free of any graphics dependency so that the counting
|
||||
rules can be unit tested on their own. MasterElement::contactUsage()
|
||||
is the thin wrapper that feeds it the linked elements.
|
||||
|
||||
This counts contacts, which is what tells you how many contacts an
|
||||
auxiliary block must provide. It is deliberately not the count that
|
||||
MasterElement::isFull() uses: a master's max_slaves is a number of
|
||||
slots, and a slave fills exactly one slot however many contacts it
|
||||
carries.
|
||||
|
||||
Two rules are easy to get wrong, and both live here so that every
|
||||
caller gets them right:
|
||||
- a slave stands for as many contacts as its "number" kind
|
||||
information says, so a 4 pole contact counts as 4, not as 1
|
||||
- a changeover contact is counted once, as sw. CrossRefItem's
|
||||
NOElements() and NCElements() both return it, so adding those two
|
||||
lists together would count it twice.
|
||||
*/
|
||||
struct ContactUsage
|
||||
{
|
||||
/**
|
||||
Contact types a slave can declare. Mirrors
|
||||
ElementData::SlaveState, which is not used directly so that this
|
||||
header stays free of the element data dependencies and can be
|
||||
unit tested on its own. MasterElement::contactUsage() maps
|
||||
between the two.
|
||||
*/
|
||||
enum Type
|
||||
{
|
||||
NO, ///< Normally open
|
||||
NC, ///< Normally closed
|
||||
SW, ///< Changeover
|
||||
Other ///< Neither of the above
|
||||
};
|
||||
|
||||
int no = 0; ///< Normally open
|
||||
int nc = 0; ///< Normally closed
|
||||
int sw = 0; ///< Changeover
|
||||
int other = 0; ///< Neither of the above
|
||||
|
||||
int total() const { return no + nc + sw + other; }
|
||||
|
||||
/**
|
||||
Add one slave element to the tally.
|
||||
@param type the contact type the slave declares
|
||||
@param contacts how many contacts it stands for. Values below 1
|
||||
are treated as 1: an element which declares no
|
||||
contact count is still one contact.
|
||||
*/
|
||||
void addSlave(Type type, int contacts)
|
||||
{
|
||||
const int n = std::max(1, contacts);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case NO: no += n; break;
|
||||
case NC: nc += n; break;
|
||||
case SW: sw += n; break;
|
||||
case Other: other += n; break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CONTACTUSAGE_H
|
||||
@@ -227,6 +227,73 @@ void MasterElement::aboutDeleteXref()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MasterElement::contactUsage
|
||||
* Count the slave contacts currently linked to this master, by type.
|
||||
* This is the single place where that count is worked out: the cross ref
|
||||
* item, the properties dialog and the link widgets all read it from here,
|
||||
* so they cannot disagree with each other.
|
||||
* @return the per type usage
|
||||
*/
|
||||
namespace {
|
||||
|
||||
/**
|
||||
Map the element data's contact type onto the tally's own, so that
|
||||
the used count and the declared capacity cannot classify the same
|
||||
contact type differently.
|
||||
*/
|
||||
ContactUsage::Type contactType(ElementData::SlaveState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case ElementData::NO: return ContactUsage::NO;
|
||||
case ElementData::NC: return ContactUsage::NC;
|
||||
case ElementData::SW: return ContactUsage::SW;
|
||||
case ElementData::Other: break;
|
||||
}
|
||||
|
||||
return ContactUsage::Other;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ContactUsage MasterElement::contactUsage() const
|
||||
{
|
||||
ContactUsage usage;
|
||||
|
||||
for (Element *elmt : connected_elements)
|
||||
{
|
||||
if (!elmt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const ElementData &data = elmt->elementData();
|
||||
usage.addSlave(contactType(data.m_slave_state), data.m_contact_count);
|
||||
}
|
||||
|
||||
return usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MasterElement::contactCapacity
|
||||
* The contacts this master declares it provides, by type, summed over its
|
||||
* contact groups. A group stands for contactCount contacts of its type.
|
||||
* Returns an empty tally when the element declares no groups, which is the
|
||||
* case for every element in the standard collection today -- callers use
|
||||
* that to decide whether a capacity is worth showing at all.
|
||||
* @return the per type capacity
|
||||
*/
|
||||
ContactUsage MasterElement::contactCapacity() const
|
||||
{
|
||||
ContactUsage capacity;
|
||||
|
||||
for (const auto &group : m_data.m_slave_contact_groups) {
|
||||
capacity.addSlave(contactType(group.type), group.contactCount);
|
||||
}
|
||||
|
||||
return capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MasterElement::isFull
|
||||
* @return true if the master has reached its maximum number of slaves
|
||||
@@ -247,7 +314,10 @@ bool MasterElement::isFull() const
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return true if current connected elements reached or exceeded the limit
|
||||
// max_slaves is a number of slots, not of contacts: it sizes the
|
||||
// element's contact group table, and a slave occupies exactly one
|
||||
// group however many contacts that group stands for. So the slots
|
||||
// in use are the linked elements, not the contacts they carry.
|
||||
return connected_elements.size() >= max_slaves;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define MASTERELEMENT_H
|
||||
|
||||
#include "element.h"
|
||||
#include "../contactusage.h"
|
||||
#include <QHash>
|
||||
#include <QMetaObject>
|
||||
|
||||
@@ -47,6 +48,8 @@ class MasterElement : public Element
|
||||
void initLink (QETProject *project) override;
|
||||
QRectF XrefBoundingRect() const;
|
||||
|
||||
ContactUsage contactUsage() const;
|
||||
ContactUsage contactCapacity() const;
|
||||
bool isFull() const; // Check Slave-Limit
|
||||
|
||||
protected:
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "../qetgraphicsitem/dynamicelementtextitem.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "../qetgraphicsitem/elementtextitemgroup.h"
|
||||
#include "../qetgraphicsitem/masterelement.h"
|
||||
#include "../qeticons.h"
|
||||
#include "dynamicelementtextitemeditor.h"
|
||||
#include "elementinfowidget.h"
|
||||
@@ -380,9 +381,48 @@ QWidget *ElementPropertiesWidget::generalWidget()
|
||||
description_string += QString(tr("Rotation : %1°\n")).arg(m_element.data()->rotation());
|
||||
description_string += QString(tr("Dimensions : %1*%2\n")).arg(m_element -> size().width()).arg(m_element -> size().height());
|
||||
description_string += QString(tr("Bornes : %1\n")).arg(m_element -> terminals().count());
|
||||
if (m_element->linkType() == Element::Master){
|
||||
description_string += QString(tr("Nombre maximum de contacts esclaves définis : %1\n")).arg(m_element -> elementData().m_max_slaves);
|
||||
description_string += QString(tr("Nombre de contacts esclaves utilisés : %1\n")).arg(m_element ->linkedElements().count());
|
||||
if (m_element->linkType() == Element::Master)
|
||||
{
|
||||
//The declared limit is optional: -1 means the element sets no
|
||||
//limit at all, which is worth saying rather than printing "-1".
|
||||
const int max_slaves = m_element->elementData().m_max_slaves;
|
||||
description_string += max_slaves == -1
|
||||
? QString(tr("Nombre maximum de contacts esclaves définis : non défini\n"))
|
||||
: QString(tr("Nombre maximum de contacts esclaves définis : %1\n")).arg(max_slaves);
|
||||
|
||||
//Left as a count of linked elements: the line above is a number
|
||||
//of slots, and a slave fills one slot however many contacts it
|
||||
//carries, so the two stay in the same unit.
|
||||
description_string += QString(tr("Nombre de contacts esclaves utilisés : %1\n")).arg(m_element->linkedElements().count());
|
||||
|
||||
//The breakdown below is in contacts, not slots: it answers how
|
||||
//many contacts an auxiliary block must provide.
|
||||
const MasterElement *master =
|
||||
static_cast<const MasterElement *>(m_element.data());
|
||||
const ContactUsage usage = master->contactUsage();
|
||||
const ContactUsage capacity = master->contactCapacity();
|
||||
|
||||
if (capacity.total() > 0)
|
||||
{
|
||||
//The element declares contact groups, so it can say not
|
||||
//only what has been used but what it has to offer. A type
|
||||
//used beyond what is declared shows as e.g. "1/0", which
|
||||
//is the point: it says this contact does not fit the part.
|
||||
description_string += QString(tr(" Contacts : NO : %1/%2, NC : %3/%4, inverseurs : %5/%6, autres : %7/%8\n"))
|
||||
.arg(usage.no).arg(capacity.no)
|
||||
.arg(usage.nc).arg(capacity.nc)
|
||||
.arg(usage.sw).arg(capacity.sw)
|
||||
.arg(usage.other).arg(capacity.other);
|
||||
}
|
||||
else if (usage.total() > 0)
|
||||
{
|
||||
//No declared groups, so a plain count of what is in use.
|
||||
description_string += QString(tr(" Contacts : NO : %1, NC : %2, inverseurs : %3, autres : %4\n"))
|
||||
.arg(usage.no)
|
||||
.arg(usage.nc)
|
||||
.arg(usage.sw)
|
||||
.arg(usage.other);
|
||||
}
|
||||
}
|
||||
description_string += QString(tr("Emplacement : %1\n")).arg(m_element.data()->location().toString());
|
||||
|
||||
|
||||
@@ -65,7 +65,14 @@ ClickableImageLabel::ClickableImageLabel(const QImage &sourceImage, QWidget *par
|
||||
*/
|
||||
void ClickableImageLabel::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() != Qt::LeftButton || pixmap().isNull())
|
||||
// QLabel::pixmap() returns a pointer in Qt5 and a value in Qt6.
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
const QPixmap label_pixmap = pixmap() ? *pixmap() : QPixmap();
|
||||
#else
|
||||
const QPixmap label_pixmap = pixmap();
|
||||
#endif
|
||||
|
||||
if (event->button() != Qt::LeftButton || label_pixmap.isNull())
|
||||
return;
|
||||
|
||||
// The label may be larger than its pixmap (layout stretching); the
|
||||
@@ -73,9 +80,9 @@ void ClickableImageLabel::mousePressEvent(QMouseEvent *event)
|
||||
// so the click has to be re-based against the pixmap's own rect
|
||||
// within the label, not the label's own top-left.
|
||||
const QRect pixmapRect(
|
||||
(width() - pixmap().width()) / 2,
|
||||
(height() - pixmap().height()) / 2,
|
||||
pixmap().width(), pixmap().height());
|
||||
(width() - label_pixmap.width()) / 2,
|
||||
(height() - label_pixmap.height()) / 2,
|
||||
label_pixmap.width(), label_pixmap.height());
|
||||
if (!pixmapRect.contains(event->pos()))
|
||||
return;
|
||||
|
||||
|
||||
@@ -353,8 +353,16 @@ void LinkSingleElementWidget::buildTree()
|
||||
|
||||
QSettings settings;
|
||||
QVariant v = settings.value(QStringLiteral("link-element-widget/report-state"));
|
||||
if(!v.isNull())
|
||||
ui->m_tree_widget->header()->restoreState(v.toByteArray());
|
||||
auto *header = ui->m_tree_widget->header();
|
||||
if (v.isNull() || !header->restoreState(v.toByteArray()))
|
||||
{
|
||||
// Keep logical column IDs stable for saved layouts, but show the
|
||||
// folio identity first even when the candidate has no conductor.
|
||||
for (int column = 5; column < 8; ++column)
|
||||
header->moveSection(header->visualIndex(column), column - 5);
|
||||
ui->m_tree_widget->resizeColumnToContents(5);
|
||||
ui->m_tree_widget->resizeColumnToContents(6);
|
||||
}
|
||||
}
|
||||
|
||||
setUpCompleter();
|
||||
|
||||
@@ -85,3 +85,9 @@ add_test(NAME tst_diagramsortkeys COMMAND tst_diagramsortkeys)
|
||||
target_include_directories(tst_diagramsortkeys PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_diagramsortkeys PRIVATE Qt::Test)
|
||||
|
||||
# contactusage.h is a header-only helper holding the contact counting
|
||||
# rules, so this test builds independently of the rest of the QET sources.
|
||||
add_executable(tst_contactusage tst_contactusage.cpp)
|
||||
add_test(NAME tst_contactusage COMMAND tst_contactusage)
|
||||
target_include_directories(tst_contactusage PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_contactusage PRIVATE Qt::Test)
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <QtTest>
|
||||
|
||||
#include "contactusage.h"
|
||||
|
||||
class tst_contactusage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
// An empty master uses nothing.
|
||||
void emptyUsesNothing()
|
||||
{
|
||||
ContactUsage usage;
|
||||
|
||||
QCOMPARE(usage.no, 0);
|
||||
QCOMPARE(usage.nc, 0);
|
||||
QCOMPARE(usage.sw, 0);
|
||||
QCOMPARE(usage.other, 0);
|
||||
QCOMPARE(usage.total(), 0);
|
||||
}
|
||||
|
||||
// Each type accumulates into its own field only.
|
||||
void countsEachTypeSeparately()
|
||||
{
|
||||
ContactUsage usage;
|
||||
usage.addSlave(ContactUsage::NO, 1);
|
||||
usage.addSlave(ContactUsage::NO, 1);
|
||||
usage.addSlave(ContactUsage::NC, 1);
|
||||
usage.addSlave(ContactUsage::SW, 1);
|
||||
usage.addSlave(ContactUsage::Other, 1);
|
||||
|
||||
QCOMPARE(usage.no, 2);
|
||||
QCOMPARE(usage.nc, 1);
|
||||
QCOMPARE(usage.sw, 1);
|
||||
QCOMPARE(usage.other, 1);
|
||||
QCOMPARE(usage.total(), 5);
|
||||
}
|
||||
|
||||
// A slave standing for several contacts counts once per contact.
|
||||
// Counting elements rather than contacts made a 4 pole contact
|
||||
// consume a single contact from the master's budget.
|
||||
void countsContactsNotElements()
|
||||
{
|
||||
ContactUsage usage;
|
||||
usage.addSlave(ContactUsage::NO, 4);
|
||||
|
||||
QCOMPARE(usage.no, 4);
|
||||
QCOMPARE(usage.total(), 4);
|
||||
}
|
||||
|
||||
// A changeover is one contact of its own kind, never one NO plus
|
||||
// one NC. CrossRefItem::NOElements() and NCElements() both return
|
||||
// changeovers, so a count built by adding those two lists would
|
||||
// report a single changeover as two contacts.
|
||||
void changeoverIsCountedOnce()
|
||||
{
|
||||
ContactUsage usage;
|
||||
usage.addSlave(ContactUsage::SW, 1);
|
||||
|
||||
QCOMPARE(usage.sw, 1);
|
||||
QCOMPARE(usage.no, 0);
|
||||
QCOMPARE(usage.nc, 0);
|
||||
QCOMPARE(usage.total(), 1);
|
||||
}
|
||||
|
||||
// An element which declares no contact count, or a nonsensical one,
|
||||
// is still a contact.
|
||||
void missingContactCountIsOneContact_data()
|
||||
{
|
||||
QTest::addColumn<int>("declared");
|
||||
|
||||
QTest::newRow("zero") << 0;
|
||||
QTest::newRow("negative") << -1;
|
||||
}
|
||||
|
||||
void missingContactCountIsOneContact()
|
||||
{
|
||||
QFETCH(int, declared);
|
||||
|
||||
ContactUsage usage;
|
||||
usage.addSlave(ContactUsage::NO, declared);
|
||||
|
||||
QCOMPARE(usage.no, 1);
|
||||
QCOMPARE(usage.total(), 1);
|
||||
}
|
||||
|
||||
// A declared capacity is summed across groups, so two NO groups of two
|
||||
// contacts each declare four NO contacts, not two groups.
|
||||
void capacitySumsAcrossGroups()
|
||||
{
|
||||
ContactUsage capacity;
|
||||
capacity.addSlave(ContactUsage::NO, 2);
|
||||
capacity.addSlave(ContactUsage::NO, 2);
|
||||
capacity.addSlave(ContactUsage::NC, 1);
|
||||
|
||||
QCOMPARE(capacity.no, 4);
|
||||
QCOMPARE(capacity.nc, 1);
|
||||
QCOMPARE(capacity.total(), 5);
|
||||
}
|
||||
|
||||
// The mix a coil would actually carry: two single NO, one 4 pole NO,
|
||||
// one NC and one changeover.
|
||||
void tallysARealisticMix()
|
||||
{
|
||||
ContactUsage usage;
|
||||
usage.addSlave(ContactUsage::NO, 1);
|
||||
usage.addSlave(ContactUsage::NO, 1);
|
||||
usage.addSlave(ContactUsage::NO, 4);
|
||||
usage.addSlave(ContactUsage::NC, 1);
|
||||
usage.addSlave(ContactUsage::SW, 1);
|
||||
|
||||
QCOMPARE(usage.no, 6);
|
||||
QCOMPARE(usage.nc, 1);
|
||||
QCOMPARE(usage.sw, 1);
|
||||
QCOMPARE(usage.total(), 8);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_contactusage)
|
||||
#include "tst_contactusage.moc"
|
||||
Reference in New Issue
Block a user