Terminal strip editor: add spare terminals, numbered, counted in the nomenclature

A spare (reserved) terminal is a terminal of a strip which is drawn on no
folio. RealTerminal already foresaw it ("a terminal set by user but not
present on any folio") but every terminal needed an element.

- RealTerminal::createSpare(): a terminal without element, with its own
  data (label, type, function, led, informations: section, width, catalog
  terminal, color, manufacturer...). Saved in the strip:
  <real_terminal spare_uuid="..." type="..." function="..."><informations>
  Older versions ignore these terminals.
- TerminalStrip::insertSpareTerminals() / insertTerminals(): insert at a
  position of the strip. Undoable commands AddSpareTerminalsCommand and
  ChangeSpareTerminalDataCommand.
- Terminal strip editor: "Add spare terminals..." button. The terminals
  are inserted after the selected terminal (at the end if none), numbered
  from its label ("X1:12" -> "X1:13", "X1:14", leading zeros kept), with
  its properties if asked. The labels can be changed or removed in the
  table, like the other cells; the Xref column shows "Spare". Moving a
  spare terminal to the independent terminals removes it (undoable).
- Nomenclature: the spare terminals are in the new spare_terminal table,
  added to element_nomenclature_view (UNION ALL, same columns), after the
  folios, in the order of their strip. exclude_from_bom is honoured.
- The modified cells of the editor model are kept by real terminal, not by
  element, since a spare terminal has none.
This commit is contained in:
Laurent Trinques
2026-10-18 16:24:14 +02:00
parent ddc71553e2
commit 015811d243
16 changed files with 747 additions and 70 deletions
@@ -19,6 +19,7 @@
#include "../../qetgraphicsitem/terminalelement.h"
#include "../realterminal.h"
#include "../physicalterminal.h"
#include "../terminalstrip.h"
/**
* @brief AddTerminalToStripCommand::AddTerminalToStripCommand
@@ -234,3 +235,72 @@ void MoveTerminalCommand::redo()
m_new_strip->addTerminals(m_terminal);
}
}
/**
* @brief AddSpareTerminalsCommand::AddSpareTerminalsCommand
* @param strip : the strip where the spare terminals are inserted
* @param pos : position of the first spare terminal in the strip,
* -1 to add them at the end of the strip
* @param spare_terminals : spare terminals made by RealTerminal::createSpare()
* @param parent : parent undo command
*/
AddSpareTerminalsCommand::AddSpareTerminalsCommand(TerminalStrip *strip, int pos,
const QVector<QSharedPointer<RealTerminal>> &spare_terminals,
QUndoCommand *parent) :
QUndoCommand{parent},
m_strip{strip},
m_pos{pos},
m_spare_terminals{spare_terminals}
{
const auto count = spare_terminals.size();
const auto strip_name = strip ? strip->name() : QString();
setText(strip_name.isEmpty()
? QObject::tr("Add %n spare terminals", "", count)
: QObject::tr("Add %n spare terminals to terminal group %1", "", count).arg(strip_name));
}
void AddSpareTerminalsCommand::undo()
{
if (m_strip) {
m_strip->removeTerminals(m_physical_terminals);
}
}
void AddSpareTerminalsCommand::redo()
{
if (!m_strip) {
return;
}
//The first time the physical terminals are made,
//after an undo the same ones are inserted again
if (m_physical_terminals.isEmpty()) {
m_physical_terminals = m_strip->insertSpareTerminals(m_pos, m_spare_terminals);
} else {
m_strip->insertTerminals(m_pos, m_physical_terminals);
}
}
/**
* @brief ChangeSpareTerminalDataCommand::ChangeSpareTerminalDataCommand
* @param spare_terminal : the spare terminal to change
* @param new_data : the new data of the terminal
* @param parent : parent undo command
*/
ChangeSpareTerminalDataCommand::ChangeSpareTerminalDataCommand(const QSharedPointer<RealTerminal> &spare_terminal,
const ElementData &new_data,
QUndoCommand *parent) :
QUndoCommand{parent},
m_spare_terminal{spare_terminal},
m_old_data{spare_terminal->spareData()},
m_new_data{new_data}
{
setText(QObject::tr("Modify the spare terminal %1").arg(spare_terminal->label()));
}
void ChangeSpareTerminalDataCommand::undo() {
m_spare_terminal->setSpareData(m_old_data);
}
void ChangeSpareTerminalDataCommand::redo() {
m_spare_terminal->setSpareData(m_new_data);
}
@@ -21,6 +21,8 @@
#include <QUndoCommand>
#include <QPointer>
#include <QVector>
#include <QSharedPointer>
#include "../../properties/elementdata.h"
class TerminalElement;
class TerminalStrip;
@@ -89,4 +91,45 @@ class MoveTerminalCommand : public QUndoCommand
};
/**
* @brief The AddSpareTerminalsCommand class
* Insert spare terminals (on no folio) in a terminal strip,
* at a position of the strip.
*/
class AddSpareTerminalsCommand : public QUndoCommand
{
public:
AddSpareTerminalsCommand(TerminalStrip *strip, int pos,
const QVector<QSharedPointer<RealTerminal>> &spare_terminals,
QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
private:
QPointer<TerminalStrip> m_strip;
int m_pos = -1;
QVector<QSharedPointer<RealTerminal>> m_spare_terminals;
QVector<QSharedPointer<PhysicalTerminal>> m_physical_terminals;
};
/**
* @brief The ChangeSpareTerminalDataCommand class
* Change the data (label, type, section...) of a spare terminal
*/
class ChangeSpareTerminalDataCommand : public QUndoCommand
{
public:
ChangeSpareTerminalDataCommand(const QSharedPointer<RealTerminal> &spare_terminal,
const ElementData &new_data,
QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
private:
QSharedPointer<RealTerminal> m_spare_terminal;
ElementData m_old_data, m_new_data;
};
#endif // ADDTERMINALTOSTRIPCOMMAND_H
+157 -26
View File
@@ -33,6 +33,118 @@ RealTerminal::RealTerminal(Element *terminal) :
m_element(terminal)
{}
/**
* @brief RealTerminal::createSpare
* Create a spare terminal (a reserved terminal) : a terminal of a strip
* which is not drawn on any folio. Its data (label, type, section,
* width...) are its own, stored with the terminal strip.
* @param data : the data of the terminal, only the terminal type,
* function, led and the informations are used.
* @param uuid : the uuid of the spare terminal
* @return the new spare terminal, not yet added to a strip
*/
QSharedPointer<RealTerminal> RealTerminal::createSpare(const ElementData &data, const QUuid &uuid)
{
auto raw_ = new RealTerminal(nullptr);
raw_->m_spare = true;
raw_->m_spare_uuid = uuid.isNull() ? QUuid::createUuid() : uuid;
raw_->m_spare_data.m_type = ElementData::Terminal;
raw_->m_spare_data.setTerminalType(data.terminalType());
raw_->m_spare_data.setTerminalFunction(data.terminalFunction());
raw_->m_spare_data.setTerminalLED(data.terminalLed());
raw_->m_spare_data.m_informations = data.m_informations;
return raw_->sharedRef();
}
/**
* @brief RealTerminal::spareFromXml
* @param xml_element : a real terminal saved by toXml()
* @return the spare terminal saved in @a xml_element,
* or a null pointer if @a xml_element is not a spare terminal.
*/
QSharedPointer<RealTerminal> RealTerminal::spareFromXml(const QDomElement &xml_element)
{
if (xml_element.tagName() != xmlTagName() ||
!xml_element.hasAttribute(QStringLiteral("spare_uuid"))) {
return QSharedPointer<RealTerminal>();
}
ElementData data_;
data_.setTerminalType(ElementData::terminalTypeFromString(
xml_element.attribute(QStringLiteral("type"))));
data_.setTerminalFunction(ElementData::terminalFunctionFromString(
xml_element.attribute(QStringLiteral("function"))));
data_.setTerminalLED(QET::infoFlagIsTrue(xml_element.attribute(QStringLiteral("led"))));
data_.m_informations.fromXml(xml_element.firstChildElement(QStringLiteral("informations")),
QStringLiteral("information"));
return createSpare(data_, QUuid(xml_element.attribute(QStringLiteral("spare_uuid"))));
}
/**
* @brief RealTerminal::isSpare
* @return true if this terminal is a spare terminal, not drawn on any folio
* @sa createSpare
*/
bool RealTerminal::isSpare() const {
return m_spare;
}
/**
* @brief RealTerminal::spareData
* @return the data of this spare terminal, an empty ElementData
* if this terminal is not a spare terminal.
*/
ElementData RealTerminal::spareData() const {
return m_spare_data;
}
/**
* @brief RealTerminal::setSpareData
* Set the data of this spare terminal (nothing is done if this terminal
* is not a spare terminal) and tell the strip, so that it is redrawn.
* @param data
*/
void RealTerminal::setSpareData(const ElementData &data)
{
if (!m_spare) {
return;
}
m_spare_data.setTerminalType(data.terminalType());
m_spare_data.setTerminalFunction(data.terminalFunction());
m_spare_data.setTerminalLED(data.terminalLed());
m_spare_data.m_informations = data.m_informations;
if (auto strip_ = parentStrip()) {
strip_->terminalDataChanged();
}
}
/**
* @brief RealTerminal::hasData
* @return true if this terminal has data : it is linked to an element,
* or it is a spare terminal.
*/
bool RealTerminal::hasData() const {
return m_spare || !m_element.isNull();
}
/**
* @brief RealTerminal::terminalData
* @return the data of this terminal : the data of its element,
* or its own data if it is a spare terminal.
*/
ElementData RealTerminal::terminalData() const
{
if (m_spare) {
return m_spare_data;
}
if (m_element) {
return m_element->elementData();
}
return ElementData();
}
RealTerminal::~RealTerminal()
{
if (m_physical_terminal) {
@@ -82,8 +194,22 @@ QWeakPointer<RealTerminal> RealTerminal::weakRef() {
QDomElement RealTerminal::toXml(QDomDocument &parent_document) const
{
auto root_elmt = parent_document.createElement(this->xmlTagName());
if (m_element)
if (m_spare)
{
//A spare terminal is on no folio : its data are saved here
root_elmt.setAttribute(QStringLiteral("spare_uuid"), m_spare_uuid.toString());
root_elmt.setAttribute(QStringLiteral("type"), ElementData::terminalTypeToString(m_spare_data.terminalType()));
root_elmt.setAttribute(QStringLiteral("function"), ElementData::terminalFunctionToString(m_spare_data.terminalFunction()));
if (m_spare_data.terminalLed()) {
root_elmt.setAttribute(QStringLiteral("led"), QStringLiteral("true"));
}
auto xml_info = parent_document.createElement(QStringLiteral("informations"));
m_spare_data.m_informations.toXml(xml_info, QStringLiteral("information"));
root_elmt.appendChild(xml_info);
}
else if (m_element) {
root_elmt.setAttribute(QStringLiteral("element_uuid"), m_element->uuid().toString());
}
return root_elmt;
}
@@ -137,7 +263,9 @@ int RealTerminal::level() const
* @return the label of this real terminal
*/
QString RealTerminal::label() const {
if (!m_element.isNull()) {
if (m_spare) {
return m_spare_data.m_informations.value(QStringLiteral("label")).toString();
} else if (!m_element.isNull()) {
return m_element->actualLabel();
} else {
return QLatin1String();
@@ -164,8 +292,8 @@ QString RealTerminal::Xref() const
* The value is stored in the informations of the element.
*/
QString RealTerminal::cable() const {
if (m_element) {
return m_element->elementData().m_informations.value(cableInfoKey()).toString();
if (hasData()) {
return terminalData().m_informations.value(cableInfoKey()).toString();
}
return QString();
}
@@ -175,8 +303,8 @@ QString RealTerminal::cable() const {
* @return The color / number of the wire of the cable, connected to this terminal.
*/
QString RealTerminal::cableWire() const {
if (m_element) {
return m_element->elementData().m_informations.value(cableWireInfoKey()).toString();
if (hasData()) {
return terminalData().m_informations.value(cableWireInfoKey()).toString();
}
return QString();
}
@@ -187,9 +315,9 @@ QString RealTerminal::cableWire() const {
* and not one of its wires.
*/
bool RealTerminal::isShield() const {
if (m_element) {
if (hasData()) {
return QET::infoFlagIsTrue(
m_element->elementData().m_informations.value(shieldInfoKey()).toString());
terminalData().m_informations.value(shieldInfoKey()).toString());
}
return false;
}
@@ -204,8 +332,8 @@ bool RealTerminal::isShield() const {
QColor RealTerminal::color() const
{
QColor main_, stripe_;
if (m_element) {
TerminalColor::parse(m_element->elementData().m_informations.value(colorInfoKey()).toString(),
if (hasData()) {
TerminalColor::parse(terminalData().m_informations.value(colorInfoKey()).toString(),
&main_, &stripe_);
}
return main_;
@@ -219,8 +347,8 @@ QColor RealTerminal::color() const
QColor RealTerminal::stripeColor() const
{
QColor main_, stripe_;
if (m_element) {
TerminalColor::parse(m_element->elementData().m_informations.value(colorInfoKey()).toString(),
if (hasData()) {
TerminalColor::parse(terminalData().m_informations.value(colorInfoKey()).toString(),
&main_, &stripe_);
}
return stripe_;
@@ -235,10 +363,10 @@ QColor RealTerminal::stripeColor() const
*/
qreal RealTerminal::section() const
{
if (m_element)
if (hasData())
{
bool ok_ = false;
const auto section_ = m_element->elementData().m_informations.value(sectionInfoKey()).toString().toDouble(&ok_);
const auto section_ = terminalData().m_informations.value(sectionInfoKey()).toString().toDouble(&ok_);
if (ok_ && section_ > 0) {
return section_;
}
@@ -255,10 +383,10 @@ qreal RealTerminal::section() const
*/
qreal RealTerminal::widthMm() const
{
if (m_element)
if (hasData())
{
bool ok_ = false;
const auto width_ = m_element->elementData().m_informations.value(widthInfoKey()).toString().toDouble(&ok_);
const auto width_ = terminalData().m_informations.value(widthInfoKey()).toString().toDouble(&ok_);
if (ok_ && width_ > 0) {
return width_;
}
@@ -276,8 +404,8 @@ qreal RealTerminal::widthMm() const
*/
QString RealTerminal::catalogEntry() const
{
if (m_element) {
return m_element->elementData().m_informations.value(catalogEntryInfoKey()).toString();
if (hasData()) {
return terminalData().m_informations.value(catalogEntryInfoKey()).toString();
}
return QString();
}
@@ -334,9 +462,9 @@ void RealTerminal::applyCatalogEntry(DiagramContext &informations, const QString
* Used to separate groups of terminals (voltages, functions...).
*/
bool RealTerminal::hasSeparatorAfter() const {
if (m_element) {
if (hasData()) {
return QET::infoFlagIsTrue(
m_element->elementData().m_informations.value(separatorInfoKey()).toString());
terminalData().m_informations.value(separatorInfoKey()).toString());
}
return false;
}
@@ -361,8 +489,8 @@ QString RealTerminal::conductor() const {
* @return
*/
ElementData::TerminalType RealTerminal::type() const {
if (m_element) {
return m_element->elementData().terminalType();
if (hasData()) {
return terminalData().terminalType();
} else {
return ElementData::TTGeneric;
}
@@ -373,8 +501,8 @@ ElementData::TerminalType RealTerminal::type() const {
* @return
*/
ElementData::TerminalFunction RealTerminal::function() const {
if (m_element) {
return m_element->elementData().terminalFunction();
if (hasData()) {
return terminalData().terminalFunction();
} else {
return ElementData::TFGeneric;
}
@@ -385,8 +513,8 @@ ElementData::TerminalFunction RealTerminal::function() const {
* @return
*/
bool RealTerminal::isLed() const {
if (m_element) {
return m_element->elementData().terminalLed();
if (hasData()) {
return terminalData().terminalLed();
} else {
return false;
}
@@ -443,6 +571,9 @@ Element *RealTerminal::element() const {
* else return a null uuid.
*/
QUuid RealTerminal::elementUuid() const {
if (m_spare) {
return m_spare_uuid; //Used to find the terminal (bridges...) like an element uuid
}
if (!m_element.isNull()) {
return m_element->uuid();
} else {
+13
View File
@@ -21,6 +21,7 @@
#include <QSharedPointer>
#include <QColor>
#include <QDomElement>
#include <QUuid>
#include "../properties/elementdata.h"
class TerminalStrip;
@@ -56,6 +57,12 @@ class RealTerminal
public:
~RealTerminal();
static QSharedPointer<RealTerminal> createSpare(const ElementData &data, const QUuid &uuid = QUuid());
static QSharedPointer<RealTerminal> spareFromXml(const QDomElement &xml_element);
bool isSpare() const;
ElementData spareData() const;
void setSpareData(const ElementData &data);
ElementData terminalData() const;
TerminalStrip *parentStrip() const noexcept;
QSharedPointer<PhysicalTerminal> physicalTerminal() const noexcept;
@@ -107,7 +114,13 @@ class RealTerminal
static QString xmlTagName();
private :
bool hasData() const;
QPointer<Element> m_element;
//A spare terminal has no element : it is on no folio, its data are its own
bool m_spare = false;
QUuid m_spare_uuid;
ElementData m_spare_data;
QWeakPointer<RealTerminal> m_this_weak;
QSharedPointer<PhysicalTerminal> m_physical_terminal;
};
+67 -2
View File
@@ -392,6 +392,60 @@ void TerminalStrip::removeTerminals(QVector<QSharedPointer<PhysicalTerminal> > p
}
}
/**
* @brief TerminalStrip::insertSpareTerminals
* Insert in this strip, at position @a pos, one physical terminal
* for each spare terminal of @a spare_terminals.
* @param pos : position of the first inserted terminal,
* out of range (-1...) to add the terminals at the end of the strip.
* @param spare_terminals : spare terminals not owned by a strip
* (RealTerminal::createSpare())
* @return the inserted physical terminals
*/
QVector<QSharedPointer<PhysicalTerminal>> TerminalStrip::insertSpareTerminals(int pos, const QVector<QSharedPointer<RealTerminal>> &spare_terminals)
{
QVector<QSharedPointer<PhysicalTerminal>> phy_vector;
for (const auto &real_t : spare_terminals)
{
if (real_t && real_t->isSpare() && !real_t->parentStrip()) {
auto raw_phy_ptr = new PhysicalTerminal(nullptr, QVector<QSharedPointer<RealTerminal>>{real_t});
phy_vector.append(raw_phy_ptr->sharedRef());
}
}
insertTerminals(pos, phy_vector);
return phy_vector;
}
/**
* @brief TerminalStrip::insertTerminals
* Insert @a phy_t_vector in this strip at position @a pos, in this order.
* The physical terminals already owned by a strip are ignored.
* @param pos : position of the first inserted terminal,
* out of range (-1...) to add the terminals at the end of the strip.
* @param phy_t_vector
*/
void TerminalStrip::insertTerminals(int pos, const QVector<QSharedPointer<PhysicalTerminal>> &phy_t_vector)
{
if (pos < 0 || pos > m_physical_terminals.size()) {
pos = m_physical_terminals.size();
}
bool added_{false};
for (const auto &phy_t : phy_t_vector)
{
if (!phy_t || phy_t->terminalStrip()) {
continue;
}
m_physical_terminals.insert(pos++, phy_t);
phy_t->setParentStrip(this);
added_ = true;
}
if (added_) {
emit orderChanged();
}
}
/**
* @brief TerminalStrip::pos
* @param terminal
@@ -1027,8 +1081,13 @@ bool TerminalStrip::fromXml(QDomElement &xml_element)
//tell where any symbol is
QSet<QUuid> terminals;
const QDomNodeList xml_reals = xml_layout.elementsByTagName(RealTerminal::xmlTagName());
for (int i = 0 ; i < xml_reals.size() ; ++i)
terminals.insert(QUuid(xml_reals.at(i).toElement().attribute(QStringLiteral("element_uuid"))));
for (int i = 0 ; i < xml_reals.size() ; ++i) {
//A spare terminal is on no folio
const auto element_uuid_ = xml_reals.at(i).toElement().attribute(QStringLiteral("element_uuid"));
if (!element_uuid_.isEmpty()) {
terminals.insert(QUuid(element_uuid_));
}
}
m_project->buildFoliosHoldingSymbols(terminals);
//Get all free elements terminal of the project: its own are on
@@ -1044,6 +1103,12 @@ bool TerminalStrip::fromXml(QDomElement &xml_element)
//Read each real terminal of the current physical terminal of the loop
for (auto &xml_real : QETXML::findInDomElement(xml_physical, RealTerminal::xmlTagName()))
{
//A spare terminal (on no folio) has its own data
if (auto spare_ = RealTerminal::spareFromXml(xml_real)) {
real_t_vector.append(spare_);
continue;
}
const auto uuid_ = QUuid(xml_real.attribute(QStringLiteral("element_uuid")));
bool found = false;
for (auto terminal_elmt : std::as_const(free_terminals))
+2
View File
@@ -95,6 +95,8 @@ class TerminalStrip : public QObject
void addTerminals (QVector<QSharedPointer<PhysicalTerminal>> phy_t_vector);
bool removeTerminal (QSharedPointer<PhysicalTerminal> phy_t);
void removeTerminals (QVector<QSharedPointer<PhysicalTerminal>> phy_t_vector);
QVector<QSharedPointer<PhysicalTerminal>> insertSpareTerminals(int pos, const QVector<QSharedPointer<RealTerminal>> &spare_terminals);
void insertTerminals(int pos, const QVector<QSharedPointer<PhysicalTerminal>> &phy_t_vector);
int pos(const QSharedPointer<PhysicalTerminal> &terminal) const;
int physicalTerminalCount() const;
@@ -49,6 +49,7 @@ struct modelRealTerminalData
mrtd.element_ = real_t->element();
mrtd.real_terminal = real_t.toWeakRef();
mrtd.bridged_ = real_t->isBridged();
mrtd.spare_ = real_t->isSpare();
}
return mrtd;
@@ -69,6 +70,7 @@ struct modelRealTerminalData
qreal width_mm_ = 0; //In mm, 0 = no width set by the user
QString catalog_entry_; //Terminal chosen in the catalog, empty if the width was typed
bool bridged_ = false;
bool spare_ = false; //Spare terminal, on no folio (RealTerminal::isSpare())
ElementData::TerminalType type_ = ElementData::TerminalType::TTGeneric;
ElementData::TerminalFunction function_ = ElementData::TerminalFunction::TFGeneric;
+218 -30
View File
@@ -34,10 +34,18 @@
#include "../terminalcolor.h"
#include "../terminalcatalog.h"
#include "../realterminal.h"
#include "../../qetinformation.h"
#include <QApplication>
#include <QCheckBox>
#include <QClipboard>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QKeyEvent>
#include <QLineEdit>
#include <QRegularExpression>
#include <QSpinBox>
#include <algorithm>
#include <limits>
@@ -218,37 +226,48 @@ void TerminalStripEditor::apply()
{
for (const auto &data_ : m_model->modifiedmodelRealTerminalData())
{
if (auto element = data_.element_)
{
auto current_data = element->elementData();
current_data.setTerminalType(data_.type_);
current_data.setTerminalFunction(data_.function_);
current_data.setTerminalLED(data_.led_);
current_data.m_informations.addValue(QStringLiteral("label"), data_.label_);
//The cable data are only stored when they are used
const auto set_info = [&current_data](const QString &key, const QString &value) {
if (!value.isEmpty() || current_data.m_informations.contains(key)) {
current_data.m_informations.addValue(key, value);
}
};
set_info(RealTerminal::cableInfoKey(), data_.cable_);
set_info(RealTerminal::cableWireInfoKey(), data_.cable_wire);
set_info(RealTerminal::shieldInfoKey(), data_.shield_ ? QStringLiteral("true") : QString());
//The color and the section are stored with the dot as decimal separator, whatever the locale
set_info(RealTerminal::colorInfoKey(), TerminalColor::toSpec(data_.color_, data_.stripe_color_));
set_info(RealTerminal::sectionInfoKey(), data_.section_ > 0 ? QString::number(data_.section_, 'g', 6) : QString());
set_info(RealTerminal::widthInfoKey(), data_.width_mm_ > 0 ? QString::number(data_.width_mm_, 'g', 6) : QString());
//The terminal chosen in the catalog, its manufacturer and its reference
//(only written when the user didn't type them)
RealTerminal::applyCatalogEntry(current_data.m_informations,
data_.width_mm_ > 0 ? data_.catalog_entry_ : QString());
set_info(RealTerminal::separatorInfoKey(), data_.separator_ ? QStringLiteral("true") : QString());
if (element->elementData() != current_data)
m_project->undoStack()->push(new ChangeElementDataCommand(element, current_data));
if (data_.level_ != data_.real_terminal.toStrongRef()->level())
m_project->undoStack()->push(new ChangeTerminalLevel(m_current_strip, data_.real_terminal, data_.level_));
const auto real_t = data_.real_terminal.toStrongRef();
auto element = data_.element_;
//A spare terminal (on no folio) has its own data, not the data of an element
if (!real_t || (!element && !data_.spare_)) {
continue;
}
const auto old_data = element ? element->elementData() : real_t->spareData();
auto current_data = old_data;
current_data.setTerminalType(data_.type_);
current_data.setTerminalFunction(data_.function_);
current_data.setTerminalLED(data_.led_);
current_data.m_informations.addValue(QStringLiteral("label"), data_.label_);
//The cable data are only stored when they are used
const auto set_info = [&current_data](const QString &key, const QString &value) {
if (!value.isEmpty() || current_data.m_informations.contains(key)) {
current_data.m_informations.addValue(key, value);
}
};
set_info(RealTerminal::cableInfoKey(), data_.cable_);
set_info(RealTerminal::cableWireInfoKey(), data_.cable_wire);
set_info(RealTerminal::shieldInfoKey(), data_.shield_ ? QStringLiteral("true") : QString());
//The color and the section are stored with the dot as decimal separator, whatever the locale
set_info(RealTerminal::colorInfoKey(), TerminalColor::toSpec(data_.color_, data_.stripe_color_));
set_info(RealTerminal::sectionInfoKey(), data_.section_ > 0 ? QString::number(data_.section_, 'g', 6) : QString());
set_info(RealTerminal::widthInfoKey(), data_.width_mm_ > 0 ? QString::number(data_.width_mm_, 'g', 6) : QString());
//The terminal chosen in the catalog, its manufacturer and its reference
//(only written when the user didn't type them)
RealTerminal::applyCatalogEntry(current_data.m_informations,
data_.width_mm_ > 0 ? data_.catalog_entry_ : QString());
set_info(RealTerminal::separatorInfoKey(), data_.separator_ ? QStringLiteral("true") : QString());
if (old_data != current_data)
{
if (element) {
m_project->undoStack()->push(new ChangeElementDataCommand(element, current_data));
} else {
m_project->undoStack()->push(new ChangeSpareTerminalDataCommand(real_t, current_data));
}
}
if (data_.level_ != real_t->level())
m_project->undoStack()->push(new ChangeTerminalLevel(m_current_strip, data_.real_terminal, data_.level_));
}
}
@@ -331,10 +350,12 @@ void TerminalStripEditor::selectionChanged()
ui->m_bridge_terminals_pb ->setDisabled(true);
ui->m_unbridge_terminals_pb->setDisabled(true);
ui->m_bridge_color_cb ->setDisabled(true);
ui->m_add_spare_pb ->setDisabled(true);
return;
}
ui->m_auto_ordering_pb->setEnabled(true);
ui->m_add_spare_pb ->setEnabled(true);
const auto index_list = ui->m_table_widget->selectionModel()->selectedIndexes();
@@ -1028,3 +1049,170 @@ void TerminalStripEditor::on_m_move_to_pb_clicked()
}
}
namespace {
/**
* @brief The NumberedLabel struct
* A terminal label split in a prefix and a number : "X1:12" is "X1:" and 12.
*/
struct NumberedLabel
{
QString prefix;
int number = 0; ///< 0 if the label has no number
int width = 0; ///< Number of digits, to keep the leading zeros ("X1:05")
};
/**
* @brief splitLabel
* @param label : a terminal label, like "X1:12", "12" or "PE"
* @return the label split in a prefix and its last number
*/
NumberedLabel splitLabel(const QString &label)
{
static const QRegularExpression re_(QStringLiteral("^(.*?)(\\d+)$"));
NumberedLabel numbered_;
const auto match_ = re_.match(label.trimmed());
if (match_.hasMatch())
{
numbered_.prefix = match_.captured(1);
numbered_.number = match_.captured(2).toInt();
const auto digits_ = match_.captured(2);
numbered_.width = digits_.startsWith(QLatin1Char('0')) ? digits_.size() : 0;
}
else {
numbered_.prefix = label.trimmed();
}
return numbered_;
}
/**
* @brief numberedLabel
* @return @a prefix followed by @a number, with leading zeros up to @a width digits
*/
QString numberedLabel(const QString &prefix, int number, int width) {
return prefix + QStringLiteral("%1").arg(number, width, 10, QLatin1Char('0'));
}
}
/**
* @brief TerminalStripEditor::on_m_add_spare_pb_clicked
* Add spare terminals (on no folio) after the selected terminal,
* or at the end of the strip if no terminal is selected.
* The labels continue the number of the selected terminal ("X1:12" ->
* "X1:13", "X1:14"...), the user can change or remove them in the table.
* The properties of the selected terminal (type, section, width, terminal
* of the catalog, color, manufacturer...) can be copied to the spare terminals,
* which then are the same part in the bill of materials.
*/
void TerminalStripEditor::on_m_add_spare_pb_clicked()
{
if (!m_model || !m_current_strip || !m_project) {
return;
}
//The last selected physical terminal in the strip, else the last one of the strip
int insert_pos_ = -1;
QSharedPointer<PhysicalTerminal> reference_;
const auto selected_ = m_model->modelPhysicalTerminalDataForIndex(
ui->m_table_widget->selectionModel()->selectedIndexes());
for (const auto &data_ : selected_)
{
const auto phy_t = m_current_strip->physicalTerminal(data_.uuid_);
const auto pos_ = m_current_strip->pos(phy_t);
if (phy_t && pos_ >= insert_pos_) {
insert_pos_ = pos_;
reference_ = phy_t;
}
}
if (reference_) {
++insert_pos_; //After the selected terminal
} else {
insert_pos_ = -1; //At the end of the strip
if (m_current_strip->physicalTerminalCount()) {
reference_ = m_current_strip->physicalTerminal(m_current_strip->physicalTerminalCount() - 1);
}
}
QSharedPointer<RealTerminal> reference_real_;
if (reference_ && !reference_->realTerminals().isEmpty()) {
reference_real_ = reference_->realTerminals().last();
}
//Proposed numbering : the one of the reference terminal, continued
const auto numbered_ = splitLabel(reference_real_ ? reference_real_->label() : QString());
QDialog dialog(this);
dialog.setWindowTitle(tr("Add spare terminals"));
auto layout_ = new QFormLayout(&dialog);
auto count_sb = new QSpinBox(&dialog);
count_sb->setRange(1, 999);
count_sb->setValue(1);
layout_->addRow(tr("Number of terminals :"), count_sb);
auto numbering_cb = new QCheckBox(tr("Number the terminals"), &dialog);
numbering_cb->setChecked(true);
layout_->addRow(numbering_cb);
auto prefix_le = new QLineEdit(numbered_.prefix, &dialog);
layout_->addRow(tr("Prefix :"), prefix_le);
auto first_sb = new QSpinBox(&dialog);
first_sb->setRange(0, 999999);
first_sb->setValue(numbered_.number + 1);
layout_->addRow(tr("First number :"), first_sb);
connect(numbering_cb, &QCheckBox::toggled, prefix_le, &QWidget::setEnabled);
connect(numbering_cb, &QCheckBox::toggled, first_sb, &QWidget::setEnabled);
auto copy_cb = new QCheckBox(tr("Same terminal as %1 (type, section, width, color, manufacturer...)")
.arg(reference_real_ && !reference_real_->label().isEmpty()
? reference_real_->label() : tr("the selected terminal")),
&dialog);
copy_cb->setChecked(!reference_real_.isNull());
copy_cb->setEnabled(!reference_real_.isNull());
layout_->addRow(copy_cb);
auto buttons_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
connect(buttons_, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(buttons_, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
layout_->addRow(buttons_);
if (dialog.exec() != QDialog::Accepted) {
return;
}
//The data shared by every spare terminal
ElementData model_;
if (copy_cb->isChecked() && reference_real_)
{
const auto reference_data_ = reference_real_->terminalData();
model_.setTerminalType(reference_data_.terminalType());
model_.setTerminalFunction(reference_data_.terminalFunction());
model_.setTerminalLED(reference_data_.terminalLed());
//What makes the part, not what belongs to this terminal (label, cable...)
for (const auto &key_ : {RealTerminal::sectionInfoKey(), RealTerminal::widthInfoKey(),
RealTerminal::catalogEntryInfoKey(), RealTerminal::colorInfoKey(),
QETInformation::ELMT_MANUFACTURER, QETInformation::ELMT_MANUFACTURER_REF,
QETInformation::ELMT_DESIGNATION, QETInformation::ELMT_PLANT,
QETInformation::ELMT_LOCATION})
{
const auto value_ = reference_data_.m_informations.value(key_).toString();
if (!value_.isEmpty()) {
model_.m_informations.addValue(key_, value_);
}
}
}
QVector<QSharedPointer<RealTerminal>> spare_vector;
for (int i = 0 ; i < count_sb->value() ; ++i)
{
auto data_ = model_;
if (numbering_cb->isChecked()) {
data_.m_informations.addValue(QStringLiteral("label"),
numberedLabel(prefix_le->text(), first_sb->value() + i, numbered_.width));
}
spare_vector.append(RealTerminal::createSpare(data_));
}
m_project->undoStack()->push(new AddSpareTerminalsCommand(m_current_strip, insert_pos_, spare_vector));
}
@@ -74,6 +74,7 @@ class TerminalStripEditor : public QWidget
void on_m_unbridge_terminals_pb_clicked();
void on_m_bridge_color_cb_activated(const QColor &col);
void on_m_move_to_pb_clicked();
void on_m_add_spare_pb_clicked();
private:
Ui::TerminalStripEditor *ui;
@@ -212,7 +212,17 @@
</property>
</widget>
</item>
<item row="16" column="0">
<item row="16" column="1" colspan="2">
<widget class="QPushButton" name="m_add_spare_pb">
<property name="toolTip">
<string>Add spare terminals, on no folio, after the selected terminal</string>
</property>
<property name="text">
<string>Add spare terminals...</string>
</property>
</widget>
</item>
<item row="17" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -61,6 +61,14 @@ const int COLUMN_COUNT = 19;
static QVector<bool> UNMODIFIED_CELL_VECTOR(COLUMN_COUNT, false);
/**
* @brief modifiedCellKey
* @return the key of the real terminal of @a data in the modified cells
*/
static const RealTerminal *modifiedCellKey(const modelRealTerminalData &data) {
return data.real_terminal.toStrongRef().data();
}
/**
* @brief parsePositiveNumber
* Convert the text written by the user to a number >= 0
@@ -204,7 +212,7 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const
case LEVEL_CELL : return mrtd.level_;
case LABEL_CELL : return mrtd.label_;
case CONDUCTOR_CELL : return mrtd.conductor_;
case XREF_CELL : return mrtd.Xref_;
case XREF_CELL : return mrtd.spare_ ? tr("Spare") : mrtd.Xref_;
case CABLE_CELL : return mrtd.cable_;
case CABLE_WIRE_CELL : return mrtd.cable_wire;
case TYPE_CELL : return ElementData::translatedTerminalType(mrtd.type_);
@@ -254,10 +262,16 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const
{
return mrtd.separator_ ? Qt::Checked : Qt::Unchecked;
}
else if (role == Qt::ForegroundRole &&
index.column() == XREF_CELL && mrtd.spare_)
{
return QBrush(Qt::gray); //"Spare" : not a reference to a folio
}
else if (role == Qt::BackgroundRole && index.column() < COLUMN_COUNT )
{
if (m_modified_cell.contains(mrtd.element_) &&
m_modified_cell.value(mrtd.element_).at(index.column()))
const auto key_ = modifiedCellKey(mrtd);
if (m_modified_cell.contains(key_) &&
m_modified_cell.value(key_).at(index.column()))
{
return QBrush(Qt::yellow);
}
@@ -425,17 +439,17 @@ bool TerminalStripModel::setData(const QModelIndex &index, const QVariant &value
{
replaceDataAtRow(mrtd, index.row());
if (mrtd.element_)
if (const auto key_ = modifiedCellKey(mrtd))
{
QVector<bool> vector_;
if (m_modified_cell.contains(mrtd.element_)) {
vector_ = m_modified_cell.value(mrtd.element_);
if (m_modified_cell.contains(key_)) {
vector_ = m_modified_cell.value(key_);
} else {
vector_ = UNMODIFIED_CELL_VECTOR;
}
vector_.replace(modified_cell, true);
m_modified_cell.insert(mrtd.element_, vector_);
m_modified_cell.insert(key_, vector_);
}
emit dataChanged(index, index);
return true;
@@ -509,7 +523,7 @@ QVector<modelRealTerminalData> TerminalStripModel::modifiedmodelRealTerminalData
for (auto j = 0 ; j < ptd_.real_data.size() ; ++j)
{
auto mrtd_ = ptd_.real_data.at(j);
if (modified_real_terminal.contains(mrtd_.element_))
if (modified_real_terminal.contains(modifiedCellKey(mrtd_)))
{
returned_vector.append(m_physical_data.at(i).real_data.at(j));
}
@@ -107,7 +107,8 @@ class TerminalStripModel : public QAbstractTableModel
private:
QPointer<TerminalStrip> m_terminal_strip;
QHash<QPointer<Element>, QVector<bool>> m_modified_cell;
//Modified cells of each real terminal (spare terminals have no element)
QHash<const RealTerminal *, QVector<bool>> m_modified_cell;
QVector<modelPhysicalTerminalData> m_physical_data;
struct BridgePixmap
{
+107
View File
@@ -40,6 +40,9 @@
#include "../titleblockproperties.h"
#include "../ElementsCollection/xmlelementcollection.h"
#include "../properties/elementdata.h"
#include "../TerminalStrip/terminalstrip.h"
#include "../TerminalStrip/physicalterminal.h"
#include "../TerminalStrip/realterminal.h"
#include <QLocale>
#include <QDate>
@@ -125,6 +128,10 @@ void projectDataBase::updateDB()
populateDiagramInfoTable();
populateElementTable();
populateElementInfoTable();
//Not the spare_terminal table : it is not built from the folios, and
//the database is made (and updateDB() called) by the constructor of the
//project, before its terminal strips exist. It is filled when a strip
//is added to the project or changed (spareTerminalsChanged()).
populateConductorTable();
populateLinkTable();
populateDrawingItemTables();
@@ -1694,6 +1701,70 @@ void projectDataBase::elementInfoChanged(QList<Element *> elements)
emit dataBaseUpdated();
}
/**
@brief projectDataBase::spareTerminalsChanged
The spare terminals of a terminal strip (on no folio) were added, removed
or changed : fill the spare_terminal table again.
*/
void projectDataBase::spareTerminalsChanged()
{
populateSpareTerminalTable();
emit dataBaseUpdated();
}
/**
@brief projectDataBase::populateSpareTerminalTable
Fill the spare_terminal table with the spare terminals of the terminal
strips of the project, lines of the nomenclature like the terminals
drawn on the folios (see createElementNomenclatureView()).
*/
void projectDataBase::populateSpareTerminalTable()
{
QSqlQuery query_(m_data_base);
query_.exec(QStringLiteral("DELETE FROM spare_terminal"));
if (!m_project) {
return;
}
const auto keys_ = QETInformation::elementInfoKeys();
QString insert_(QStringLiteral("INSERT INTO spare_terminal (uuid, diagram_position, title, folio, position"));
QString values_(QStringLiteral(" VALUES (:uuid, :diagram_position, :title, :folio, :position"));
for (const auto &key_ : keys_) {
insert_ += QStringLiteral(", ") + key_;
values_ += QStringLiteral(", :") + key_;
}
query_.prepare(insert_ + QStringLiteral(")") + values_ + QStringLiteral(")"));
//After the folios, strip by strip
int strip_index_ = 0;
for (const auto &strip_ : m_project->terminalStrip())
{
++strip_index_;
const auto physical_ = strip_->physicalTerminal();
for (int pos_ = 0 ; pos_ < physical_.size() ; ++pos_)
{
for (const auto &real_t : physical_.at(pos_)->realTerminals())
{
if (!real_t || !real_t->isSpare()) {
continue;
}
const auto informations_ = real_t->spareData().m_informations;
query_.bindValue(QStringLiteral(":uuid"), real_t->elementUuid().toString());
query_.bindValue(QStringLiteral(":diagram_position"), 1000000 + strip_index_);
query_.bindValue(QStringLiteral(":title"), QVariant());
query_.bindValue(QStringLiteral(":folio"), QVariant());
query_.bindValue(QStringLiteral(":position"), pos_);
for (const auto &key_ : keys_) {
query_.bindValue(QStringLiteral(":") + key_, informations_.value(key_).toString());
}
if (!query_.exec()) {
qDebug() << "projectDataBase::populateSpareTerminalTable insert error : " << query_.lastError();
}
}
}
}
}
void projectDataBase::addDiagram(Diagram *diagram)
{
m_content_changed = true;
@@ -2698,6 +2769,21 @@ bool projectDataBase::createDataBase()
}
}
//The spare terminals of the terminal strips : terminals on no folio,
//lines of the nomenclature like the terminals drawn on the folios
//(see createElementNomenclatureView()). One column by information,
//like element_info.
QString spare_terminal_table(QStringLiteral(
"CREATE TABLE spare_terminal(uuid VARCHAR(50) PRIMARY KEY NOT NULL, "
"diagram_position INTEGER, title VARCHAR(100), folio VARCHAR(100), position INTEGER"));
for (const auto &key_ : QETInformation::elementInfoKeys()) {
spare_terminal_table += QStringLiteral(", ") + key_ + QStringLiteral(" VARCHAR(100)");
}
spare_terminal_table += QStringLiteral(")");
if (!query_.exec(spare_terminal_table)) {
qDebug() << " spare_terminal_table query : " << query_.lastError();
}
createElementNomenclatureView();
createSummaryView();
createWiringListView();
@@ -2801,6 +2887,27 @@ void projectDataBase::createElementNomenclatureView()
//See discussion #847.
" AND e.type IN ('simple', 'terminal', 'master', 'slave', 'thumbnail')");
//The spare terminals of the terminal strips (on no folio) are lines of
//the nomenclature too : the same columns, read in the spare_terminal
//table. They come after the folios (diagram_position), in the order
//of their strip (position).
{
const QString select_ = QStringLiteral(" AS SELECT ");
const auto begin_ = create_view.indexOf(select_) + select_.size();
const auto end_ = create_view.indexOf(QStringLiteral(" FROM element_info ei"));
QString columns_ = create_view.mid(begin_, end_ - begin_);
columns_.replace(QStringLiteral("ei."), QStringLiteral("st."));
columns_.replace(QStringLiteral("d.pos AS diagram_position"), QStringLiteral("st.diagram_position AS diagram_position"));
columns_.replace(QStringLiteral("e.type AS element_type"), QStringLiteral("'terminal' AS element_type"));
columns_.replace(QStringLiteral("e.sub_type AS element_sub_type"), QStringLiteral("NULL AS element_sub_type"));
columns_.replace(QStringLiteral("di.title AS title"), QStringLiteral("st.title AS title"));
columns_.replace(QStringLiteral("di.folio AS folio"), QStringLiteral("st.folio AS folio"));
columns_.replace(QStringLiteral("e.pos AS position"), QStringLiteral("st.position AS position"));
create_view += QStringLiteral(" UNION ALL SELECT ") + columns_ +
QStringLiteral(" FROM spare_terminal st"
" WHERE COALESCE(LOWER(TRIM(st.exclude_from_bom)), '') NOT IN ('true', '1', 'yes', 'on')");
}
QSqlQuery query(m_data_base);
if (!query.exec(create_view)) {
qDebug() << query.lastError();
+2
View File
@@ -234,6 +234,7 @@ class projectDataBase : public QObject
void elementMoved();
public:
void spareTerminalsChanged();
signals:
void dataBaseUpdated();
@@ -250,6 +251,7 @@ class projectDataBase : public QObject
void populateElementTable();
void populateElementInfoTable();
void populateElementInfoTableFromFolios();
void populateSpareTerminalTable();
void populateDiagramInfoTable();
void populateConductorTable();
void populateLinkTable();
+29 -2
View File
@@ -4506,9 +4506,30 @@ TerminalStrip *QETProject::newTerminalStrip(QString installation, QString locati
this);
m_terminal_strip_vector.append(ts);
watchTerminalStrip(ts);
return ts;
}
/**
* @brief QETProject::watchTerminalStrip
* The spare terminals of @a strip (on no folio) are lines of the
* nomenclature : keep the database up to date with them.
* @param strip
* @param watch : false to stop watching @a strip
*/
void QETProject::watchTerminalStrip(TerminalStrip *strip, bool watch)
{
if (!strip) {
return;
}
disconnect(strip, nullptr, &m_data_base, nullptr);
if (watch) {
connect(strip, &TerminalStrip::orderChanged, &m_data_base, &projectDataBase::spareTerminalsChanged);
connect(strip, &TerminalStrip::dataChanged, &m_data_base, &projectDataBase::spareTerminalsChanged);
}
m_data_base.spareTerminalsChanged();
}
/**
* @brief QETProject::addTerminalStrip
* Add \p strip to the terminal strip list of the project.
@@ -4525,6 +4546,7 @@ bool QETProject::addTerminalStrip(TerminalStrip *strip)
return true;
m_terminal_strip_vector.append(strip);
watchTerminalStrip(strip);
return true;
}
@@ -4535,8 +4557,13 @@ bool QETProject::addTerminalStrip(TerminalStrip *strip)
* @param strip
* @return true if successfully removed.
*/
bool QETProject::removeTerminalStrip(TerminalStrip *strip) {
return m_terminal_strip_vector.removeOne(strip);
bool QETProject::removeTerminalStrip(TerminalStrip *strip)
{
const bool removed_ = m_terminal_strip_vector.removeOne(strip);
if (removed_) {
watchTerminalStrip(strip, false);
}
return removed_;
}
/**
+1
View File
@@ -443,6 +443,7 @@ class QETProject : public QObject
}
private:
void watchTerminalStrip(TerminalStrip *strip, bool watch = true);
bool stampSaved();
void readProjectXml(QDomDocument &xml_project);
void readDiagramsXml(QDomDocument &xml_project);