PLC Manager

This commit is contained in:
Kellermorph
2026-07-27 22:16:52 +02:00
parent 0a4b337115
commit b3a8ae898e
32 changed files with 4157 additions and 177 deletions
+219 -2
View File
@@ -28,6 +28,7 @@
#include "elementtextitemgroup.h"
#include "qgraphicsitemutility.h"
#include "terminal.h"
#include "../properties/elementdata.h"
//define the height of the header.
static int header = 5;
@@ -235,8 +236,11 @@ void CrossRefItem::updateLabel()
qp.setPen(pen_);
qp.setFont(QETApp::diagramTextsFont(5));
//Draw cross or contact, only if master element is linked.
if (! m_element->linkedElements().isEmpty())
// PLC table is drawn by Element::drawPlcTable(), not by CrossRefItem
if (m_element->elementData().m_master_type == ElementData::PLC)
return;
//Draw cross or contact, only if master element is linked.
else if (! m_element->linkedElements().isEmpty())
{
m_update_map = true;
XRefProperties::DisplayHas dh = m_properties.displayHas();
@@ -325,6 +329,11 @@ void CrossRefItem::paint(
// caused a use-after-free crash (QRegion::begin, Qt5Gui+0x49af60)
// confirmed by analysis of 19+ coredumps.
// m_update_map=false: draw functions do not overwrite m_hovered_contacts_map.
// PLC table is drawn by Element::drawPlcTable(), not by CrossRefItem
if (m_element->elementData().m_master_type == ElementData::PLC)
return;
if (m_element->linkedElements().isEmpty()) return;
QPen pen_;
@@ -1336,3 +1345,211 @@ QList<Element *> CrossRefItem::NCElements() const
return nc_list;
}
/**
* @brief CrossRefItem::drawAsPlcTable
* Draw the PLC IO table for PLC master elements.
* The table shows columns: Type | Address | Function | Comment | CrossRef
* Columns and rows are configurable via PlcMasterData.
* @param painter painter to use
*/
void CrossRefItem::drawAsPlcTable(QPainter &painter)
{
// Get PLC data from the element
ElementData ed = m_element->elementData();
if (ed.m_master_type != ElementData::PLC)
return;
ElementData::PlcMasterData plc_data = ed.plcMasterData();
if (plc_data.ios.isEmpty())
return;
// Define column indices
const int COL_TYPE = 0;
const int COL_ADDRESS = 1;
const int COL_FUNCTION = 2;
const int COL_COMMENT = 3;
const int COL_CROSSREF = 4;
const int COL_COUNT = 5;
// Column headers (French)
QMap<int, QString> headers;
headers[COL_TYPE] = QObject::tr("Type");
headers[COL_ADDRESS] = QObject::tr("Adresse");
headers[COL_FUNCTION] = QObject::tr("Fonction");
headers[COL_COMMENT] = QObject::tr("Commentaire");
headers[COL_CROSSREF] = QObject::tr("Réf. croisée");
// Build list of visible columns
QList<int> visible_cols;
for (int i = 0; i < COL_COUNT; ++i) {
if (plc_data.colVisible.value(i, true))
visible_cols.append(i);
}
if (visible_cols.isEmpty())
return;
// Default column widths if not set (in scene units, roughly mm * 2.835)
QMap<int, qreal> col_widths;
for (int col : visible_cols) {
if (plc_data.colWidths.contains(col) && plc_data.colWidths[col] > 0)
col_widths[col] = plc_data.colWidths[col];
else {
switch (col) {
case COL_TYPE: col_widths[col] = 35; break;
case COL_ADDRESS: col_widths[col] = 25; break;
case COL_FUNCTION: col_widths[col] = 50; break;
case COL_COMMENT: col_widths[col] = 40; break;
case COL_CROSSREF: col_widths[col] = 30; break;
default: col_widths[col] = 30; break;
}
}
}
qreal row_h = plc_data.rowHeight > 0 ? plc_data.rowHeight : 8.0;
qreal header_h = row_h + 2.0;
// Calculate total width
qreal total_width = 0;
for (int col : visible_cols)
total_width += col_widths[col];
// Calculate total height: header + all IO rows
int total_ios = plc_data.ios.size();
// Collect active break positions (sorted)
QList<int> breaks;
for (int bp : plc_data.breakPositions) {
if (bp > 0 && bp < total_ios && !breaks.contains(bp))
breaks.append(bp);
}
std::sort(breaks.begin(), breaks.end());
// Build block boundaries
QList<int> block_starts;
block_starts.append(0);
for (int bp : breaks)
block_starts.append(bp);
qreal total_height;
int block_count = block_starts.size();
if (block_count > 1) {
int max_rows = 0;
for (int b = 0; b < block_count; ++b) {
int start = block_starts.at(b);
int end = (b + 1 < block_starts.size()) ? block_starts.at(b + 1) : total_ios;
max_rows = qMax(max_rows, end - start);
}
total_height = header_h + max_rows * row_h;
total_width = total_width * block_count + (block_count - 1) * 3;
} else {
total_height = header_h + total_ios * row_h;
}
// Draw background rectangle
QRectF bg_rect(0, 0, total_width, total_height);
painter.fillRect(bg_rect, Qt::white);
QPen border_pen(Qt::black, 0.5);
painter.setPen(border_pen);
painter.drawRect(bg_rect);
// Draw header row
qreal x_offset = 0;
for (int block = 0; block < block_count; ++block) {
qreal block_x = block * (col_widths.value(visible_cols.first(), 30) * visible_cols.size() + 3);
qreal cx = block_x;
// Draw column headers
QFont header_font = painter.font();
header_font.setBold(true);
painter.setFont(header_font);
for (int col : visible_cols) {
QRectF header_rect(cx, 0, col_widths[col], header_h);
painter.fillRect(header_rect, QColor(220, 220, 220));
painter.drawRect(header_rect);
painter.drawText(header_rect, Qt::AlignCenter, headers[col]);
cx += col_widths[col];
}
painter.setFont(painter.font());
// Draw IO rows for this block
int start_idx = block_starts.at(block);
int end_idx = (block + 1 < block_starts.size()) ? block_starts.at(block + 1) : total_ios;
for (int row = 0; row < (end_idx - start_idx); ++row) {
int io_idx = start_idx + row;
const ElementData::PlcIO &io = plc_data.ios.at(io_idx);
qreal ry = header_h + row * row_h;
cx = block_x;
// Get linked slave element for this IO (via group index)
QString cross_ref_text = io.crossRef;
for (Element *slave : m_element->linkedElements()) {
if (m_element->groupIndexForElement(slave) == io_idx) {
cross_ref_text = elementPositionText(slave);
break;
}
}
for (int col : visible_cols) {
QRectF cell_rect(cx, ry, col_widths[col], row_h);
painter.drawRect(cell_rect);
QString cell_text;
switch (col) {
case COL_TYPE:
cell_text = ElementData::translatedPlcIOType(io.type);
break;
case COL_ADDRESS:
cell_text = io.address;
break;
case COL_FUNCTION:
cell_text = io.functionText;
break;
case COL_COMMENT:
cell_text = io.comment;
break;
case COL_CROSSREF:
cell_text = cross_ref_text;
break;
}
QRectF text_rect = cell_rect.adjusted(1, 0, -1, 0);
painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, cell_text);
// If function text doesn't fit, try to show as much as possible
if (col == COL_FUNCTION && !io.functionText.isEmpty()) {
QFontMetrics fm(painter.font());
QString elided = fm.elidedText(io.functionText, Qt::ElideRight, text_rect.width());
// Clear and redraw with elided text
painter.save();
painter.setBrush(Qt::white);
painter.drawRect(text_rect);
painter.restore();
painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, elided);
}
// Store hover map for cross-reference cells
if (col == COL_CROSSREF && m_update_map && !cross_ref_text.isEmpty()) {
for (Element *slave : m_element->linkedElements()) {
if (m_element->groupIndexForElement(slave) == io_idx) {
m_hovered_contacts_map.insert(slave, cell_rect);
break;
}
}
}
cx += col_widths[col];
}
}
}
prepareGeometryChange();
m_bounding_rect = bg_rect;
m_shape_path.addRect(bg_rect);
}
+1
View File
@@ -106,6 +106,7 @@ class CrossRefItem : public QGraphicsObject
void setUpCrossBoundingRect(QPainter &painter);
void drawAsCross(QPainter &painter);
void drawAsContacts(QPainter &painter);
void drawAsPlcTable(QPainter &painter);
QRectF drawContact(QPainter &painter, int flags, Element *elmt, int pole_index = 0);
void fillCrossRef(QPainter &painter);
void AddExtraInfo(QPainter &painter, const QString&);
@@ -280,6 +280,10 @@ Element *DynamicElementTextItem::elementUseForInfo() const
return elmt;
case Element::Slave:
{
//PLC slave stores its own PLC variables in elementInformations
//(populated from master on link). Return self so %{plc_*} vars resolve.
if (elmt->elementData().m_slave_type == ElementData::PLCSlave)
return elmt;
if(elmt->linkedElements().isEmpty())
return nullptr;
else
@@ -1347,18 +1351,20 @@ void DynamicElementTextItem::updateXref()
}
}
}
else if (m_parent_element.data()->linkType() == Element::Slave)
else if (m_parent_element.data()->linkType() == Element::Slave &&
!m_parent_element.data()->linkedElements().isEmpty())
{
if(m_master_element && !parentGroup() &&
Element *master_elmt = m_parent_element.data()->linkedElements().first();
if(master_elmt && !parentGroup() &&
(
(m_text_from == DynamicElementTextItem::ElementInfo && m_info_name == "label") ||
(m_text_from == DynamicElementTextItem::CompositeText && m_composite_text.contains("%{label}"))
)
)
{
XRefProperties xrp = diagram()->project()->defaultXRefProperties(m_master_element.data()->kindInformations()["type"].toString());
XRefProperties xrp = diagram()->project()->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
QString xref_label = xrp.slaveLabel();
xref_label = autonum::AssignVariables::formulaToLabel(xref_label, m_master_element.data()->rSequenceStruct(), m_master_element.data()->diagram(), m_master_element.data());
xref_label = autonum::AssignVariables::formulaToLabel(xref_label, master_elmt->rSequenceStruct(), master_elmt->diagram(), master_elmt);
if(!m_slave_Xref_item)
{
@@ -1367,9 +1373,9 @@ void DynamicElementTextItem::updateXref()
m_slave_Xref_item->setDefaultTextColor(Qt::black);
m_slave_Xref_item->installSceneEventFilter(this);
m_update_slave_Xref_connection << connect(m_master_element.data(), &Element::xChanged, this, &DynamicElementTextItem::updateXref);
m_update_slave_Xref_connection << connect(m_master_element.data(), &Element::yChanged, this, &DynamicElementTextItem::updateXref);
m_update_slave_Xref_connection << connect(m_master_element.data(), &Element::elementInfoChange, this, &DynamicElementTextItem::updateXref);
m_update_slave_Xref_connection << connect(master_elmt, &Element::xChanged, this, &DynamicElementTextItem::updateXref);
m_update_slave_Xref_connection << connect(master_elmt, &Element::yChanged, this, &DynamicElementTextItem::updateXref);
m_update_slave_Xref_connection << connect(master_elmt, &Element::elementInfoChange, this, &DynamicElementTextItem::updateXref);
m_update_slave_Xref_connection << connect(diagram(), &Diagram::diagramInformationChanged, this, &DynamicElementTextItem::updateXref);
m_update_slave_Xref_connection << connect(diagram()->project(), &QETProject::projectDiagramsOrderChanged, this, &DynamicElementTextItem::updateXref);
m_update_slave_Xref_connection << connect(diagram()->project(), &QETProject::diagramRemoved, this, &DynamicElementTextItem::updateXref);
@@ -1439,9 +1445,12 @@ void DynamicElementTextItem::setPlainText(const QString &text)
}
else if (m_slave_Xref_item)
{
XRefProperties xrp = diagram()->project()->defaultXRefProperties(m_master_element.data()->kindInformations()["type"].toString());
setXref_item(xrp.getXrefPos());
Element *master_elmt = m_parent_element.data()->linkedElements().isEmpty()
? nullptr : m_parent_element.data()->linkedElements().first();
if (master_elmt) {
XRefProperties xrp = diagram()->project()->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
setXref_item(xrp.getXrefPos());
}
}
}
+274 -4
View File
@@ -18,6 +18,7 @@
#include "element.h"
#include "../qetproject.h"
#include "../PropertiesEditor/propertieseditordialog.h"
#include "../autoNum/assignvariables.h"
#include "../autoNum/numerotationcontextcommands.h"
#include "../diagram.h"
#include "../diagramcommands.h"
@@ -26,6 +27,8 @@
#include "../elementprovider.h"
#include "../factory/elementpicturefactory.h"
#include "../properties/terminaldata.h"
#include "../properties/xrefproperties.h"
#include "../qetinformation.h"
#include "../qetgraphicsitem/conductor.h"
#include "../qetgraphicsitem/terminal.h"
#include "../ui/elementpropertieswidget.h"
@@ -229,13 +232,25 @@ void Element::paint(
painter->setBrush(brush);
if (options && options->levelOfDetailFromTransform(painter->worldTransform()) < 0.5)
{
painter->drawPicture(0, 0, m_low_zoom_picture);
if (!m_low_zoom_picture.isNull())
painter->drawPicture(0, 0, m_low_zoom_picture);
} else {
painter->drawPicture(0, 0, m_picture);
if (!m_picture.isNull())
painter->drawPicture(0, 0, m_picture);
}
painter->restore(); //Restore the QPainter after use drawPicture
// Draw PLC table from in-memory data (overwrites the stale cached picture)
if (scene() && painter->isActive() &&
m_data.m_type == ElementData::Master &&
m_data.m_master_type == ElementData::PLC)
{
const auto &plc_data = m_data.plcMasterData();
if (!plc_data.ios.isEmpty())
drawPlcTable(painter);
}
//Draw the selection rectangle
if ( isSelected() || m_mouse_over ) {
QGIUtility::drawBoundingRectSelection(this, painter);
@@ -486,6 +501,13 @@ bool Element::buildFromXml(const QDomElement &xml_def_elmt, int *state)
if (qde.isNull())
continue;
// Store plc_table positions for runtime rendering
if (qde.tagName() == QLatin1String("plc_table")) {
qreal x = qde.attribute("x", "0").toDouble();
qreal y = qde.attribute("y", "0").toDouble();
m_plc_table_positions.append(QPointF(x, y));
}
if (parseElement(qde)) {
++ parsed_elements_count;
}
@@ -502,8 +524,8 @@ bool Element::buildFromXml(const QDomElement &xml_def_elmt, int *state)
ElementPictureFactory *epf = ElementPictureFactory::instance();
epf->getPictures(m_location,
const_cast<QPicture&>(m_picture),
const_cast<QPicture&>(m_low_zoom_picture));
m_picture,
m_low_zoom_picture);
if(!m_picture.isNull())
++ parsed_elements_count;
@@ -1394,6 +1416,8 @@ ElementData Element::elementData() const
void Element::setElementData(ElementData data)
{
auto old_info = m_data.m_informations;
auto old_plc = m_data.m_type == ElementData::Master && m_data.m_master_type == ElementData::PLC
? m_data.plcMasterData() : ElementData::PlcMasterData();
m_data = data;
if (old_info != m_data.m_informations) {
@@ -1403,6 +1427,42 @@ void Element::setElementData(ElementData data)
}
emit elementInfoChange(old_info, m_data.m_informations);
}
// Propagate PLC master data changes to linked slaves
if (m_data.m_type == ElementData::Master && m_data.m_master_type == ElementData::PLC)
{
const auto &new_plc = m_data.plcMasterData();
bool plc_changed = (old_plc.ios != new_plc.ios);
if (plc_changed && !m_group_index_map.isEmpty())
{
for (auto it = m_group_index_map.constBegin(); it != m_group_index_map.constEnd(); ++it)
{
Element *slave = it.key();
int io_idx = it.value();
if (!slave || io_idx < 0 || io_idx >= new_plc.ios.size())
continue;
const auto &io = new_plc.ios.at(io_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,
[&]() -> QString {
if (!diagram() || !diagram()->project())
return QString();
XRefProperties xrp = diagram()->project()
->defaultXRefProperties("plc");
autonum::sequentialNumbers seq;
return autonum::AssignVariables::formulaToLabel(
xrp.slaveLabel(), seq, diagram(), this);
}());
ctx.addValue(QETInformation::ELMT_LABEL, actualLabel());
slave->setElementInformations(ctx);
}
}
}
}
/**
@@ -1638,3 +1698,213 @@ void Element::updateConductorTexts()
}
}
}
/**
@brief Element::drawPlcTable
Draw the PLC table directly from in-memory ElementData.
This overlays the stale picture from the .elmt file with current data.
*/
void Element::drawPlcTable(QPainter *painter)
{
if (!painter || !painter->isActive() || !scene())
return;
if (m_data.m_type != ElementData::Master ||
m_data.m_master_type != ElementData::PLC)
return;
const auto &plc_data = m_data.plcMasterData();
if (plc_data.ios.isEmpty())
return;
// Use plc_table positions from the .elmt file, or default to (0,0)
QList<QPointF> positions = m_plc_table_positions;
if (positions.isEmpty())
positions.append(QPointF(0, 0));
const int COL_COUNT = 5;
const int COL_TYPE = 0;
const int COL_ADDRESS = 1;
const int COL_FUNCTION = 2;
const int COL_COMMENT = 3;
const int COL_CROSSREF = 4;
// Build header labels
QMap<int, QString> headers;
headers[COL_TYPE] = QObject::tr("Type");
headers[COL_ADDRESS] = QObject::tr("Adresse");
headers[COL_FUNCTION] = QObject::tr("Fonction");
headers[COL_COMMENT] = QObject::tr("Commentaire");
headers[COL_CROSSREF] = QObject::tr("Réf. croisée");
// Override with custom column names
if (!plc_data.columnNames.isEmpty()) {
QList<int> all_cols;
all_cols << COL_TYPE << COL_ADDRESS << COL_FUNCTION << COL_COMMENT << COL_CROSSREF;
for (int i = 0; i < qMin(plc_data.columnNames.size(), all_cols.size()); ++i) {
if (!plc_data.columnNames.at(i).isEmpty())
headers[all_cols.at(i)] = plc_data.columnNames.at(i);
}
}
// Build visible columns
QList<int> visible_cols;
if (!plc_data.columnOrder.isEmpty()) {
for (int logical : plc_data.columnOrder) {
if (logical >= 0 && logical < COL_COUNT
&& plc_data.colVisible.value(logical, true)
&& !visible_cols.contains(logical))
visible_cols.append(logical);
}
for (int i = 0; i < COL_COUNT; ++i) {
if (plc_data.colVisible.value(i, true) && !visible_cols.contains(i))
visible_cols.append(i);
}
} else {
for (int i = 0; i < COL_COUNT; ++i) {
if (plc_data.colVisible.value(i, true))
visible_cols.append(i);
}
}
if (visible_cols.isEmpty())
return;
// Column widths
QMap<int, qreal> col_widths;
for (int col : visible_cols) {
if (plc_data.colWidths.contains(col) && plc_data.colWidths[col] > 0)
col_widths[col] = plc_data.colWidths[col];
else {
switch (col) {
case COL_TYPE: col_widths[col] = 35; break;
case COL_ADDRESS: col_widths[col] = 25; break;
case COL_FUNCTION: col_widths[col] = 50; break;
case COL_COMMENT: col_widths[col] = 40; break;
case COL_CROSSREF: col_widths[col] = 30; break;
default: col_widths[col] = 30; break;
}
}
}
qreal row_h = plc_data.rowHeight > 0 ? plc_data.rowHeight : 8.0;
qreal header_h = plc_data.showHeaders ? (row_h + 2.0) : 0;
int total_ios = plc_data.ios.size();
// Parse break positions
QList<int> breaks;
for (int bp : plc_data.breakPositions) {
if (bp > 0 && bp < total_ios && !breaks.contains(bp))
breaks.append(bp);
}
std::sort(breaks.begin(), breaks.end());
QList<int> block_starts;
block_starts.append(0);
for (int bp : breaks)
block_starts.append(bp);
int block_count = block_starts.size();
qreal total_width = 0;
for (int col : visible_cols)
total_width += col_widths[col];
QPen border_pen(Qt::black, 0.5);
// Fonts
QFont header_font = plc_data.headerFont;
if (header_font.family().isEmpty()) {
header_font = painter->font();
header_font.setBold(true);
}
QFont cell_font = plc_data.cellFont;
if (cell_font.family().isEmpty()) {
cell_font = painter->font();
}
for (const QPointF &pos : positions) {
for (int block = 0; block < block_count; ++block) {
qreal bx = pos.x() + block * (total_width + 3);
qreal cx = bx;
// Draw header background
if (plc_data.showHeaders) {
painter->save();
painter->setPen(Qt::NoPen);
painter->setBrush(QColor(220, 220, 220));
painter->drawRect(QRectF(cx, pos.y(), total_width, header_h));
painter->restore();
painter->setFont(header_font);
painter->setPen(border_pen);
painter->setBrush(Qt::NoBrush);
for (int col : visible_cols) {
QRectF hr(cx, pos.y(), col_widths[col], header_h);
painter->drawRect(hr);
QRectF text_rect = hr.adjusted(1, 0, -1, 0);
painter->drawText(text_rect, Qt::AlignCenter, headers.value(col, QString()));
cx += col_widths[col];
}
}
// Draw IO rows
painter->setFont(cell_font);
int start_idx = block_starts.at(block);
int end_idx = (block + 1 < block_starts.size())
? block_starts.at(block + 1) : total_ios;
for (int row = 0; row < (end_idx - start_idx); ++row) {
int io_idx = start_idx + row;
if (io_idx >= plc_data.ios.size()) break;
const auto &io = plc_data.ios.at(io_idx);
qreal ry = pos.y() + header_h + row * row_h;
cx = bx;
for (int col : visible_cols) {
QRectF cr(cx, ry, col_widths[col], row_h);
painter->setPen(border_pen);
painter->setBrush(Qt::NoBrush);
painter->drawRect(cr);
QString cell_text;
switch (col) {
case COL_TYPE: cell_text = ElementData::translatedPlcIOType(io.type); break;
case COL_ADDRESS: cell_text = io.address; break;
case COL_FUNCTION: cell_text = io.functionText; break;
case COL_COMMENT: cell_text = io.comment; break;
case COL_CROSSREF:
{
cell_text = io.crossRef;
for (auto it = m_group_index_map.constBegin();
it != m_group_index_map.constEnd(); ++it) {
if (it.value() == io_idx) {
Element *slave = it.key();
if (slave && slave->diagram() && diagram()
&& diagram()->project())
{
XRefProperties xrp = diagram()->project()
->defaultXRefProperties("plc");
autonum::sequentialNumbers seq;
cell_text = autonum::AssignVariables
::formulaToLabel(
xrp.masterLabel(),
seq,
slave->diagram(),
slave);
}
break;
}
}
break;
}
}
QRectF text_rect = cr.adjusted(1, 0, -1, 0);
painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, cell_text);
cx += col_widths[col];
}
}
}
}
}
+8 -5
View File
@@ -256,11 +256,14 @@ class Element : public QetGraphicsItem
bool m_freeze_label = false;
QString m_F_str;
ElementsLocation m_location;
QList <Terminal *> m_terminals;
const QPicture m_picture;
const QPicture m_low_zoom_picture;
ElementData m_data;
ElementsLocation m_location;
QList <Terminal *> m_terminals;
QPicture m_picture;
QPicture m_low_zoom_picture;
ElementData m_data;
QList<QPointF> m_plc_table_positions; // Positions of plc_table parts in the element definition
void drawPlcTable(QPainter *painter);
private:
bool m_must_highlight = false;
+73 -5
View File
@@ -18,8 +18,10 @@
#include "masterelement.h"
#include "../qetproject.h"
#include "../diagram.h"
#include "../qetinformation.h"
#include "crossrefitem.h"
#include "dynamicelementtextitem.h"
#include "../properties/elementdata.h"
#include <QRegularExpression>
@@ -61,9 +63,14 @@ void MasterElement::linkToElement(Element *elmt)
elmt->linkToElement(this);
XRefProperties xrp = diagram()->project()->defaultXRefProperties(kindInformations()["type"].toString());
if (!m_Xref_item && xrp.snapTo() == XRefProperties::Bottom)
if (!m_Xref_item && (xrp.snapTo() == XRefProperties::Bottom ||
m_data.m_master_type == ElementData::PLC))
m_Xref_item = new CrossRefItem(this); //create cross ref item if not yet
// For PLC masters, connect slave position changes to trigger master repaint
if (m_data.m_master_type == ElementData::PLC)
connectSlavePositionUpdates(elmt);
emit linkedElementChanged();
aboutDeleteXref();
}
@@ -94,10 +101,28 @@ void MasterElement::unlinkElement(Element *elmt)
//Ensure elmt is linked to this element
if (connected_elements.contains(elmt))
{
// Clear PLC variables on slave before unlinking
if (m_data.m_master_type == ElementData::PLC)
{
DiagramContext ctx = elmt->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);
elmt->setElementInformations(ctx);
setGroupIndexForElement(elmt, -1);
}
connected_elements.removeOne(elmt);
elmt -> unlinkElement (this);
elmt -> setHighlighted (false);
// Disconnect slave position updates for PLC masters
disconnectSlavePositionUpdates(elmt);
aboutDeleteXref();
emit linkedElementChanged();
}
@@ -141,7 +166,7 @@ void MasterElement::xrefPropertiesChanged()
{
if(!diagram())
return;
XRefProperties xrp = diagram()->project()->defaultXRefProperties(kindInformations()["type"].toString());
if(xrp.snapTo() == XRefProperties::Bottom)
{
@@ -150,6 +175,12 @@ void MasterElement::xrefPropertiesChanged()
if(!m_Xref_item)
m_Xref_item = new CrossRefItem(this);
}
// Always create Xref for PLC master elements (they show IO table even without linked slaves)
else if (m_data.m_master_type == ElementData::PLC)
{
if(!m_Xref_item)
m_Xref_item = new CrossRefItem(this);
}
aboutDeleteXref();
}
@@ -167,7 +198,11 @@ void MasterElement::aboutDeleteXref()
{
if(!m_Xref_item)
return;
// Never delete Xref for PLC master elements - they always show the IO table
if (m_data.m_master_type == ElementData::PLC)
return;
XRefProperties xrp = diagram()->project()->defaultXRefProperties(kindInformations()["type"].toString());
if (xrp.snapTo() != XRefProperties::Bottom && m_Xref_item)
{
@@ -175,7 +210,7 @@ void MasterElement::aboutDeleteXref()
m_Xref_item = nullptr;
return;
}
if (m_Xref_item->boundingRect().isNull())
{
delete m_Xref_item;
@@ -207,3 +242,36 @@ bool MasterElement::isFull() const
// Return true if current connected elements reached or exceeded the limit
return connected_elements.size() >= max_slaves;
}
/**
@brief MasterElement::connectSlavePositionUpdates
Connect slave xChanged/yChanged to master update() so the PLC table
repaints in real-time when a slave moves on the diagram.
@param slave the slave element
*/
void MasterElement::connectSlavePositionUpdates(Element *slave)
{
if (!slave)
return;
m_slave_x_conn[slave] = connect(slave, &QGraphicsObject::xChanged,
[this]() { update(); });
m_slave_y_conn[slave] = connect(slave, &QGraphicsObject::yChanged,
[this]() { update(); });
}
/**
@brief MasterElement::disconnectSlavePositionUpdates
Disconnect slave position signals from master.
@param slave the slave element
*/
void MasterElement::disconnectSlavePositionUpdates(Element *slave)
{
if (!slave)
return;
if (m_slave_x_conn.contains(slave)) {
disconnect(m_slave_x_conn.take(slave));
}
if (m_slave_y_conn.contains(slave)) {
disconnect(m_slave_y_conn.take(slave));
}
}
+6
View File
@@ -19,6 +19,8 @@
#define MASTERELEMENT_H
#include "element.h"
#include <QHash>
#include <QMetaObject>
class CrossRefItem;
@@ -55,10 +57,14 @@ class MasterElement : public Element
private:
void xrefPropertiesChanged();
void aboutDeleteXref ();
void connectSlavePositionUpdates(Element *slave);
void disconnectSlavePositionUpdates(Element *slave);
private:
CrossRefItem *m_Xref_item = nullptr;
bool m_first_scene_change = true;
QHash<Element*, QMetaObject::Connection> m_slave_x_conn;
QHash<Element*, QMetaObject::Connection> m_slave_y_conn;
};
#endif // MASTERELEMENT_H