mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-11 06:34:14 +02:00
bf9b34398d
Cables are drawn line objects (toolbar button next to the auto break/reconnect button, click-move-click, right click cancels), not components. The type comes from a CSV catalog following the material list pattern (designation, cores, core colours, editable through the entry dialog with "Nouvelle entrée" / "Modifier l'entrée"). Each core is bound to the wire its colour label stands on, and the cable reference is generated per core and written into the Conductor entry via Conductor::setCableReference(label, cable uuid, slot). The conductor entry is where it has to live because the terminal strip plan (Klemmenplan) consumes it in the next step. Numbering: "Cables" rule under Programmeinstellungen/Neues Projekt/Nummerierung auto (mirrored in project properties), literal "W" fallback without a rule, French gate dialog when no rule exists, Nummerierung menu with the renumber dialog (preferred axis, per-folio counter, rule and counter state written back, hand-typed names ask, whole-project scope). Also included: label block and under-line texts with per-text font and alignment plus the material-list cell margins, free-core placement and right-click removal, claim questions for wires of other cables, multi-selection drag as one undo step, type change with confirmation, cross-references for type "Cable" (%f-%l%c, own font, clickable links in exported PDFs), report propagation of the cable definition, Listes menu (table of contents, material list, terminal strip manager, terminal generator plugin, cable list), cable list with freely selectable columns (installation/localisation of cable, start and end, Blatt start/end, length, used cores...), CSV export dialog with column pick and preview in the Projekt menu, script API (addTable kind "cable_list", exportCableList) and a unit test for the type catalog.
696 lines
23 KiB
C++
696 lines
23 KiB
C++
/*
|
|
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 "linkelementcommand.h"
|
|
|
|
#include "../cable/cablemanager.h"
|
|
#include "../conductorautonumerotation.h"
|
|
#include "../diagram.h"
|
|
#include "../diagramposition.h"
|
|
#include "../qetproject.h"
|
|
#include "../qetgraphicsitem/conductor.h"
|
|
#include "../qetgraphicsitem/element.h"
|
|
#include "../qetgraphicsitem/terminal.h"
|
|
#include "../ui/potentialselectordialog.h"
|
|
#include "../qetinformation.h"
|
|
#include "../properties/elementdata.h"
|
|
#include "../properties/xrefproperties.h"
|
|
#include "../autoNum/assignvariables.h"
|
|
#include "../autoNum/numerotationcontextcommands.h"
|
|
|
|
#include <algorithm>
|
|
#include <QCollator>
|
|
|
|
static const QString plcTerminalKeys[] = {
|
|
QETInformation::ELMT_PLC_T1,
|
|
QETInformation::ELMT_PLC_T2,
|
|
QETInformation::ELMT_PLC_T3,
|
|
QETInformation::ELMT_PLC_T4
|
|
};
|
|
|
|
/**
|
|
@brief Get the cross-ref text for a slave element using XRefProperties formula
|
|
@param master the PLC master element
|
|
@param slave the slave element
|
|
@return formatted cross-ref string (e.g. "(1-C4)")
|
|
*/
|
|
static QString plcCrossRefText(Element *master, Element *slave)
|
|
{
|
|
if (!master || !slave || !master->diagram()
|
|
|| !master->diagram()->project())
|
|
return QString();
|
|
XRefProperties xrp = master->diagram()->project()
|
|
->defaultXRefProperties("plc");
|
|
autonum::sequentialNumbers seq;
|
|
return autonum::AssignVariables::formulaToLabel(
|
|
xrp.slaveLabel(), seq, master->diagram(), master);
|
|
}
|
|
|
|
/**
|
|
@brief refreshCableLabelsForReport
|
|
A folio report carries a cable core over to the next sheet, so
|
|
making or breaking such a link changes which wires belong to which
|
|
cable. Work the cable fields out again straight away, so the wire
|
|
coming out of the linked arrow shows the cable right after the link
|
|
is made -- and drops it right after the link is taken away.
|
|
@param element the element the link was made or broken on
|
|
*/
|
|
static void refreshCableLabelsForReport(Element *element)
|
|
{
|
|
if (!element || !(element->linkType() & Element::AllReport)) return;
|
|
if (Diagram *diagram = element->diagram()) {
|
|
CableManager::refreshLabels(diagram->project());
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief LinkElementCommand::LinkElementCommand
|
|
Constructor
|
|
@param element_ : element where we work the link / unlink
|
|
@param parent : parent undo
|
|
*/
|
|
LinkElementCommand::LinkElementCommand(Element *element_, QUndoCommand *parent):
|
|
QUndoCommand(parent),
|
|
m_element(element_),
|
|
m_first_redo (true)
|
|
{
|
|
m_linked_before = m_linked_after = m_element->linkedElements();
|
|
setText(QObject::tr("Éditer les référence croisé", "edite the cross reference"));
|
|
}
|
|
|
|
/**
|
|
@brief LinkElementCommand::mergeWith
|
|
@param other try to merge this command with other
|
|
@return true if merge with success else false
|
|
*/
|
|
bool LinkElementCommand::mergeWith(const QUndoCommand *other)
|
|
{
|
|
if (id() != other->id() || other->childCount()) return false;
|
|
LinkElementCommand const *undo = static_cast<const LinkElementCommand *> (other);
|
|
if (m_element != undo->m_element) return false;
|
|
m_linked_after = undo->m_linked_after;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
@brief LinkElementCommand::isLinkable
|
|
@param element_a
|
|
@param element_b
|
|
@param already_linked
|
|
@return true if element_a and element_b can be linked between them.
|
|
There is few condition to be linked :
|
|
1- element_a and element_b must be linkable type. (Ex : A is master and B is slave 'OK', A and B is master 'KO')
|
|
2- For element type slave and report (no matter if element is 'A' or 'B'), the element must be free (not connected to an element)
|
|
3- we can override the section 2 by set already_linked to true. In this case, if slave or report is already
|
|
linked to the other element ('A' or 'B') return true, but if linked to another element (not 'A' or 'B') return false
|
|
*/
|
|
bool LinkElementCommand::isLinkable(Element *element_a, Element *element_b, bool already_linked)
|
|
{
|
|
switch(element_a->linkType())
|
|
{
|
|
case Element::Simple: return false;
|
|
|
|
case Element::NextReport:
|
|
{
|
|
//Type isn't good
|
|
if (element_b->linkType() != Element::PreviousReport) return false;
|
|
//two report is free
|
|
if (element_a->isFree() && element_b->isFree()) return true;
|
|
//Reports aren't free but are already linked between them and and already_linked is true
|
|
if (element_a->linkedElements().contains(element_b) && already_linked) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
case Element::PreviousReport:
|
|
{
|
|
//Type isn't good
|
|
if (element_b->linkType() != Element::NextReport) return false;
|
|
//two report is free
|
|
if (element_a->isFree() && element_b->isFree()) return true;
|
|
//Reports aren't free but are already linked between them and and already_linked is true
|
|
if (element_a->linkedElements().contains(element_b) && already_linked) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
case Element::Master:
|
|
{
|
|
//Type isn't good
|
|
if (element_b->linkType() != Element::Slave) return false;
|
|
|
|
// PLC master can only link to PLC slave, and vice versa
|
|
{
|
|
const bool master_is_plc = (element_a->elementData().m_master_type == ElementData::PLC);
|
|
const bool slave_is_plc = (element_b->elementData().m_slave_type == ElementData::PLCSlave);
|
|
if (master_is_plc != slave_is_plc) return false;
|
|
}
|
|
|
|
//element_b is free
|
|
if (element_b->isFree()) return true;
|
|
//element_b isn't free but already linked to element_a and already_linked is true
|
|
if (element_a->linkedElements().contains(element_b) && already_linked) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
case Element::Slave:
|
|
{
|
|
//Type isn't good
|
|
if (element_b->linkType() != Element::Master) return false;
|
|
|
|
// PLC master can only link to PLC slave, and vice versa
|
|
{
|
|
const bool slave_is_plc = (element_a->elementData().m_slave_type == ElementData::PLCSlave);
|
|
const bool master_is_plc = (element_b->elementData().m_master_type == ElementData::PLC);
|
|
if (slave_is_plc != master_is_plc) return false;
|
|
}
|
|
|
|
//Element_a is free
|
|
if (element_a->isFree()) return true;
|
|
//element_a isn't free but already linked to element_b and already_linked is true;
|
|
if (element_b->linkedElements().contains(element_a) && already_linked) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
case Element::Terminale: return false;
|
|
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief LinkElementCommand::setLink
|
|
Replace all linked elements of edited element
|
|
by elements stored in element_list
|
|
This method do several check to know if element can be linked or not.
|
|
@param element_list
|
|
*/
|
|
void LinkElementCommand::setLink(const QList<Element *>& element_list)
|
|
{
|
|
m_linked_after.clear();
|
|
setUpNewLink(element_list, true);
|
|
}
|
|
|
|
/**
|
|
@brief LinkElementCommand::setLink
|
|
This is an overloaded function.
|
|
@param element_
|
|
*/
|
|
void LinkElementCommand::setLink(Element *element_)
|
|
{
|
|
QList<Element *> list;
|
|
list << element_;
|
|
setLink(list);
|
|
}
|
|
|
|
/**
|
|
@brief LinkElementCommand::unlink
|
|
Unlink all elements of element_list from the edited element.
|
|
@param element_list
|
|
*/
|
|
void LinkElementCommand::unlink(QList<Element *> element_list)
|
|
{
|
|
foreach(Element *elmt, element_list)
|
|
m_linked_after.removeAll(elmt);
|
|
}
|
|
|
|
/**
|
|
@brief LinkElementCommand::unlinkAll
|
|
Unlink all element of the edited element
|
|
*/
|
|
void LinkElementCommand::unlinkAll()
|
|
{
|
|
m_linked_after.clear();
|
|
}
|
|
|
|
/**
|
|
@brief LinkElementCommand::undo
|
|
Undo this command
|
|
*/
|
|
void LinkElementCommand::undo()
|
|
{
|
|
if(m_element->diagram()) m_element->diagram()->showMe();
|
|
|
|
//Clear group index for slave elements when undoing
|
|
if (m_element->linkType() == Element::Slave)
|
|
{
|
|
int group_idx = m_group_index;
|
|
if (m_group_indices.contains(m_element))
|
|
group_idx = m_group_indices.value(m_element);
|
|
|
|
if (group_idx >= 0)
|
|
{
|
|
// Reset master labels on slave terminals
|
|
QList<Terminal *> slave_terms = m_element->terminals();
|
|
for (Terminal *t : slave_terms)
|
|
{
|
|
t->setUseMasterLabel(false);
|
|
t->setMasterLabelIndex(0);
|
|
}
|
|
|
|
// Clear PLC variables on slave when unlinking from PLC master
|
|
DiagramContext ctx = m_element->elementInformations();
|
|
ctx.remove(QETInformation::ELMT_PLC_TYPE);
|
|
ctx.remove(QETInformation::ELMT_PLC_ADDRESS);
|
|
ctx.remove(QETInformation::ELMT_PLC_FUNCTION);
|
|
ctx.remove(QETInformation::ELMT_PLC_COMMENT);
|
|
ctx.remove(QETInformation::ELMT_PLC_CROSSREF);
|
|
ctx.remove(QETInformation::ELMT_LABEL);
|
|
ctx.remove(QETInformation::ELMT_XREF);
|
|
m_element->setElementInformations(ctx);
|
|
|
|
foreach(Element *elmt, m_element->linkedElements())
|
|
{
|
|
if (elmt->linkType() == Element::Master)
|
|
{
|
|
elmt->setGroupIndexForElement(m_element, -1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (m_element->linkType() == Element::Master)
|
|
{
|
|
for (auto it = m_group_indices.constBegin(); it != m_group_indices.constEnd(); ++it)
|
|
{
|
|
Element *slave = it.key();
|
|
if (m_element->linkedElements().contains(slave))
|
|
{
|
|
// Reset master labels on slave terminals
|
|
QList<Terminal *> slave_terms = slave->terminals();
|
|
for (Terminal *t : slave_terms)
|
|
{
|
|
t->setUseMasterLabel(false);
|
|
t->setMasterLabelIndex(0);
|
|
}
|
|
|
|
// Clear PLC variables on slave when unlinking from PLC master
|
|
if (m_element->elementData().m_master_type == ElementData::PLC)
|
|
{
|
|
DiagramContext ctx = slave->elementInformations();
|
|
ctx.remove(QETInformation::ELMT_PLC_TYPE);
|
|
ctx.remove(QETInformation::ELMT_PLC_ADDRESS);
|
|
ctx.remove(QETInformation::ELMT_PLC_FUNCTION);
|
|
ctx.remove(QETInformation::ELMT_PLC_COMMENT);
|
|
ctx.remove(QETInformation::ELMT_PLC_CROSSREF);
|
|
ctx.remove(QETInformation::ELMT_LABEL);
|
|
ctx.remove(QETInformation::ELMT_XREF);
|
|
slave->setElementInformations(ctx);
|
|
}
|
|
|
|
m_element->setGroupIndexForElement(slave, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
makeLink(m_linked_before);
|
|
refreshCableLabelsForReport(m_element);
|
|
QUndoCommand::undo();
|
|
}
|
|
|
|
/**
|
|
@brief LinkElementCommand::redo
|
|
Redo this command
|
|
*/
|
|
void LinkElementCommand::redo()
|
|
{
|
|
if(m_element->diagram()) m_element->diagram()->showMe();
|
|
makeLink(m_linked_after);
|
|
|
|
//If the action is to link two reports together, and the conductors
|
|
//of the new potential disagree on a property that matters, a
|
|
//dialog asks what to do. See reportLinkNeedsPotentialChoice() for
|
|
//what "disagree" checks and the bug fixed there (bugtracker #974).
|
|
if (m_first_redo && (m_element->linkType() & Element::AllReport) \
|
|
&& m_element->conductors().size() \
|
|
&& m_linked_after.size() && m_linked_after.first()->conductors().size())
|
|
{
|
|
if (reportLinkNeedsPotentialChoice(m_element, m_linked_after.first()))
|
|
{
|
|
PotentialSelectorDialog psd(m_element, this);
|
|
psd.exec();
|
|
}
|
|
m_first_redo = false;
|
|
}
|
|
refreshCableLabelsForReport(m_element);
|
|
QUndoCommand::redo();
|
|
}
|
|
|
|
/**
|
|
@brief LinkElementCommand::reportLinkNeedsPotentialChoice
|
|
Whether linking these two report elements (next_report/previous_report)
|
|
would pop PotentialSelectorDialog -- i.e. whether their conductors (if
|
|
any exist yet, on either side) disagree on a property redo() cares
|
|
about. Exposed as its own static method, rather than left inline in
|
|
redo(), for the same reason ConductorCreator::needsPotentialChoice()
|
|
is: a caller with nobody there to answer a modal dialog (the scripting
|
|
API) can check first and decline, and the condition cannot drift away
|
|
from the one redo() actually applies.
|
|
|
|
Bug fixed here (bugtracker #974): the original check built ONE
|
|
combined list from three unrelated fields (tension_protocol,
|
|
wire_color, wire_section) and tested that whole list for equality --
|
|
comparing a tension-protocol string against a wire-colour string is
|
|
never equal even when each field individually matches across every
|
|
conductor, and wire_color/wire_section are ConductorProperties::
|
|
m_wire_color/m_wire_section, a separate free-text documentation pair
|
|
that says nothing about how the wire is actually drawn (that is
|
|
"color"/"style"). Net effect: the dialog could not reliably detect a
|
|
real mismatch, including the exact case #974 reported -- two
|
|
report-linked conductors drawn in different colours -- and could just
|
|
as easily fire on conductors that matched in every way that mattered.
|
|
Comparing each relevant field (text/num, function, tension protocol,
|
|
colour, line style) on its own fixes both.
|
|
|
|
@param element_a @param element_b the two elements about to be (or
|
|
already) linked; order does not matter
|
|
@return true if the dialog would (or does) open
|
|
*/
|
|
bool LinkElementCommand::reportLinkNeedsPotentialChoice(Element *element_a, Element *element_b)
|
|
{
|
|
if (!element_a || !element_b) return false;
|
|
if (element_a->conductors().isEmpty() || element_b->conductors().isEmpty()) return false;
|
|
|
|
QSet<Conductor *> c_list;
|
|
for (Element *e : {element_a, element_b})
|
|
{
|
|
if (e->conductors().isEmpty()) continue;
|
|
c_list << e->conductors().first();
|
|
c_list += e->conductors().first()->relatedPotentialConductors();
|
|
}
|
|
if (c_list.size() < 2) return false;
|
|
|
|
QStringList str_txt, str_funct, str_tens, str_color, str_style;
|
|
for (const Conductor *c : std::as_const(c_list))
|
|
{
|
|
str_txt << c->properties().text;
|
|
str_funct << c->properties().m_function;
|
|
str_tens << c->properties().m_tension_protocol;
|
|
str_color << c->properties().color.name();
|
|
str_style << QString::number(int(c->properties().style));
|
|
}
|
|
return !QET::eachStrIsEqual(str_txt) || !QET::eachStrIsEqual(str_funct)
|
|
|| !QET::eachStrIsEqual(str_tens) || !QET::eachStrIsEqual(str_color)
|
|
|| !QET::eachStrIsEqual(str_style);
|
|
}
|
|
|
|
/**
|
|
@brief LinkElementCommand::setUpNewLink
|
|
Update the content of m_link_after with the content of element_list.
|
|
Each linkable element (know via the static method isLinkable)
|
|
is added to m_linked_after
|
|
already_link is used for the static method isLinkable.
|
|
@param element_list
|
|
@param already_link
|
|
*/
|
|
void LinkElementCommand::setUpNewLink(
|
|
const QList<Element *> &element_list, bool already_link)
|
|
{
|
|
//m_element is a master we can connect several element to it
|
|
//if m_element isn't master (may be a report or slave) we can connect only one element
|
|
if (m_element->linkType() == Element::Master || element_list.size() == 1)
|
|
{
|
|
foreach(Element *elmt, element_list)
|
|
if (isLinkable(m_element, elmt, already_link))
|
|
m_linked_after << elmt;
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "LinkElementCommand::setUpNewLink : try to link several elements to a report element or slave element,"
|
|
" only the first element of the list will be taken to be linked";
|
|
foreach(Element *elmt, element_list)
|
|
if (isLinkable(m_element, elmt, already_link))
|
|
{
|
|
m_linked_after << elmt;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief LinkElementCommand::makeLink
|
|
Make the link between m_element and element_list;
|
|
This method unlinks elements if needed.
|
|
@param element_list
|
|
*/
|
|
void LinkElementCommand::makeLink(const QList<Element *> &element_list)
|
|
{
|
|
//List is empty, that means m_element must be free, so we unlink all elements
|
|
if (element_list.isEmpty())
|
|
{
|
|
m_element->unlinkAllElements();
|
|
return;
|
|
}
|
|
|
|
//We link all element from element_list
|
|
foreach(Element *elmt, element_list)
|
|
m_element->linkToElement(elmt);
|
|
|
|
//Set group index for slave-master links
|
|
if (m_element->linkType() == Element::Slave)
|
|
{
|
|
int group_idx = m_group_index;
|
|
|
|
//Check if we have a per-slave group index
|
|
if (m_group_indices.contains(m_element))
|
|
group_idx = m_group_indices.value(m_element);
|
|
|
|
if (group_idx >= 0)
|
|
{
|
|
foreach(Element *elmt, element_list)
|
|
{
|
|
if (elmt->linkType() == Element::Master)
|
|
{
|
|
elmt->setGroupIndexForElement(m_element, group_idx);
|
|
|
|
// Set master labels on slave terminals
|
|
if (elmt->elementData().m_master_type == ElementData::PLC)
|
|
{
|
|
const auto &plc_data = elmt->elementData().plcMasterData();
|
|
if (group_idx < plc_data.ios.size())
|
|
{
|
|
const QStringList labels = plc_data.ios.at(group_idx).effectiveTerminals();
|
|
QList<Terminal *> slave_terms = m_element->terminals();
|
|
QCollator collator;
|
|
collator.setNumericMode(true);
|
|
std::sort(slave_terms.begin(), slave_terms.end(),
|
|
[&collator](Terminal *a, Terminal *b) {
|
|
return collator.compare(a->baseName(), b->baseName()) < 0;
|
|
});
|
|
for (int i = 0; i < slave_terms.size(); ++i)
|
|
{
|
|
if (i < labels.size())
|
|
{
|
|
slave_terms.at(i)->setUseMasterLabel(true);
|
|
slave_terms.at(i)->setMasterLabelIndex(i);
|
|
}
|
|
}
|
|
|
|
// Populate PLC variables on the slave
|
|
const auto &io = plc_data.ios.at(group_idx);
|
|
DiagramContext ctx = m_element->elementInformations();
|
|
ctx.addValue(QETInformation::ELMT_PLC_TYPE,
|
|
ElementData::translatedPlcIOType(io.type));
|
|
ctx.addValue(QETInformation::ELMT_PLC_ADDRESS, io.address);
|
|
ctx.addValue(QETInformation::ELMT_PLC_FUNCTION, io.functionText);
|
|
ctx.addValue(QETInformation::ELMT_PLC_COMMENT, io.comment);
|
|
ctx.addValue(QETInformation::ELMT_PLC_CROSSREF,
|
|
plcCrossRefText(elmt, m_element));
|
|
ctx.addValue(QETInformation::ELMT_LABEL,
|
|
elmt->actualLabel());
|
|
ctx.addValue(QETInformation::ELMT_PLC_TC,
|
|
QString::number(io.terminalCount));
|
|
const QStringList eff_terms = io.effectiveTerminals();
|
|
for (int t = 0; t < io.terminalCount && t < 4; ++t)
|
|
{
|
|
QString val = (t < eff_terms.size())
|
|
? eff_terms.at(t) : QString();
|
|
ctx.addValue(plcTerminalKeys[t], val);
|
|
}
|
|
m_element->setElementInformations(ctx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const auto &groups = elmt->elementData().m_slave_contact_groups;
|
|
if (group_idx < groups.size())
|
|
{
|
|
const QStringList &labels = groups.at(group_idx).labels;
|
|
QList<Terminal *> slave_terms = m_element->terminals();
|
|
QCollator collator;
|
|
collator.setNumericMode(true);
|
|
std::sort(slave_terms.begin(), slave_terms.end(),
|
|
[&collator](Terminal *a, Terminal *b) {
|
|
return collator.compare(a->baseName(), b->baseName()) < 0;
|
|
});
|
|
for (int i = 0; i < slave_terms.size(); ++i)
|
|
{
|
|
if (i < labels.size())
|
|
{
|
|
slave_terms.at(i)->setUseMasterLabel(true);
|
|
slave_terms.at(i)->setMasterLabelIndex(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (m_element->linkType() == Element::Master)
|
|
{
|
|
//For master linking to slaves, set group indices for each slave
|
|
for (auto it = m_group_indices.constBegin(); it != m_group_indices.constEnd(); ++it)
|
|
{
|
|
Element *slave = it.key();
|
|
int group_idx = it.value();
|
|
if (group_idx >= 0 && element_list.contains(slave))
|
|
{
|
|
m_element->setGroupIndexForElement(slave, group_idx);
|
|
|
|
// Set master labels on slave terminals
|
|
if (m_element->elementData().m_master_type == ElementData::PLC)
|
|
{
|
|
const auto &plc_data = m_element->elementData().plcMasterData();
|
|
if (group_idx < plc_data.ios.size())
|
|
{
|
|
const QStringList labels = plc_data.ios.at(group_idx).effectiveTerminals();
|
|
QList<Terminal *> slave_terms = slave->terminals();
|
|
QCollator collator;
|
|
collator.setNumericMode(true);
|
|
std::sort(slave_terms.begin(), slave_terms.end(),
|
|
[&collator](Terminal *a, Terminal *b) {
|
|
return collator.compare(a->baseName(), b->baseName()) < 0;
|
|
});
|
|
for (int i = 0; i < slave_terms.size(); ++i)
|
|
{
|
|
if (i < labels.size())
|
|
{
|
|
slave_terms.at(i)->setUseMasterLabel(true);
|
|
slave_terms.at(i)->setMasterLabelIndex(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const auto &groups = m_element->elementData().m_slave_contact_groups;
|
|
if (group_idx < groups.size())
|
|
{
|
|
const QStringList &labels = groups.at(group_idx).labels;
|
|
QList<Terminal *> slave_terms = slave->terminals();
|
|
QCollator collator;
|
|
collator.setNumericMode(true);
|
|
std::sort(slave_terms.begin(), slave_terms.end(),
|
|
[&collator](Terminal *a, Terminal *b) {
|
|
return collator.compare(a->baseName(), b->baseName()) < 0;
|
|
});
|
|
for (int i = 0; i < slave_terms.size(); ++i)
|
|
{
|
|
if (i < labels.size())
|
|
{
|
|
slave_terms.at(i)->setUseMasterLabel(true);
|
|
slave_terms.at(i)->setMasterLabelIndex(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Populate PLC variables on the slave if master is PLC type
|
|
if (m_element->elementData().m_master_type == ElementData::PLC)
|
|
{
|
|
const auto &plc_data = m_element->elementData().plcMasterData();
|
|
if (group_idx < plc_data.ios.size())
|
|
{
|
|
const auto &io = plc_data.ios.at(group_idx);
|
|
DiagramContext ctx = slave->elementInformations();
|
|
ctx.addValue(QETInformation::ELMT_PLC_TYPE,
|
|
ElementData::translatedPlcIOType(io.type));
|
|
ctx.addValue(QETInformation::ELMT_PLC_ADDRESS, io.address);
|
|
ctx.addValue(QETInformation::ELMT_PLC_FUNCTION, io.functionText);
|
|
ctx.addValue(QETInformation::ELMT_PLC_COMMENT, io.comment);
|
|
ctx.addValue(QETInformation::ELMT_PLC_CROSSREF,
|
|
plcCrossRefText(m_element, slave));
|
|
ctx.addValue(QETInformation::ELMT_LABEL,
|
|
m_element->actualLabel());
|
|
ctx.addValue(QETInformation::ELMT_PLC_TC,
|
|
QString::number(io.terminalCount));
|
|
const QStringList eff_terms = io.effectiveTerminals();
|
|
for (int t = 0; t < io.terminalCount && t < 4; ++t)
|
|
{
|
|
QString val = (t < eff_terms.size())
|
|
? eff_terms.at(t) : QString();
|
|
ctx.addValue(plcTerminalKeys[t], val);
|
|
}
|
|
slave->setElementInformations(ctx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* At this point there may be unwanted linked elements to m_element.
|
|
* We must unlink it.
|
|
* Elements from element_list are wanted so we compare element_list
|
|
* to current linked element of m_element
|
|
*/
|
|
QList<Element *> to_unlink = m_element->linkedElements();
|
|
foreach(Element *elmt, element_list)
|
|
to_unlink.removeAll(elmt);
|
|
|
|
//All elements stored in to_unlink is unwanted we unlink it from m_element
|
|
if (!to_unlink.isEmpty())
|
|
{
|
|
foreach(Element *elmt, to_unlink)
|
|
{
|
|
// Clear PLC variables on the slave before unlinking
|
|
// The slave is either elmt (if m_element is master) or m_element (if m_element is slave)
|
|
Element *slave = nullptr;
|
|
if (m_element->elementData().m_master_type == ElementData::PLC)
|
|
slave = elmt;
|
|
else if (m_element->linkType() == Element::Slave)
|
|
{
|
|
for (Element *linked : m_element->linkedElements())
|
|
{
|
|
if (linked->elementData().m_master_type == ElementData::PLC)
|
|
{
|
|
slave = m_element;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (slave)
|
|
{
|
|
DiagramContext ctx = slave->elementInformations();
|
|
ctx.remove(QETInformation::ELMT_PLC_TYPE);
|
|
ctx.remove(QETInformation::ELMT_PLC_ADDRESS);
|
|
ctx.remove(QETInformation::ELMT_PLC_FUNCTION);
|
|
ctx.remove(QETInformation::ELMT_PLC_COMMENT);
|
|
ctx.remove(QETInformation::ELMT_PLC_CROSSREF);
|
|
ctx.remove(QETInformation::ELMT_LABEL);
|
|
ctx.remove(QETInformation::ELMT_XREF);
|
|
slave->setElementInformations(ctx);
|
|
}
|
|
m_element->unlinkElement(elmt);
|
|
}
|
|
}
|
|
}
|