mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-08-03 10:34:14 +02:00
@@ -359,6 +359,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/editor/graphicspart/partpolygon.h
|
${QET_DIR}/sources/editor/graphicspart/partpolygon.h
|
||||||
${QET_DIR}/sources/editor/graphicspart/partrectangle.cpp
|
${QET_DIR}/sources/editor/graphicspart/partrectangle.cpp
|
||||||
${QET_DIR}/sources/editor/graphicspart/partrectangle.h
|
${QET_DIR}/sources/editor/graphicspart/partrectangle.h
|
||||||
|
${QET_DIR}/sources/editor/graphicspart/partplctable.cpp
|
||||||
|
${QET_DIR}/sources/editor/graphicspart/partplctable.h
|
||||||
${QET_DIR}/sources/editor/graphicspart/partterminal.cpp
|
${QET_DIR}/sources/editor/graphicspart/partterminal.cpp
|
||||||
${QET_DIR}/sources/editor/graphicspart/partterminal.h
|
${QET_DIR}/sources/editor/graphicspart/partterminal.h
|
||||||
${QET_DIR}/sources/editor/graphicspart/parttext.cpp
|
${QET_DIR}/sources/editor/graphicspart/parttext.cpp
|
||||||
@@ -703,6 +705,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/ui/marginseditdialog.h
|
${QET_DIR}/sources/ui/marginseditdialog.h
|
||||||
${QET_DIR}/sources/ui/masterpropertieswidget.cpp
|
${QET_DIR}/sources/ui/masterpropertieswidget.cpp
|
||||||
${QET_DIR}/sources/ui/masterpropertieswidget.h
|
${QET_DIR}/sources/ui/masterpropertieswidget.h
|
||||||
|
${QET_DIR}/sources/ui/plclinkwidget.cpp
|
||||||
|
${QET_DIR}/sources/ui/plclinkwidget.h
|
||||||
${QET_DIR}/sources/ui/multipastedialog.cpp
|
${QET_DIR}/sources/ui/multipastedialog.cpp
|
||||||
${QET_DIR}/sources/ui/multipastedialog.h
|
${QET_DIR}/sources/ui/multipastedialog.h
|
||||||
${QET_DIR}/sources/ui/potentialselectordialog.cpp
|
${QET_DIR}/sources/ui/potentialselectordialog.cpp
|
||||||
|
|||||||
@@ -275,6 +275,12 @@ namespace autonum
|
|||||||
|
|
||||||
str.replace("%{void}", QString());
|
str.replace("%{void}", QString());
|
||||||
|
|
||||||
|
str.replace("%{plc_type}", dc.value("plc_type").toString());
|
||||||
|
str.replace("%{plc_address}", dc.value("plc_address").toString());
|
||||||
|
str.replace("%{plc_function}", dc.value("plc_function").toString());
|
||||||
|
str.replace("%{plc_comment}", dc.value("plc_comment").toString());
|
||||||
|
str.replace("%{plc_crossref}", dc.value("plc_crossref").toString());
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,11 @@ QSqlQuery projectDataBase::newQuery(const QString &query) {
|
|||||||
*/
|
*/
|
||||||
void projectDataBase::addElement(Element *element)
|
void projectDataBase::addElement(Element *element)
|
||||||
{
|
{
|
||||||
|
if (!element || !element->diagram()) {
|
||||||
|
qDebug() << "projectDataBase::addElement: null element or diagram";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_insert_elements_query.bindValue(":uuid", element->uuid().toString());
|
m_insert_elements_query.bindValue(":uuid", element->uuid().toString());
|
||||||
m_insert_elements_query.bindValue(":diagram_uuid", element->diagram()->uuid().toString());
|
m_insert_elements_query.bindValue(":diagram_uuid", element->diagram()->uuid().toString());
|
||||||
m_insert_elements_query.bindValue(":pos", element->diagram()->convertPosition(element->scenePos()).toString());
|
m_insert_elements_query.bindValue(":pos", element->diagram()->convertPosition(element->scenePos()).toString());
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include "graphicspart/partline.h"
|
#include "graphicspart/partline.h"
|
||||||
#include "graphicspart/partpolygon.h"
|
#include "graphicspart/partpolygon.h"
|
||||||
#include "graphicspart/partrectangle.h"
|
#include "graphicspart/partrectangle.h"
|
||||||
|
#include "graphicspart/partplctable.h"
|
||||||
#include "graphicspart/partterminal.h"
|
#include "graphicspart/partterminal.h"
|
||||||
#include "graphicspart/parttext.h"
|
#include "graphicspart/parttext.h"
|
||||||
#include "ui/qetelementeditor.h"
|
#include "ui/qetelementeditor.h"
|
||||||
@@ -84,12 +85,12 @@ ElementData ElementScene::elementData() {
|
|||||||
|
|
||||||
void ElementScene::setElementData(ElementData data)
|
void ElementScene::setElementData(ElementData data)
|
||||||
{
|
{
|
||||||
bool emit_ = m_element_data.m_informations != data.m_informations;
|
bool emit_info = (m_element_data != data);
|
||||||
bool type_changed = m_element_data.m_type != data.m_type;
|
bool type_changed = m_element_data.m_type != data.m_type;
|
||||||
|
|
||||||
m_element_data = data;
|
m_element_data = data;
|
||||||
|
|
||||||
if (emit_)
|
if (emit_info)
|
||||||
emit elementInfoChanged();
|
emit elementInfoChanged();
|
||||||
if (type_changed)
|
if (type_changed)
|
||||||
emit elementTypeChanged();
|
emit elementTypeChanged();
|
||||||
@@ -926,9 +927,41 @@ void ElementScene::slot_editProperties()
|
|||||||
|
|
||||||
if (m_element_data != epew.editedData())
|
if (m_element_data != epew.editedData())
|
||||||
{
|
{
|
||||||
|
ElementData new_data = epew.editedData();
|
||||||
|
|
||||||
|
// Check PLC state BEFORE pushing (push calls redo which changes m_element_data)
|
||||||
|
bool old_plc = (m_element_data.m_type == ElementData::Master &&
|
||||||
|
m_element_data.m_master_type == ElementData::PLC);
|
||||||
|
bool new_plc = (new_data.m_type == ElementData::Master &&
|
||||||
|
new_data.m_master_type == ElementData::PLC);
|
||||||
|
|
||||||
undoStack().push(new changeElementDataCommand(this,
|
undoStack().push(new changeElementDataCommand(this,
|
||||||
m_element_data,
|
m_element_data,
|
||||||
epew.editedData()));
|
new_data));
|
||||||
|
|
||||||
|
if (new_plc && !old_plc) {
|
||||||
|
// Switched TO PLC: create table if not present
|
||||||
|
bool has_plc = false;
|
||||||
|
for (QGraphicsItem *item : items()) {
|
||||||
|
if (dynamic_cast<PartPlcTable *>(item)) {
|
||||||
|
has_plc = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!has_plc) {
|
||||||
|
PartPlcTable *pt = new PartPlcTable(m_element_editor);
|
||||||
|
addItem(pt);
|
||||||
|
}
|
||||||
|
} else if (!new_plc && old_plc) {
|
||||||
|
// Switched FROM PLC: remove table
|
||||||
|
for (QGraphicsItem *item : items()) {
|
||||||
|
if (PartPlcTable *pt = dynamic_cast<PartPlcTable *>(item)) {
|
||||||
|
removeItem(pt);
|
||||||
|
delete pt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1172,15 +1205,16 @@ ElementContent ElementScene::loadContent(const QDomDocument &xml_document)
|
|||||||
CustomElementPart *cep = nullptr;
|
CustomElementPart *cep = nullptr;
|
||||||
PartDynamicTextField *pdtf = nullptr;
|
PartDynamicTextField *pdtf = nullptr;
|
||||||
|
|
||||||
if (qde.tagName() == "line") cep = new PartLine (m_element_editor);
|
if (qde.tagName() == "line") cep = new PartLine (m_element_editor);
|
||||||
else if (qde.tagName() == "rect") cep = new PartRectangle(m_element_editor);
|
else if (qde.tagName() == "rect") cep = new PartRectangle (m_element_editor);
|
||||||
else if (qde.tagName() == "ellipse") cep = new PartEllipse (m_element_editor);
|
else if (qde.tagName() == "ellipse") cep = new PartEllipse (m_element_editor);
|
||||||
else if (qde.tagName() == "circle") cep = new PartEllipse (m_element_editor);
|
else if (qde.tagName() == "circle") cep = new PartEllipse (m_element_editor);
|
||||||
else if (qde.tagName() == "polygon") cep = new PartPolygon (m_element_editor);
|
else if (qde.tagName() == "polygon") cep = new PartPolygon (m_element_editor);
|
||||||
else if (qde.tagName() == "terminal") cep = new PartTerminal (m_element_editor);
|
else if (qde.tagName() == "terminal") cep = new PartTerminal (m_element_editor);
|
||||||
else if (qde.tagName() == "text") cep = new PartText (m_element_editor);
|
else if (qde.tagName() == "text") cep = new PartText (m_element_editor);
|
||||||
else if (qde.tagName() == "arc") cep = new PartArc (m_element_editor);
|
else if (qde.tagName() == "arc") cep = new PartArc (m_element_editor);
|
||||||
else if (qde.tagName() == "dynamic_text") cep = new PartDynamicTextField (m_element_editor);
|
else if (qde.tagName() == "dynamic_text") cep = new PartDynamicTextField (m_element_editor);
|
||||||
|
else if (qde.tagName() == "plc_table") cep = new PartPlcTable (m_element_editor);
|
||||||
//For the input (aka the old text field) we try to convert it to the new partDynamicTextField
|
//For the input (aka the old text field) we try to convert it to the new partDynamicTextField
|
||||||
else if (qde.tagName() == "input") cep = pdtf = new PartDynamicTextField(m_element_editor);
|
else if (qde.tagName() == "input") cep = pdtf = new PartDynamicTextField(m_element_editor);
|
||||||
else continue;
|
else continue;
|
||||||
|
|||||||
@@ -0,0 +1,696 @@
|
|||||||
|
/*
|
||||||
|
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 "partplctable.h"
|
||||||
|
|
||||||
|
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
|
||||||
|
#include "../../QetGraphicsItemModeler/qetgraphicshandleritem.h"
|
||||||
|
#include "../../QetGraphicsItemModeler/qetgraphicshandlerutility.h"
|
||||||
|
#include "../../properties/elementdata.h"
|
||||||
|
#include "../elementscene.h"
|
||||||
|
#include "../editorcommands.h"
|
||||||
|
#include "../ui/qetelementeditor.h"
|
||||||
|
|
||||||
|
#include <QPen>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::PartPlcTable
|
||||||
|
Constructor
|
||||||
|
@param editor the QETElementEditor of this item
|
||||||
|
@param parent parent item
|
||||||
|
*/
|
||||||
|
PartPlcTable::PartPlcTable(QETElementEditor *editor, QGraphicsItem *parent) :
|
||||||
|
CustomElementGraphicPart(editor, parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::~PartPlcTable
|
||||||
|
*/
|
||||||
|
PartPlcTable::~PartPlcTable()
|
||||||
|
{
|
||||||
|
removeHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::calculateTableSize
|
||||||
|
Calculate the table size from PLC data in the element scene.
|
||||||
|
@return the calculated size in item coordinates
|
||||||
|
*/
|
||||||
|
QSizeF PartPlcTable::calculateTableSize() const
|
||||||
|
{
|
||||||
|
if (!elementEditor() || !elementScene())
|
||||||
|
return QSizeF(100, 50);
|
||||||
|
|
||||||
|
ElementData ed = elementScene()->elementData();
|
||||||
|
if (ed.m_master_type != ElementData::PLC)
|
||||||
|
return QSizeF(100, 50);
|
||||||
|
|
||||||
|
const auto &plc_data = ed.plcMasterData();
|
||||||
|
if (plc_data.ios.isEmpty())
|
||||||
|
return QSizeF(100, 50);
|
||||||
|
|
||||||
|
const int COL_COUNT = 5;
|
||||||
|
|
||||||
|
// Build list of 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())
|
||||||
|
visible_cols << 0 << 1 << 2; // fallback: Type, Address, Function
|
||||||
|
|
||||||
|
// Default 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 0: col_widths[col] = 35; break; // Type
|
||||||
|
case 1: col_widths[col] = 25; break; // Address
|
||||||
|
case 2: col_widths[col] = 50; break; // Function
|
||||||
|
case 3: col_widths[col] = 40; break; // Comment
|
||||||
|
case 5: col_widths[col] = 30; break; // CrossRef
|
||||||
|
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;
|
||||||
|
|
||||||
|
qreal total_width = 0;
|
||||||
|
for (int col : visible_cols)
|
||||||
|
total_width += col_widths[col];
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// Find the tallest block (most rows)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QSizeF(total_width, total_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::paint
|
||||||
|
Draw this PLC table
|
||||||
|
@param painter
|
||||||
|
@param options
|
||||||
|
@param widget
|
||||||
|
*/
|
||||||
|
void PartPlcTable::paint(QPainter *painter, const QStyleOptionGraphicsItem *options, QWidget *widget)
|
||||||
|
{
|
||||||
|
Q_UNUSED(widget);
|
||||||
|
Q_UNUSED(options);
|
||||||
|
|
||||||
|
// Auto-size from PLC data
|
||||||
|
QSizeF table_size = calculateTableSize();
|
||||||
|
if (m_rect.size() != table_size) {
|
||||||
|
prepareGeometryChange();
|
||||||
|
QPointF top_left = m_rect.topLeft();
|
||||||
|
m_rect = QRectF(top_left, table_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyStylesToQPainter(*painter);
|
||||||
|
QPen t = painter->pen();
|
||||||
|
t.setCosmetic(options && options->levelOfDetailFromTransform(painter->worldTransform()) < 1.0);
|
||||||
|
if (isSelected())
|
||||||
|
t.setColor(Qt::red);
|
||||||
|
t.setJoinStyle(Qt::MiterJoin);
|
||||||
|
if (!m_rect.width() || !m_rect.height())
|
||||||
|
t.setWidth(0);
|
||||||
|
painter->setPen(t);
|
||||||
|
|
||||||
|
// Get PLC data
|
||||||
|
ElementData ed = (elementEditor() && elementScene()) ? elementScene()->elementData() : ElementData();
|
||||||
|
if (ed.m_master_type != ElementData::PLC) {
|
||||||
|
// Draw placeholder
|
||||||
|
painter->setBrush(QColor(255, 255, 200));
|
||||||
|
painter->drawRect(m_rect);
|
||||||
|
painter->drawText(m_rect, Qt::AlignCenter, QObject::tr("Table PLC"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &plc_data = ed.plcMasterData();
|
||||||
|
if (plc_data.ios.isEmpty()) {
|
||||||
|
painter->setBrush(QColor(255, 255, 200));
|
||||||
|
painter->drawRect(m_rect);
|
||||||
|
painter->drawText(m_rect, Qt::AlignCenter, QObject::tr("Table PLC (vide)"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 set
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// 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: start, break1, break2, ..., end
|
||||||
|
QList<int> block_starts;
|
||||||
|
block_starts.append(0);
|
||||||
|
for (int bp : breaks)
|
||||||
|
block_starts.append(bp);
|
||||||
|
int block_count = block_starts.size();
|
||||||
|
|
||||||
|
// Draw background
|
||||||
|
painter->save();
|
||||||
|
painter->setPen(Qt::NoPen);
|
||||||
|
painter->setBrush(Qt::white);
|
||||||
|
painter->drawRect(m_rect);
|
||||||
|
painter->restore();
|
||||||
|
|
||||||
|
// Draw outer border
|
||||||
|
QPen border_pen(Qt::black, 0.5);
|
||||||
|
painter->setPen(border_pen);
|
||||||
|
painter->setBrush(Qt::NoBrush);
|
||||||
|
painter->drawRect(m_rect);
|
||||||
|
|
||||||
|
// Draw each block
|
||||||
|
for (int block = 0; block < block_count; ++block) {
|
||||||
|
qreal block_w = 0;
|
||||||
|
for (int col : visible_cols)
|
||||||
|
block_w += col_widths[col];
|
||||||
|
|
||||||
|
qreal block_x = m_rect.x() + block * (block_w + 3);
|
||||||
|
qreal cx = block_x;
|
||||||
|
|
||||||
|
// Draw column headers
|
||||||
|
QFont header_font = plc_data.headerFont.family().isEmpty()
|
||||||
|
? painter->font() : plc_data.headerFont;
|
||||||
|
header_font.setBold(true);
|
||||||
|
painter->setFont(header_font);
|
||||||
|
|
||||||
|
for (int col : visible_cols) {
|
||||||
|
QRectF header_rect(cx, m_rect.y(), col_widths[col], header_h);
|
||||||
|
painter->fillRect(header_rect, QColor(220, 220, 220));
|
||||||
|
painter->setPen(border_pen);
|
||||||
|
painter->drawRect(header_rect);
|
||||||
|
QString header_text = headers.value(col, QString());
|
||||||
|
painter->drawText(header_rect, Qt::AlignCenter, header_text);
|
||||||
|
cx += col_widths[col];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw IO rows
|
||||||
|
QFont cell_font = plc_data.cellFont.family().isEmpty()
|
||||||
|
? painter->font() : plc_data.cellFont;
|
||||||
|
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;
|
||||||
|
const ElementData::PlcIO &io = plc_data.ios.at(io_idx);
|
||||||
|
|
||||||
|
qreal ry = m_rect.y() + header_h + row * row_h;
|
||||||
|
cx = block_x;
|
||||||
|
|
||||||
|
for (int col : visible_cols) {
|
||||||
|
QRectF cell_rect(cx, ry, col_widths[col], row_h);
|
||||||
|
painter->setPen(border_pen);
|
||||||
|
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 = io.crossRef; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRectF text_rect = cell_rect.adjusted(1, 0, -1, 0);
|
||||||
|
painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, cell_text);
|
||||||
|
|
||||||
|
cx += col_widths[col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_hovered)
|
||||||
|
drawShadowShape(painter);
|
||||||
|
|
||||||
|
if (isSelected())
|
||||||
|
drawCross(m_rect.center(), painter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::toXml
|
||||||
|
Export this PLC table part in xml
|
||||||
|
@param xml_document : Xml document to use for create the xml element.
|
||||||
|
@return an xml element that describe this part
|
||||||
|
*/
|
||||||
|
const QDomElement PartPlcTable::toXml(QDomDocument &xml_document) const
|
||||||
|
{
|
||||||
|
QDomElement xml_element = xml_document.createElement("plc_table");
|
||||||
|
qreal x = qRound(m_rect.x() * 100.0) / 100.0;
|
||||||
|
qreal y = qRound(m_rect.y() * 100.0) / 100.0;
|
||||||
|
|
||||||
|
xml_element.setAttribute("x", QString::number(x));
|
||||||
|
xml_element.setAttribute("y", QString::number(y));
|
||||||
|
|
||||||
|
stylesToXml(xml_element);
|
||||||
|
return xml_element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::fromXml
|
||||||
|
Import the properties of this PLC table part from a xml element.
|
||||||
|
@param qde : Xml document to use.
|
||||||
|
*/
|
||||||
|
void PartPlcTable::fromXml(const QDomElement &qde)
|
||||||
|
{
|
||||||
|
stylesFromXml(qde);
|
||||||
|
qreal x = qde.attribute("x", "0").toDouble();
|
||||||
|
qreal y = qde.attribute("y", "0").toDouble();
|
||||||
|
setPos(mapFromScene(x, y));
|
||||||
|
|
||||||
|
// Auto-size from PLC data
|
||||||
|
QSizeF table_size = calculateTableSize();
|
||||||
|
prepareGeometryChange();
|
||||||
|
m_rect = QRectF(QPointF(0, 0), table_size);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::rect
|
||||||
|
@return : Returns the item's rectangle.
|
||||||
|
*/
|
||||||
|
QRectF PartPlcTable::rect() const
|
||||||
|
{
|
||||||
|
return m_rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::setRect
|
||||||
|
Sets the item's rectangle to be the given rectangle.
|
||||||
|
@param rect
|
||||||
|
*/
|
||||||
|
void PartPlcTable::setRect(const QRectF &rect)
|
||||||
|
{
|
||||||
|
if (rect == m_rect) return;
|
||||||
|
prepareGeometryChange();
|
||||||
|
m_rect = rect;
|
||||||
|
adjustHandlerPos();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::sceneGeometricRect
|
||||||
|
@return the minimum, margin-less rectangle this part can fit into, in scene
|
||||||
|
coordinates.
|
||||||
|
*/
|
||||||
|
QRectF PartPlcTable::sceneGeometricRect() const
|
||||||
|
{
|
||||||
|
return(mapToScene(m_rect).boundingRect());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::shape
|
||||||
|
@return the shape of this item
|
||||||
|
*/
|
||||||
|
QPainterPath PartPlcTable::shape() const
|
||||||
|
{
|
||||||
|
QPainterPath fill;
|
||||||
|
fill.addRect(m_rect);
|
||||||
|
|
||||||
|
QPainterPath stroke;
|
||||||
|
stroke.addRect(m_rect);
|
||||||
|
|
||||||
|
QPainterPathStroker pps;
|
||||||
|
pps.setWidth(m_hovered? penWeight()+SHADOWS_HEIGHT : penWeight());
|
||||||
|
stroke = pps.createStroke(stroke);
|
||||||
|
|
||||||
|
return fill.united(stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPainterPath PartPlcTable::shadowShape() const
|
||||||
|
{
|
||||||
|
QPainterPath shape;
|
||||||
|
shape.addRect(m_rect);
|
||||||
|
|
||||||
|
QPainterPathStroker pps;
|
||||||
|
pps.setWidth(penWeight());
|
||||||
|
|
||||||
|
return (pps.createStroke(shape));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::boundingRect
|
||||||
|
@return Bounding rectangle this part can fit into
|
||||||
|
*/
|
||||||
|
QRectF PartPlcTable::boundingRect() const
|
||||||
|
{
|
||||||
|
qreal adjust = (SHADOWS_HEIGHT + penWeight()) / 2;
|
||||||
|
if (penWeight() == 0) adjust += 0.5;
|
||||||
|
|
||||||
|
QRectF r = m_rect.normalized();
|
||||||
|
r.adjust(-adjust, -adjust, adjust, adjust);
|
||||||
|
|
||||||
|
return(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::isUseless
|
||||||
|
@return true if this part is irrelevant and does not deserve to be saved.
|
||||||
|
A PLC table part is always relevant.
|
||||||
|
*/
|
||||||
|
bool PartPlcTable::isUseless() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::startUserTransformation
|
||||||
|
@param initial_selection_rect
|
||||||
|
*/
|
||||||
|
void PartPlcTable::startUserTransformation(const QRectF &initial_selection_rect)
|
||||||
|
{
|
||||||
|
Q_UNUSED(initial_selection_rect)
|
||||||
|
saved_points_.clear();
|
||||||
|
saved_points_ << mapToScene(m_rect.topLeft()) << mapToScene(m_rect.bottomRight());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::handleUserTransformation
|
||||||
|
@param initial_selection_rect
|
||||||
|
@param new_selection_rect
|
||||||
|
*/
|
||||||
|
void PartPlcTable::handleUserTransformation(const QRectF &initial_selection_rect, const QRectF &new_selection_rect)
|
||||||
|
{
|
||||||
|
QList<QPointF> mapped_points = mapPoints(initial_selection_rect, new_selection_rect, saved_points_);
|
||||||
|
setRect(QRectF(mapFromScene(mapped_points.at(0)), mapFromScene(mapped_points.at(1))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::mouseReleaseEvent
|
||||||
|
*/
|
||||||
|
void PartPlcTable::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||||
|
{
|
||||||
|
CustomElementGraphicPart::mouseReleaseEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::itemChange
|
||||||
|
@param change
|
||||||
|
@param value
|
||||||
|
@return
|
||||||
|
*/
|
||||||
|
QVariant PartPlcTable::itemChange(GraphicsItemChange change, const QVariant &value)
|
||||||
|
{
|
||||||
|
if (change == ItemPositionHasChanged)
|
||||||
|
{
|
||||||
|
adjustHandlerPos();
|
||||||
|
}
|
||||||
|
else if (change == ItemSceneChange)
|
||||||
|
{
|
||||||
|
setSelected(false);
|
||||||
|
}
|
||||||
|
else if (change == ItemSceneHasChanged)
|
||||||
|
{
|
||||||
|
if (ElementScene *es = elementScene()) {
|
||||||
|
connect(es, &ElementScene::elementInfoChanged,
|
||||||
|
this, [this]() {
|
||||||
|
QSizeF table_size = calculateTableSize();
|
||||||
|
if (m_rect.size() != table_size) {
|
||||||
|
QPointF top_left = m_rect.topLeft();
|
||||||
|
setRect(QRectF(top_left, table_size));
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QGraphicsItem::itemChange(change, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::sceneEventFilter
|
||||||
|
@param watched
|
||||||
|
@param event
|
||||||
|
@return
|
||||||
|
*/
|
||||||
|
bool PartPlcTable::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
|
||||||
|
{
|
||||||
|
if(watched->type() == QetGraphicsHandlerItem::Type)
|
||||||
|
{
|
||||||
|
QetGraphicsHandlerItem *qghi = qgraphicsitem_cast<QetGraphicsHandlerItem *>(watched);
|
||||||
|
|
||||||
|
if(m_handler_vector.contains(qghi))
|
||||||
|
{
|
||||||
|
m_vector_index = m_handler_vector.indexOf(qghi);
|
||||||
|
if (m_vector_index != -1)
|
||||||
|
{
|
||||||
|
if(event->type() == QEvent::GraphicsSceneMousePress)
|
||||||
|
{
|
||||||
|
handlerMousePressEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(event->type() == QEvent::GraphicsSceneMouseMove)
|
||||||
|
{
|
||||||
|
handlerMouseMoveEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (event->type() == QEvent::GraphicsSceneMouseRelease)
|
||||||
|
{
|
||||||
|
handlerMouseReleaseEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::switchResizeMode
|
||||||
|
*/
|
||||||
|
void PartPlcTable::switchResizeMode()
|
||||||
|
{
|
||||||
|
if (m_resize_mode == 1)
|
||||||
|
{
|
||||||
|
m_resize_mode = 2;
|
||||||
|
for (QetGraphicsHandlerItem *qghi : m_handler_vector)
|
||||||
|
qghi->setColor(Qt::darkGreen);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_resize_mode = 1;
|
||||||
|
qDeleteAll(m_handler_vector);
|
||||||
|
m_handler_vector.clear();
|
||||||
|
addHandler();
|
||||||
|
for (QetGraphicsHandlerItem *qghi : m_handler_vector) {
|
||||||
|
qghi->setColor(Qt::blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::adjustHandlerPos
|
||||||
|
*/
|
||||||
|
void PartPlcTable::adjustHandlerPos()
|
||||||
|
{
|
||||||
|
if (m_handler_vector.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QVector<QPointF> points_vector = QetGraphicsHandlerUtility::pointsForRect(m_rect);
|
||||||
|
|
||||||
|
if (m_handler_vector.size() == points_vector.size())
|
||||||
|
{
|
||||||
|
points_vector = mapToScene(points_vector);
|
||||||
|
for (int i = 0 ; i < points_vector.size() ; ++i)
|
||||||
|
m_handler_vector.at(i)->setPos(points_vector.at(i));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qDeleteAll(m_handler_vector);
|
||||||
|
m_handler_vector.clear();
|
||||||
|
addHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PartPlcTable::handlerMousePressEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
|
||||||
|
{
|
||||||
|
Q_UNUSED(qghi)
|
||||||
|
Q_UNUSED(event)
|
||||||
|
|
||||||
|
m_old_rect = m_rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PartPlcTable::handlerMouseMoveEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
|
||||||
|
{
|
||||||
|
Q_UNUSED(qghi)
|
||||||
|
|
||||||
|
QPointF new_pos = event->scenePos();
|
||||||
|
if (event->modifiers() != Qt::ControlModifier)
|
||||||
|
new_pos = elementScene()->snapToGrid(event->scenePos());
|
||||||
|
new_pos = mapFromScene(new_pos);
|
||||||
|
|
||||||
|
setRect(QetGraphicsHandlerUtility::rectForPosAtIndex(m_rect, new_pos, m_vector_index));
|
||||||
|
adjustHandlerPos();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PartPlcTable::handlerMouseReleaseEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
|
||||||
|
{
|
||||||
|
Q_UNUSED(qghi)
|
||||||
|
Q_UNUSED(event)
|
||||||
|
|
||||||
|
QUndoCommand *undo = new QUndoCommand("Modifier une table PLC");
|
||||||
|
if (m_old_rect != m_rect) {
|
||||||
|
QPropertyUndoCommand *u = new QPropertyUndoCommand(this, "rect", QVariant(m_old_rect.normalized()), QVariant(m_rect.normalized()), undo);
|
||||||
|
u->setAnimated(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
elementScene()->undoStack().push(undo);
|
||||||
|
m_vector_index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::addHandler
|
||||||
|
Do not add resize handlers - table size is data-driven.
|
||||||
|
Move is handled by the base class QGraphicsItem drag behavior.
|
||||||
|
*/
|
||||||
|
void PartPlcTable::addHandler()
|
||||||
|
{
|
||||||
|
// No resize handlers - size comes from PLC data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PartPlcTable::removeHandler
|
||||||
|
Remove the handlers of this item
|
||||||
|
*/
|
||||||
|
void PartPlcTable::removeHandler()
|
||||||
|
{
|
||||||
|
if (!m_handler_vector.isEmpty())
|
||||||
|
{
|
||||||
|
qDeleteAll(m_handler_vector);
|
||||||
|
m_handler_vector.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2006-2026 The QElectroTech Team
|
||||||
|
This file is part of QElectroTech.
|
||||||
|
|
||||||
|
QElectroTech is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
QElectroTech is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifndef PARTPLCTABLE_H
|
||||||
|
#define PARTPLCTABLE_H
|
||||||
|
|
||||||
|
#include "customelementgraphicpart.h"
|
||||||
|
#include "../../QetGraphicsItemModeler/qetgraphicshandleritem.h"
|
||||||
|
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief The PartPlcTable class
|
||||||
|
This class represents a PLC I/O table preview in the element editor.
|
||||||
|
It shows the user where the PLC table will appear at runtime, so they
|
||||||
|
can position other element parts (rectangles, terminals, etc.) around it.
|
||||||
|
The actual PLC data is read from ElementScene::elementData().
|
||||||
|
*/
|
||||||
|
class PartPlcTable : public CustomElementGraphicPart
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(QRectF rect READ rect WRITE setRect)
|
||||||
|
|
||||||
|
public:
|
||||||
|
PartPlcTable(QETElementEditor *editor, QGraphicsItem *parent = nullptr);
|
||||||
|
~PartPlcTable() override;
|
||||||
|
|
||||||
|
enum { Type = UserType + 1120 };
|
||||||
|
int type () const override { return Type; }
|
||||||
|
void paint (QPainter *, const QStyleOptionGraphicsItem *, QWidget * = nullptr) override;
|
||||||
|
QString name () const override { return QObject::tr("table PLC", "element part name"); }
|
||||||
|
|
||||||
|
QString xmlName () const override { return QString("plc_table"); }
|
||||||
|
const QDomElement toXml (QDomDocument &) const override;
|
||||||
|
void fromXml (const QDomElement &) override;
|
||||||
|
|
||||||
|
QRectF rect() const;
|
||||||
|
void setRect(const QRectF &rect);
|
||||||
|
|
||||||
|
QRectF sceneGeometricRect() const override;
|
||||||
|
QPainterPath shape () const override;
|
||||||
|
QPainterPath shadowShape() const override;
|
||||||
|
QRectF boundingRect() const override;
|
||||||
|
bool isUseless() const override;
|
||||||
|
|
||||||
|
void startUserTransformation(const QRectF &) override;
|
||||||
|
void handleUserTransformation(const QRectF &, const QRectF &) override;
|
||||||
|
|
||||||
|
void addHandler() override;
|
||||||
|
void removeHandler() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||||
|
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
|
||||||
|
bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void switchResizeMode();
|
||||||
|
void adjustHandlerPos();
|
||||||
|
void handlerMousePressEvent (QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event);
|
||||||
|
void handlerMouseMoveEvent (QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event);
|
||||||
|
void handlerMouseReleaseEvent (QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event);
|
||||||
|
QSizeF calculateTableSize() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QRectF m_rect,
|
||||||
|
m_old_rect;
|
||||||
|
QList<QPointF> saved_points_;
|
||||||
|
int m_resize_mode = 1,
|
||||||
|
m_vector_index = -1;
|
||||||
|
QVector<QetGraphicsHandlerItem *> m_handler_vector;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PARTPLCTABLE_H
|
||||||
@@ -197,6 +197,10 @@ void DynamicTextFieldEditor::setUpConnections()
|
|||||||
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::textWidthChanged, this, [=](){this -> updateForm();});
|
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::textWidthChanged, this, [=](){this -> updateForm();});
|
||||||
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::compositeTextChanged,this, [=](){this -> updateForm();});
|
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::compositeTextChanged,this, [=](){this -> updateForm();});
|
||||||
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::keepVisualRotationChanged, this, [=](){this -> updateForm();});
|
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::keepVisualRotationChanged, this, [=](){this -> updateForm();});
|
||||||
|
|
||||||
|
// Refresh info combo when element data changes (e.g. type switched to PLC-Slave)
|
||||||
|
m_connection_list << connect(elementEditor()->elementScene(), &ElementScene::elementInfoChanged,
|
||||||
|
this, &DynamicTextFieldEditor::fillInfoComboBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DynamicTextFieldEditor::disconnectConnections()
|
void DynamicTextFieldEditor::disconnectConnections()
|
||||||
@@ -218,13 +222,34 @@ void DynamicTextFieldEditor::fillInfoComboBox()
|
|||||||
ui -> m_elmt_info_cb -> clear();
|
ui -> m_elmt_info_cb -> clear();
|
||||||
|
|
||||||
QStringList strl;
|
QStringList strl;
|
||||||
auto type = elementEditor()->elementScene()->elementData().m_type;
|
auto ed = elementEditor()->elementScene()->elementData();
|
||||||
|
auto type = ed.m_type;
|
||||||
|
|
||||||
if((type & ElementData::AllReport) || (type == ElementData::ConductorDefinition)) {
|
if((type & ElementData::AllReport) || (type == ElementData::ConductorDefinition)) {
|
||||||
strl = QETInformation::folioReportInfoKeys();
|
strl = QETInformation::folioReportInfoKeys();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
strl = QETInformation::elementInfoKeys();
|
strl = QETInformation::elementInfoKeys();
|
||||||
|
|
||||||
|
bool is_plc_slave = (type == ElementData::Slave
|
||||||
|
&& ed.m_slave_type == ElementData::PLCSlave);
|
||||||
|
|
||||||
|
if (is_plc_slave) {
|
||||||
|
QStringList plc_keys = {
|
||||||
|
QETInformation::ELMT_PLC_TYPE,
|
||||||
|
QETInformation::ELMT_PLC_ADDRESS,
|
||||||
|
QETInformation::ELMT_PLC_FUNCTION,
|
||||||
|
QETInformation::ELMT_PLC_COMMENT,
|
||||||
|
QETInformation::ELMT_PLC_CROSSREF
|
||||||
|
};
|
||||||
|
strl = plc_keys + strl;
|
||||||
|
} else {
|
||||||
|
strl.removeAll(QETInformation::ELMT_PLC_TYPE);
|
||||||
|
strl.removeAll(QETInformation::ELMT_PLC_ADDRESS);
|
||||||
|
strl.removeAll(QETInformation::ELMT_PLC_FUNCTION);
|
||||||
|
strl.removeAll(QETInformation::ELMT_PLC_COMMENT);
|
||||||
|
strl.removeAll(QETInformation::ELMT_PLC_CROSSREF);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i=0; i<strl.size();++i) {
|
for (int i=0; i<strl.size();++i) {
|
||||||
|
|||||||
@@ -28,6 +28,19 @@
|
|||||||
#include <QSignalBlocker>
|
#include <QSignalBlocker>
|
||||||
#include <QTableWidgetItem>
|
#include <QTableWidgetItem>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
#include <QTableWidget>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QFontDialog>
|
||||||
|
#include <QFont>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QSplitter>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief The EditorDelegate class
|
@brief The EditorDelegate class
|
||||||
@@ -91,9 +104,16 @@ void ElementPropertiesEditorWidget::upDateInterface()
|
|||||||
|
|
||||||
if (m_data.m_type == ElementData::Slave)
|
if (m_data.m_type == ElementData::Slave)
|
||||||
{
|
{
|
||||||
ui->m_state_cb->setCurrentIndex(
|
// If PLC Slave, select "Esclave PLC" in state combo (not in type combo)
|
||||||
ui->m_state_cb->findData(
|
if (m_data.m_slave_type == ElementData::PLCSlave) {
|
||||||
m_data.m_slave_state));
|
ui->m_state_cb->setCurrentIndex(
|
||||||
|
ui->m_state_cb->findData(
|
||||||
|
ElementData::PLCSlave));
|
||||||
|
} else {
|
||||||
|
ui->m_state_cb->setCurrentIndex(
|
||||||
|
ui->m_state_cb->findData(
|
||||||
|
m_data.m_slave_state));
|
||||||
|
}
|
||||||
ui->m_type_cb->setCurrentIndex (
|
ui->m_type_cb->setCurrentIndex (
|
||||||
ui->m_type_cb->findData(
|
ui->m_type_cb->findData(
|
||||||
m_data.m_slave_type));
|
m_data.m_slave_type));
|
||||||
@@ -120,6 +140,26 @@ void ElementPropertiesEditorWidget::upDateInterface()
|
|||||||
if (m_data.m_slave_contact_groups_enabled) {
|
if (m_data.m_slave_contact_groups_enabled) {
|
||||||
populateSlaveGroupsTable();
|
populateSlaveGroupsTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PLC configuration
|
||||||
|
if (m_data.m_master_type == ElementData::PLC) {
|
||||||
|
if (!m_plc_gb) {
|
||||||
|
createPlcConfigWidgets();
|
||||||
|
}
|
||||||
|
m_plc_gb->setVisible(true);
|
||||||
|
ui->max_slaves_checkbox->setVisible(false);
|
||||||
|
ui->max_slaves_spinbox->setVisible(false);
|
||||||
|
ui->m_slave_groups_checkbox->setVisible(false);
|
||||||
|
ui->m_slave_groups_table->setVisible(false);
|
||||||
|
populatePlcTable();
|
||||||
|
} else {
|
||||||
|
if (m_plc_gb)
|
||||||
|
m_plc_gb->setVisible(false);
|
||||||
|
ui->max_slaves_checkbox->setVisible(true);
|
||||||
|
ui->max_slaves_spinbox->setVisible(true);
|
||||||
|
ui->m_slave_groups_checkbox->setVisible(true);
|
||||||
|
ui->m_slave_groups_table->setVisible(true);
|
||||||
|
}
|
||||||
} else if (m_data.m_type == ElementData::Terminal) {
|
} else if (m_data.m_type == ElementData::Terminal) {
|
||||||
ui->m_terminal_type_cb->setCurrentIndex(
|
ui->m_terminal_type_cb->setCurrentIndex(
|
||||||
ui->m_terminal_type_cb->findData(
|
ui->m_terminal_type_cb->findData(
|
||||||
@@ -152,6 +192,7 @@ void ElementPropertiesEditorWidget::setUpInterface()
|
|||||||
ui->m_state_cb->addItem(tr("Normalement fermé"), ElementData::NC);
|
ui->m_state_cb->addItem(tr("Normalement fermé"), ElementData::NC);
|
||||||
ui->m_state_cb->addItem(tr("Inverseur"), ElementData::SW);
|
ui->m_state_cb->addItem(tr("Inverseur"), ElementData::SW);
|
||||||
ui->m_state_cb->addItem(tr("Other"), ElementData::Other);
|
ui->m_state_cb->addItem(tr("Other"), ElementData::Other);
|
||||||
|
ui->m_state_cb->addItem(tr("Esclave PLC"), ElementData::PLCSlave);
|
||||||
ui->m_type_cb->addItem(tr("Simple"), ElementData::SSimple);
|
ui->m_type_cb->addItem(tr("Simple"), ElementData::SSimple);
|
||||||
ui->m_type_cb->addItem(tr("Puissance"), ElementData::Power);
|
ui->m_type_cb->addItem(tr("Puissance"), ElementData::Power);
|
||||||
ui->m_type_cb->addItem(tr("Temporisé travail"), ElementData::DelayOn);
|
ui->m_type_cb->addItem(tr("Temporisé travail"), ElementData::DelayOn);
|
||||||
@@ -162,6 +203,8 @@ void ElementPropertiesEditorWidget::setUpInterface()
|
|||||||
ui->m_master_type_cb->addItem(tr("Bobine"), ElementData::Coil);
|
ui->m_master_type_cb->addItem(tr("Bobine"), ElementData::Coil);
|
||||||
ui->m_master_type_cb->addItem(tr("Organe de protection"), ElementData::Protection);
|
ui->m_master_type_cb->addItem(tr("Organe de protection"), ElementData::Protection);
|
||||||
ui->m_master_type_cb->addItem(tr("Commutateur / bouton"), ElementData::Commutator);
|
ui->m_master_type_cb->addItem(tr("Commutateur / bouton"), ElementData::Commutator);
|
||||||
|
ui->m_master_type_cb->addItem(tr("Module PLC"), ElementData::PLC);
|
||||||
|
ui->m_master_type_cb->setMinimumWidth(150);
|
||||||
|
|
||||||
//Terminal option
|
//Terminal option
|
||||||
ui->m_terminal_type_cb->addItem(tr("Générique"), ElementData::TTGeneric);
|
ui->m_terminal_type_cb->addItem(tr("Générique"), ElementData::TTGeneric);
|
||||||
@@ -186,6 +229,38 @@ void ElementPropertiesEditorWidget::setUpInterface()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Connect master type combo box to show/hide PLC configuration
|
||||||
|
connect(ui->m_master_type_cb, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||||
|
auto master_type = ui->m_master_type_cb->itemData(index).value<ElementData::MasterType>();
|
||||||
|
if (master_type == ElementData::PLC) {
|
||||||
|
if (!m_plc_gb) {
|
||||||
|
createPlcConfigWidgets();
|
||||||
|
}
|
||||||
|
m_plc_gb->setVisible(true);
|
||||||
|
ui->max_slaves_checkbox->setVisible(false);
|
||||||
|
ui->max_slaves_spinbox->setVisible(false);
|
||||||
|
ui->m_slave_groups_checkbox->setVisible(false);
|
||||||
|
ui->m_slave_groups_table->setVisible(false);
|
||||||
|
populatePlcTable();
|
||||||
|
} else {
|
||||||
|
if (m_plc_gb)
|
||||||
|
m_plc_gb->setVisible(false);
|
||||||
|
ui->max_slaves_checkbox->setVisible(true);
|
||||||
|
ui->max_slaves_spinbox->setVisible(true);
|
||||||
|
ui->m_slave_groups_checkbox->setVisible(true);
|
||||||
|
ui->m_slave_groups_table->setVisible(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// When "Esclave PLC" is selected in state combo, disable type combo
|
||||||
|
connect(ui->m_state_cb, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||||
|
bool is_plc = (ui->m_state_cb->itemData(index) == ElementData::PLCSlave);
|
||||||
|
ui->m_type_cb->setEnabled(!is_plc);
|
||||||
|
if (is_plc) {
|
||||||
|
ui->m_type_cb->setCurrentIndex(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
populateTree();
|
populateTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +306,30 @@ void ElementPropertiesEditorWidget::updateTree()
|
|||||||
void ElementPropertiesEditorWidget::populateTree()
|
void ElementPropertiesEditorWidget::populateTree()
|
||||||
{
|
{
|
||||||
const auto keys = QETInformation::elementEditorElementInfoKeys();
|
const auto keys = QETInformation::elementEditorElementInfoKeys();
|
||||||
|
|
||||||
|
// For PLC Slave: add PLC-specific info keys at the top
|
||||||
|
if (m_data.m_type == ElementData::Slave
|
||||||
|
&& m_data.m_slave_type == ElementData::PLCSlave)
|
||||||
|
{
|
||||||
|
QStringList plc_keys = {
|
||||||
|
QETInformation::ELMT_PLC_TYPE,
|
||||||
|
QETInformation::ELMT_PLC_ADDRESS,
|
||||||
|
QETInformation::ELMT_PLC_FUNCTION,
|
||||||
|
QETInformation::ELMT_PLC_COMMENT,
|
||||||
|
QETInformation::ELMT_PLC_CROSSREF
|
||||||
|
};
|
||||||
|
for (const QString &key : plc_keys)
|
||||||
|
{
|
||||||
|
QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_tree);
|
||||||
|
qtwi->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable);
|
||||||
|
qtwi->setData(0, Qt::DisplayRole,
|
||||||
|
QETInformation::translatedInfoKey(key));
|
||||||
|
qtwi->setData(0, Qt::UserRole, key);
|
||||||
|
qtwi->setText(1, m_data.m_informations.value(key).toString());
|
||||||
|
qtwi->setForeground(0, QColor(0, 100, 180));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(const QString& key : keys)
|
for(const QString& key : keys)
|
||||||
{
|
{
|
||||||
QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_tree);
|
QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_tree);
|
||||||
@@ -254,21 +353,31 @@ void ElementPropertiesEditorWidget::on_m_buttonBox_accepted()
|
|||||||
|
|
||||||
if (m_data.m_type == ElementData::Slave)
|
if (m_data.m_type == ElementData::Slave)
|
||||||
{
|
{
|
||||||
m_data.m_slave_state = ui->m_state_cb->currentData().value<ElementData::SlaveState>();
|
auto state_val = ui->m_state_cb->currentData();
|
||||||
m_data.m_slave_type = ui->m_type_cb->currentData().value<ElementData::SlaveType>();
|
if (state_val == ElementData::PLCSlave) {
|
||||||
|
m_data.m_slave_type = ElementData::PLCSlave;
|
||||||
|
m_data.m_slave_state = ElementData::Other;
|
||||||
|
} else {
|
||||||
|
m_data.m_slave_state = state_val.value<ElementData::SlaveState>();
|
||||||
|
m_data.m_slave_type = ui->m_type_cb->currentData().value<ElementData::SlaveType>();
|
||||||
|
}
|
||||||
m_data.m_contact_count = ui->m_number_ctc->value();
|
m_data.m_contact_count = ui->m_number_ctc->value();
|
||||||
}
|
}
|
||||||
else if (m_data.m_type == ElementData::Master) {
|
else if (m_data.m_type == ElementData::Master) {
|
||||||
m_data.m_master_type = ui->m_master_type_cb->currentData().value<ElementData::MasterType>();
|
m_data.m_master_type = ui->m_master_type_cb->currentData().value<ElementData::MasterType>();
|
||||||
|
|
||||||
//If the checkbox is checked, save the number; otherwise, -1 (infinity)
|
//If the checkbox is checked, save the number; otherwise, -1 (infinity)
|
||||||
if (ui->max_slaves_checkbox->isChecked()) {
|
if (ui->max_slaves_checkbox->isVisible() && ui->max_slaves_checkbox->isChecked()) {
|
||||||
m_data.m_max_slaves = ui->max_slaves_spinbox->value();
|
m_data.m_max_slaves = ui->max_slaves_spinbox->value();
|
||||||
} else {
|
} else {
|
||||||
m_data.m_max_slaves = -1;
|
m_data.m_max_slaves = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
readSlaveGroupsFromTable();
|
if (m_data.m_master_type == ElementData::PLC) {
|
||||||
|
readPlcTable();
|
||||||
|
} else {
|
||||||
|
readSlaveGroupsFromTable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (m_data.m_type == ElementData::Terminal)
|
else if (m_data.m_type == ElementData::Terminal)
|
||||||
{
|
{
|
||||||
@@ -532,3 +641,601 @@ void ElementPropertiesEditorWidget::readSlaveGroupsFromTable()
|
|||||||
m_data.m_slave_contact_groups.append(group);
|
m_data.m_slave_contact_groups.append(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementPropertiesEditorWidget::createPlcConfigWidgets
|
||||||
|
* Create the PLC configuration widgets programmatically and add them to m_master_gb
|
||||||
|
*/
|
||||||
|
void ElementPropertiesEditorWidget::createPlcConfigWidgets()
|
||||||
|
{
|
||||||
|
// Create PLC group box
|
||||||
|
m_plc_gb = new QGroupBox(tr("Configuration PLC"), ui->m_master_gb);
|
||||||
|
auto *plc_layout = new QVBoxLayout(m_plc_gb);
|
||||||
|
|
||||||
|
// Toolbar
|
||||||
|
auto *toolbar = new QHBoxLayout();
|
||||||
|
auto *add_btn = new QPushButton(tr("+"), m_plc_gb);
|
||||||
|
auto *remove_btn = new QPushButton(tr("-"), m_plc_gb);
|
||||||
|
|
||||||
|
add_btn->setMaximumWidth(30);
|
||||||
|
remove_btn->setMaximumWidth(30);
|
||||||
|
|
||||||
|
toolbar->addStretch();
|
||||||
|
toolbar->addWidget(add_btn);
|
||||||
|
toolbar->addWidget(remove_btn);
|
||||||
|
plc_layout->addLayout(toolbar);
|
||||||
|
|
||||||
|
// Tables side by side: IO table (left) + Terminal table (right)
|
||||||
|
auto *tables_splitter = new QSplitter(Qt::Horizontal, m_plc_gb);
|
||||||
|
|
||||||
|
// IO Table
|
||||||
|
m_plc_table = new QTableWidget(tables_splitter);
|
||||||
|
m_plc_table->setColumnCount(5);
|
||||||
|
m_plc_table->setHorizontalHeaderLabels({
|
||||||
|
tr("Type"), tr("Adresse"), tr("Fonction"),
|
||||||
|
tr("Commentaire"), tr("Réf. croisée")
|
||||||
|
});
|
||||||
|
m_plc_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
|
||||||
|
m_plc_table->horizontalHeader()->setSectionsMovable(true);
|
||||||
|
m_plc_table->horizontalHeader()->setSectionsClickable(true);
|
||||||
|
m_plc_table->horizontalHeader()->resizeSection(0, 120);
|
||||||
|
m_plc_table->horizontalHeader()->resizeSection(1, 100);
|
||||||
|
m_plc_table->horizontalHeader()->resizeSection(2, 150);
|
||||||
|
m_plc_table->horizontalHeader()->resizeSection(3, 150);
|
||||||
|
m_plc_table->horizontalHeader()->resizeSection(4, 100);
|
||||||
|
m_plc_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
m_plc_table->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
m_plc_table->setMinimumHeight(200);
|
||||||
|
tables_splitter->addWidget(m_plc_table);
|
||||||
|
|
||||||
|
// Terminal table (per IO: Nb + T1-T4)
|
||||||
|
m_plc_terminal_table = new QTableWidget(tables_splitter);
|
||||||
|
m_plc_terminal_table->setColumnCount(2);
|
||||||
|
m_plc_terminal_table->setHorizontalHeaderLabels({
|
||||||
|
tr("Nb."), tr("T1")
|
||||||
|
});
|
||||||
|
m_plc_terminal_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
|
||||||
|
m_plc_terminal_table->horizontalHeader()->resizeSection(0, 50);
|
||||||
|
m_plc_terminal_table->horizontalHeader()->resizeSection(1, 80);
|
||||||
|
m_plc_terminal_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
m_plc_terminal_table->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
m_plc_terminal_table->setMinimumHeight(200);
|
||||||
|
tables_splitter->addWidget(m_plc_terminal_table);
|
||||||
|
|
||||||
|
tables_splitter->setStretchFactor(0, 3);
|
||||||
|
tables_splitter->setStretchFactor(1, 1);
|
||||||
|
tables_splitter->setSizes({500, 150});
|
||||||
|
|
||||||
|
plc_layout->addWidget(tables_splitter);
|
||||||
|
|
||||||
|
// Font settings
|
||||||
|
auto *font_layout = new QHBoxLayout();
|
||||||
|
|
||||||
|
m_plc_header_font_btn = new QPushButton(tr("Police des en-têtes"), m_plc_gb);
|
||||||
|
m_plc_header_font_btn->setToolTip(tr("Configurer la police des en-têtes de colonnes"));
|
||||||
|
connect(m_plc_header_font_btn, &QPushButton::clicked, this, &ElementPropertiesEditorWidget::plcSelectHeaderFont);
|
||||||
|
font_layout->addWidget(m_plc_header_font_btn);
|
||||||
|
|
||||||
|
m_plc_cell_font_btn = new QPushButton(tr("Police du texte"), m_plc_gb);
|
||||||
|
m_plc_cell_font_btn->setToolTip(tr("Configurer la police du texte dans les cellules"));
|
||||||
|
connect(m_plc_cell_font_btn, &QPushButton::clicked, this, &ElementPropertiesEditorWidget::plcSelectCellFont);
|
||||||
|
font_layout->addWidget(m_plc_cell_font_btn);
|
||||||
|
|
||||||
|
m_plc_show_headers_cb = new QCheckBox(tr("Afficher les en-têtes sur la feuille"), m_plc_gb);
|
||||||
|
m_plc_show_headers_cb->setToolTip(tr("Afficher ou masquer les en-têtes de colonnes du tableau PLC sur la feuille"));
|
||||||
|
m_plc_show_headers_cb->setChecked(true);
|
||||||
|
font_layout->addWidget(m_plc_show_headers_cb);
|
||||||
|
|
||||||
|
font_layout->addStretch();
|
||||||
|
plc_layout->addLayout(font_layout);
|
||||||
|
|
||||||
|
// Display settings
|
||||||
|
auto *settings_layout = new QGridLayout();
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
m_plc_break_checkboxes[i] = new QCheckBox(
|
||||||
|
tr("Saut %1 après:").arg(i + 1), m_plc_gb);
|
||||||
|
settings_layout->addWidget(m_plc_break_checkboxes[i], i, 0);
|
||||||
|
|
||||||
|
m_plc_break_spinboxes[i] = new QSpinBox(m_plc_gb);
|
||||||
|
m_plc_break_spinboxes[i]->setMinimum(0);
|
||||||
|
m_plc_break_spinboxes[i]->setMaximum(128);
|
||||||
|
m_plc_break_spinboxes[i]->setSpecialValueText(tr("Aucun"));
|
||||||
|
m_plc_break_spinboxes[i]->setEnabled(false);
|
||||||
|
settings_layout->addWidget(m_plc_break_spinboxes[i], i, 1);
|
||||||
|
|
||||||
|
connect(m_plc_break_checkboxes[i], &QCheckBox::toggled,
|
||||||
|
m_plc_break_spinboxes[i], &QSpinBox::setEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
settings_layout->addWidget(new QLabel(tr("H. ligne:"), m_plc_gb), 4, 0);
|
||||||
|
m_plc_row_height_spinbox = new QSpinBox(m_plc_gb);
|
||||||
|
m_plc_row_height_spinbox->setMinimum(4);
|
||||||
|
m_plc_row_height_spinbox->setMaximum(30);
|
||||||
|
m_plc_row_height_spinbox->setValue(8);
|
||||||
|
m_plc_row_height_spinbox->setSuffix(tr(" mm"));
|
||||||
|
settings_layout->addWidget(m_plc_row_height_spinbox, 4, 1);
|
||||||
|
|
||||||
|
settings_layout->setColumnStretch(2, 1);
|
||||||
|
plc_layout->addLayout(settings_layout);
|
||||||
|
|
||||||
|
// Column name, visibility and width
|
||||||
|
auto *col_layout = new QHBoxLayout();
|
||||||
|
QStringList col_names = {tr("Type"), tr("Adresse"), tr("Fonction"),
|
||||||
|
tr("Commentaire"), tr("Réf.")};
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
auto *col_widget = new QVBoxLayout();
|
||||||
|
|
||||||
|
auto *le = new QLineEdit(m_plc_gb);
|
||||||
|
le->setPlaceholderText(col_names.at(i));
|
||||||
|
le->setToolTip(tr("Nom personnalisé de la colonne (vide = par défaut)"));
|
||||||
|
m_plc_col_name_edits.append(le);
|
||||||
|
col_widget->addWidget(le);
|
||||||
|
|
||||||
|
auto *cb = new QCheckBox(tr("Visible"), m_plc_gb);
|
||||||
|
cb->setChecked(true);
|
||||||
|
m_plc_col_visibility_checkboxes.append(cb);
|
||||||
|
col_widget->addWidget(cb);
|
||||||
|
|
||||||
|
auto *sb = new QSpinBox(m_plc_gb);
|
||||||
|
sb->setMinimum(10);
|
||||||
|
sb->setMaximum(200);
|
||||||
|
sb->setValue(40);
|
||||||
|
sb->setSuffix(tr(" mm"));
|
||||||
|
m_plc_col_width_spinboxes.append(sb);
|
||||||
|
col_widget->addWidget(sb);
|
||||||
|
|
||||||
|
col_layout->addLayout(col_widget);
|
||||||
|
}
|
||||||
|
plc_layout->addLayout(col_layout);
|
||||||
|
|
||||||
|
// Add to master group box
|
||||||
|
ui->m_master_gb->layout()->addWidget(m_plc_gb);
|
||||||
|
|
||||||
|
// Connect signals
|
||||||
|
connect(add_btn, &QPushButton::clicked, this, &ElementPropertiesEditorWidget::plcAddRow);
|
||||||
|
connect(remove_btn, &QPushButton::clicked, this, &ElementPropertiesEditorWidget::plcRemoveRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementPropertiesEditorWidget::populatePlcTable
|
||||||
|
* Fill the PLC table from m_data
|
||||||
|
*/
|
||||||
|
void ElementPropertiesEditorWidget::populatePlcTable()
|
||||||
|
{
|
||||||
|
if (!m_plc_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_plc_table->setRowCount(0);
|
||||||
|
if (m_plc_terminal_table)
|
||||||
|
m_plc_terminal_table->setRowCount(0);
|
||||||
|
|
||||||
|
const auto &plc_data = m_data.m_plc_master_data;
|
||||||
|
m_plc_table->setRowCount(plc_data.ios.size());
|
||||||
|
if (m_plc_terminal_table)
|
||||||
|
m_plc_terminal_table->setRowCount(plc_data.ios.size());
|
||||||
|
|
||||||
|
for (int row = 0; row < plc_data.ios.size(); ++row) {
|
||||||
|
const auto &io = plc_data.ios.at(row);
|
||||||
|
|
||||||
|
// --- IO Table ---
|
||||||
|
auto *type_cb = new QComboBox(m_plc_table);
|
||||||
|
QStringList plc_types = ElementData::plcIOTypeList();
|
||||||
|
for (int t = 0; t < plc_types.size(); ++t) {
|
||||||
|
type_cb->addItem(plc_types.at(t), t);
|
||||||
|
}
|
||||||
|
type_cb->setCurrentIndex(static_cast<int>(io.type));
|
||||||
|
m_plc_table->setCellWidget(row, 0, type_cb);
|
||||||
|
|
||||||
|
m_plc_table->setItem(row, 1, new QTableWidgetItem(io.address));
|
||||||
|
m_plc_table->setItem(row, 2, new QTableWidgetItem(io.functionText));
|
||||||
|
m_plc_table->setItem(row, 3, new QTableWidgetItem(io.comment));
|
||||||
|
|
||||||
|
auto *crossref_item = new QTableWidgetItem(io.crossRef);
|
||||||
|
crossref_item->setFlags(crossref_item->flags() & ~Qt::ItemIsEditable);
|
||||||
|
m_plc_table->setItem(row, 4, crossref_item);
|
||||||
|
|
||||||
|
// --- Terminal Table ---
|
||||||
|
if (m_plc_terminal_table) {
|
||||||
|
int tc = qMax(1, io.terminalCount);
|
||||||
|
|
||||||
|
// Ensure enough columns
|
||||||
|
while (m_plc_terminal_table->columnCount() < tc + 1)
|
||||||
|
m_plc_terminal_table->insertColumn(m_plc_terminal_table->columnCount());
|
||||||
|
|
||||||
|
auto *tc_sb = new QSpinBox(m_plc_terminal_table);
|
||||||
|
tc_sb->setMinimum(1);
|
||||||
|
tc_sb->setMaximum(4);
|
||||||
|
tc_sb->setValue(tc);
|
||||||
|
m_plc_terminal_table->setCellWidget(row, 0, tc_sb);
|
||||||
|
connect(tc_sb, QOverload<int>::of(&QSpinBox::valueChanged),
|
||||||
|
this, [this, row](int val) { plcTerminalCountChanged(row, val); });
|
||||||
|
|
||||||
|
for (int i = 0; i < m_plc_terminal_table->columnCount() - 1; ++i) {
|
||||||
|
QString val = (i < io.terminals.size()) ? io.terminals.at(i) : QString();
|
||||||
|
auto *item = new QTableWidgetItem(val);
|
||||||
|
if (i >= tc) {
|
||||||
|
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
||||||
|
item->setBackground(QColor(230, 230, 230));
|
||||||
|
}
|
||||||
|
m_plc_terminal_table->setItem(row, i + 1, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load display settings
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
int val = plc_data.breakPositions.value(i, 0);
|
||||||
|
bool enabled = (val > 0);
|
||||||
|
m_plc_break_checkboxes[i]->setChecked(enabled);
|
||||||
|
m_plc_break_spinboxes[i]->setValue(enabled ? val : 0);
|
||||||
|
}
|
||||||
|
m_plc_row_height_spinbox->setValue(static_cast<int>(plc_data.rowHeight));
|
||||||
|
|
||||||
|
// Load fonts
|
||||||
|
m_plc_header_font = plc_data.headerFont;
|
||||||
|
m_plc_cell_font = plc_data.cellFont;
|
||||||
|
if (m_plc_header_font.family().isEmpty()) {
|
||||||
|
m_plc_header_font = QFont(m_plc_table->font());
|
||||||
|
m_plc_header_font.setBold(true);
|
||||||
|
m_plc_header_font.setPointSize(8);
|
||||||
|
}
|
||||||
|
if (m_plc_cell_font.family().isEmpty()) {
|
||||||
|
m_plc_cell_font = QFont(m_plc_table->font());
|
||||||
|
m_plc_cell_font.setPointSize(8);
|
||||||
|
}
|
||||||
|
m_plc_header_font_btn->setText(tr("Police des en-têtes: %1 %2pt")
|
||||||
|
.arg(m_plc_header_font.family()).arg(m_plc_header_font.pointSize()));
|
||||||
|
m_plc_cell_font_btn->setText(tr("Police du texte: %1 %2pt")
|
||||||
|
.arg(m_plc_cell_font.family()).arg(m_plc_cell_font.pointSize()));
|
||||||
|
m_plc_show_headers_cb->setChecked(plc_data.showHeaders);
|
||||||
|
|
||||||
|
// UI columns 0-4 map to data model indices 0,1,2,3,4
|
||||||
|
const int ui_to_data[] = {0, 1, 2, 3, 4};
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
int di = ui_to_data[i];
|
||||||
|
m_plc_col_visibility_checkboxes.at(i)->setChecked(
|
||||||
|
plc_data.colVisible.value(di, true));
|
||||||
|
m_plc_col_width_spinboxes.at(i)->setValue(
|
||||||
|
static_cast<int>(plc_data.colWidths.value(di, 40)));
|
||||||
|
|
||||||
|
if (i < plc_data.columnNames.size())
|
||||||
|
m_plc_col_name_edits.at(i)->setText(plc_data.columnNames.at(i));
|
||||||
|
else
|
||||||
|
m_plc_col_name_edits.at(i)->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore column visual order (translate data model index → table col index)
|
||||||
|
QHeaderView *hdr = m_plc_table->horizontalHeader();
|
||||||
|
if (!plc_data.columnOrder.isEmpty()) {
|
||||||
|
const int data_to_table[] = {0, 1, 2, 3, 4};
|
||||||
|
for (int visual = 0; visual < plc_data.columnOrder.size(); ++visual) {
|
||||||
|
int data_idx = plc_data.columnOrder.at(visual);
|
||||||
|
if (data_idx < 0 || data_idx > 4) continue;
|
||||||
|
int logical = data_to_table[data_idx];
|
||||||
|
if (logical < 0 || logical >= 5) continue;
|
||||||
|
if (hdr->visualIndex(logical) != visual)
|
||||||
|
hdr->moveSection(hdr->visualIndex(logical), visual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementPropertiesEditorWidget::readPlcTable
|
||||||
|
* Read the PLC data from the table back into m_data
|
||||||
|
*/
|
||||||
|
void ElementPropertiesEditorWidget::readPlcTable()
|
||||||
|
{
|
||||||
|
if (!m_plc_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ElementData::PlcMasterData plc_data;
|
||||||
|
|
||||||
|
for (int row = 0; row < m_plc_table->rowCount(); ++row) {
|
||||||
|
ElementData::PlcIO io;
|
||||||
|
|
||||||
|
auto *type_cb = qobject_cast<QComboBox *>(m_plc_table->cellWidget(row, 0));
|
||||||
|
if (type_cb)
|
||||||
|
io.type = static_cast<ElementData::PlcIOType>(type_cb->currentData().toInt());
|
||||||
|
|
||||||
|
auto *addr_item = m_plc_table->item(row, 1);
|
||||||
|
if (addr_item)
|
||||||
|
io.address = addr_item->text();
|
||||||
|
|
||||||
|
auto *func_item = m_plc_table->item(row, 2);
|
||||||
|
if (func_item)
|
||||||
|
io.functionText = func_item->text();
|
||||||
|
|
||||||
|
auto *comment_item = m_plc_table->item(row, 3);
|
||||||
|
if (comment_item)
|
||||||
|
io.comment = comment_item->text();
|
||||||
|
|
||||||
|
auto *crossref_item = m_plc_table->item(row, 4);
|
||||||
|
if (crossref_item)
|
||||||
|
io.crossRef = crossref_item->text();
|
||||||
|
|
||||||
|
// Read terminal data from terminal table
|
||||||
|
if (m_plc_terminal_table && row < m_plc_terminal_table->rowCount()) {
|
||||||
|
auto *tc_sb = qobject_cast<QSpinBox *>(m_plc_terminal_table->cellWidget(row, 0));
|
||||||
|
if (tc_sb) {
|
||||||
|
io.terminalCount = tc_sb->value();
|
||||||
|
for (int i = 0; i < io.terminalCount && i < 4; ++i) {
|
||||||
|
auto *term_item = m_plc_terminal_table->item(row, i + 1);
|
||||||
|
io.terminals.append(term_item ? term_item->text() : QString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plc_data.ios.append(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
plc_data.rowHeight = m_plc_row_height_spinbox->value();
|
||||||
|
|
||||||
|
// Save break positions
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
if (m_plc_break_checkboxes[i]->isChecked())
|
||||||
|
plc_data.breakPositions.append(m_plc_break_spinboxes[i]->value());
|
||||||
|
else
|
||||||
|
plc_data.breakPositions.append(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI columns 0-4 map to data model indices 0,1,2,3,4
|
||||||
|
const int ui_to_data[] = {0, 1, 2, 3, 4};
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
int di = ui_to_data[i];
|
||||||
|
plc_data.colVisible[di] = m_plc_col_visibility_checkboxes.at(i)->isChecked();
|
||||||
|
plc_data.colWidths[di] = m_plc_col_width_spinboxes.at(i)->value();
|
||||||
|
|
||||||
|
QString name = m_plc_col_name_edits.at(i)->text().trimmed();
|
||||||
|
if (!name.isEmpty())
|
||||||
|
plc_data.columnNames.append(name);
|
||||||
|
else
|
||||||
|
plc_data.columnNames.append(QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save current column visual order (translate table col index → data model index)
|
||||||
|
QHeaderView *hdr = m_plc_table->horizontalHeader();
|
||||||
|
const int table_to_data[] = {0, 1, 2, 3, 4};
|
||||||
|
for (int visual = 0; visual < 5; ++visual) {
|
||||||
|
int table_col = hdr->logicalIndex(visual);
|
||||||
|
plc_data.columnOrder.append(table_to_data[table_col]);
|
||||||
|
}
|
||||||
|
|
||||||
|
plc_data.headerFont = m_plc_header_font;
|
||||||
|
plc_data.cellFont = m_plc_cell_font;
|
||||||
|
plc_data.showHeaders = m_plc_show_headers_cb->isChecked();
|
||||||
|
|
||||||
|
m_data.setPlcMasterData(plc_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementPropertiesEditorWidget::plcAddRow
|
||||||
|
* Add a new empty row to the PLC IO table
|
||||||
|
*/
|
||||||
|
void ElementPropertiesEditorWidget::plcAddRow()
|
||||||
|
{
|
||||||
|
if (!m_plc_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int row = m_plc_table->rowCount();
|
||||||
|
m_plc_table->insertRow(row);
|
||||||
|
|
||||||
|
// Type combo
|
||||||
|
auto *type_cb = new QComboBox(m_plc_table);
|
||||||
|
QStringList plc_types = ElementData::plcIOTypeList();
|
||||||
|
for (int t = 0; t < plc_types.size(); ++t) {
|
||||||
|
type_cb->addItem(plc_types.at(t), t);
|
||||||
|
}
|
||||||
|
m_plc_table->setCellWidget(row, 0, type_cb);
|
||||||
|
|
||||||
|
m_plc_table->setItem(row, 1, new QTableWidgetItem());
|
||||||
|
m_plc_table->setItem(row, 2, new QTableWidgetItem());
|
||||||
|
m_plc_table->setItem(row, 3, new QTableWidgetItem());
|
||||||
|
|
||||||
|
auto *crossref_item = new QTableWidgetItem();
|
||||||
|
crossref_item->setFlags(crossref_item->flags() & ~Qt::ItemIsEditable);
|
||||||
|
m_plc_table->setItem(row, 4, crossref_item);
|
||||||
|
|
||||||
|
// Terminal table row
|
||||||
|
if (m_plc_terminal_table) {
|
||||||
|
m_plc_terminal_table->insertRow(row);
|
||||||
|
|
||||||
|
// Ensure enough columns (at least Nb + T1)
|
||||||
|
while (m_plc_terminal_table->columnCount() < 2)
|
||||||
|
m_plc_terminal_table->insertColumn(m_plc_terminal_table->columnCount());
|
||||||
|
|
||||||
|
auto *tc_sb = new QSpinBox(m_plc_terminal_table);
|
||||||
|
tc_sb->setMinimum(1);
|
||||||
|
tc_sb->setMaximum(4);
|
||||||
|
tc_sb->setValue(1);
|
||||||
|
m_plc_terminal_table->setCellWidget(row, 0, tc_sb);
|
||||||
|
connect(tc_sb, QOverload<int>::of(&QSpinBox::valueChanged),
|
||||||
|
this, [this, row](int val) { plcTerminalCountChanged(row, val); });
|
||||||
|
|
||||||
|
// T1 editable, rest gray
|
||||||
|
auto *t1_item = new QTableWidgetItem();
|
||||||
|
m_plc_terminal_table->setItem(row, 1, t1_item);
|
||||||
|
|
||||||
|
for (int i = 2; i < m_plc_terminal_table->columnCount(); ++i) {
|
||||||
|
auto *item = new QTableWidgetItem();
|
||||||
|
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
||||||
|
item->setBackground(QColor(230, 230, 230));
|
||||||
|
m_plc_terminal_table->setItem(row, i, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementPropertiesEditorWidget::plcTerminalCountChanged
|
||||||
|
* Enable/disable T1-T4 cells and grow columns based on terminal count spinner for a row
|
||||||
|
*/
|
||||||
|
void ElementPropertiesEditorWidget::plcTerminalCountChanged(int row, int count)
|
||||||
|
{
|
||||||
|
Q_UNUSED(row)
|
||||||
|
if (!m_plc_terminal_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Find max terminal count across all rows
|
||||||
|
int max_tc = 1;
|
||||||
|
for (int r = 0; r < m_plc_terminal_table->rowCount(); ++r) {
|
||||||
|
auto *sb = qobject_cast<QSpinBox *>(m_plc_terminal_table->cellWidget(r, 0));
|
||||||
|
if (sb && sb->value() > max_tc)
|
||||||
|
max_tc = sb->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure enough columns
|
||||||
|
int needed = max_tc + 1; // +1 for Nb column
|
||||||
|
while (m_plc_terminal_table->columnCount() < needed)
|
||||||
|
m_plc_terminal_table->insertColumn(m_plc_terminal_table->columnCount());
|
||||||
|
|
||||||
|
// Rebuild headers
|
||||||
|
QStringList th;
|
||||||
|
th << tr("Nb.");
|
||||||
|
for (int i = 1; i < m_plc_terminal_table->columnCount(); ++i)
|
||||||
|
th << tr("T%1").arg(i);
|
||||||
|
m_plc_terminal_table->setHorizontalHeaderLabels(th);
|
||||||
|
|
||||||
|
// Enable/disable cells per row
|
||||||
|
for (int r = 0; r < m_plc_terminal_table->rowCount(); ++r) {
|
||||||
|
auto *sb = qobject_cast<QSpinBox *>(m_plc_terminal_table->cellWidget(r, 0));
|
||||||
|
int tc = sb ? sb->value() : 1;
|
||||||
|
for (int c = 1; c < m_plc_terminal_table->columnCount(); ++c) {
|
||||||
|
auto *item = m_plc_terminal_table->item(r, c);
|
||||||
|
if (!item) {
|
||||||
|
item = new QTableWidgetItem();
|
||||||
|
m_plc_terminal_table->setItem(r, c, item);
|
||||||
|
}
|
||||||
|
if (c <= tc) {
|
||||||
|
item->setFlags(item->flags() | Qt::ItemIsEditable);
|
||||||
|
item->setBackground(Qt::NoBrush);
|
||||||
|
} else {
|
||||||
|
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
||||||
|
item->setText(QString());
|
||||||
|
item->setBackground(QColor(230, 230, 230));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementPropertiesEditorWidget::plcRemoveRow
|
||||||
|
* Remove selected rows from the PLC IO table
|
||||||
|
*/
|
||||||
|
void ElementPropertiesEditorWidget::plcRemoveRow()
|
||||||
|
{
|
||||||
|
if (!m_plc_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QModelIndexList selected = m_plc_table->selectionModel()->selectedRows();
|
||||||
|
if (selected.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Remove from bottom to top
|
||||||
|
std::sort(selected.begin(), selected.end(),
|
||||||
|
[](const QModelIndex &a, const QModelIndex &b) { return a.row() > b.row(); });
|
||||||
|
|
||||||
|
for (const QModelIndex &idx : selected) {
|
||||||
|
m_plc_table->removeRow(idx.row());
|
||||||
|
if (m_plc_terminal_table && idx.row() < m_plc_terminal_table->rowCount())
|
||||||
|
m_plc_terminal_table->removeRow(idx.row());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementPropertiesEditorWidget::plcPasteFromClipboard
|
||||||
|
* Paste IO data from clipboard (tab-separated, e.g. from Excel)
|
||||||
|
*/
|
||||||
|
void ElementPropertiesEditorWidget::plcPasteFromClipboard()
|
||||||
|
{
|
||||||
|
if (!m_plc_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString clipboard_text = QApplication::clipboard()->text();
|
||||||
|
if (clipboard_text.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QStringList lines = clipboard_text.split('\n', Qt::SkipEmptyParts);
|
||||||
|
|
||||||
|
int start_row = m_plc_table->rowCount();
|
||||||
|
m_plc_table->setRowCount(start_row + lines.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < lines.size(); ++i) {
|
||||||
|
QStringList cells = lines.at(i).split('\t');
|
||||||
|
int row = start_row + i;
|
||||||
|
|
||||||
|
// Type combo
|
||||||
|
auto *type_cb = new QComboBox(m_plc_table);
|
||||||
|
QStringList plc_types = ElementData::plcIOTypeList();
|
||||||
|
for (int t = 0; t < plc_types.size(); ++t) {
|
||||||
|
type_cb->addItem(plc_types.at(t), t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to match type from clipboard
|
||||||
|
if (!cells.isEmpty()) {
|
||||||
|
QString type_str = cells.at(0).trimmed();
|
||||||
|
int type_idx = -1;
|
||||||
|
for (int t = 0; t < plc_types.size(); ++t) {
|
||||||
|
if (plc_types.at(t).compare(type_str, Qt::CaseInsensitive) == 0) {
|
||||||
|
type_idx = t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type_idx >= 0)
|
||||||
|
type_cb->setCurrentIndex(type_idx);
|
||||||
|
}
|
||||||
|
m_plc_table->setCellWidget(row, 0, type_cb);
|
||||||
|
|
||||||
|
// Address
|
||||||
|
m_plc_table->setItem(row, 1, new QTableWidgetItem(
|
||||||
|
cells.size() > 1 ? cells.at(1).trimmed() : QString()));
|
||||||
|
|
||||||
|
// Function text
|
||||||
|
m_plc_table->setItem(row, 2, new QTableWidgetItem(
|
||||||
|
cells.size() > 2 ? cells.at(2).trimmed() : QString()));
|
||||||
|
|
||||||
|
// Comment
|
||||||
|
m_plc_table->setItem(row, 3, new QTableWidgetItem(
|
||||||
|
cells.size() > 3 ? cells.at(3).trimmed() : QString()));
|
||||||
|
|
||||||
|
// CrossRef (read-only)
|
||||||
|
auto *crossref_item = new QTableWidgetItem(
|
||||||
|
cells.size() > 4 ? cells.at(4).trimmed() : QString());
|
||||||
|
crossref_item->setFlags(crossref_item->flags() & ~Qt::ItemIsEditable);
|
||||||
|
m_plc_table->setItem(row, 4, crossref_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementPropertiesEditorWidget::plcSelectHeaderFont
|
||||||
|
* Open font dialog to select column header font
|
||||||
|
*/
|
||||||
|
void ElementPropertiesEditorWidget::plcSelectHeaderFont()
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
QFont font = QFontDialog::getFont(&ok, m_plc_header_font, this,
|
||||||
|
tr("Police des en-têtes de colonnes"));
|
||||||
|
if (ok) {
|
||||||
|
m_plc_header_font = font;
|
||||||
|
m_plc_header_font_btn->setText(tr("Police des en-têtes: %1 %2pt")
|
||||||
|
.arg(font.family()).arg(font.pointSize()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementPropertiesEditorWidget::plcSelectCellFont
|
||||||
|
* Open font dialog to select cell text font
|
||||||
|
*/
|
||||||
|
void ElementPropertiesEditorWidget::plcSelectCellFont()
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
QFont font = QFontDialog::getFont(&ok, m_plc_cell_font, this,
|
||||||
|
tr("Police du texte des cellules"));
|
||||||
|
if (ok) {
|
||||||
|
m_plc_cell_font = font;
|
||||||
|
m_plc_cell_font_btn->setText(tr("Police du texte: %1 %2pt")
|
||||||
|
.arg(font.family()).arg(font.pointSize()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,13 @@
|
|||||||
#include <QAbstractButton>
|
#include <QAbstractButton>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
|
class QTableWidget;
|
||||||
|
class QSpinBox;
|
||||||
|
class QCheckBox;
|
||||||
|
class QGroupBox;
|
||||||
|
class QPushButton;
|
||||||
|
class QLineEdit;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ElementPropertiesEditorWidget;
|
class ElementPropertiesEditorWidget;
|
||||||
}
|
}
|
||||||
@@ -51,6 +58,9 @@ class ElementPropertiesEditorWidget : public QDialog
|
|||||||
void populateTree();
|
void populateTree();
|
||||||
void populateSlaveGroupsTable();
|
void populateSlaveGroupsTable();
|
||||||
void readSlaveGroupsFromTable();
|
void readSlaveGroupsFromTable();
|
||||||
|
void createPlcConfigWidgets();
|
||||||
|
void populatePlcTable();
|
||||||
|
void readPlcTable();
|
||||||
|
|
||||||
//SLOTS
|
//SLOTS
|
||||||
private slots:
|
private slots:
|
||||||
@@ -58,11 +68,33 @@ class ElementPropertiesEditorWidget : public QDialog
|
|||||||
void on_m_base_type_cb_currentIndexChanged(int index);
|
void on_m_base_type_cb_currentIndexChanged(int index);
|
||||||
void on_m_slave_groups_checkbox_toggled(bool checked);
|
void on_m_slave_groups_checkbox_toggled(bool checked);
|
||||||
void on_max_slaves_checkbox_toggled(bool checked);
|
void on_max_slaves_checkbox_toggled(bool checked);
|
||||||
|
void plcAddRow();
|
||||||
|
void plcRemoveRow();
|
||||||
|
void plcPasteFromClipboard();
|
||||||
|
void plcTerminalCountChanged(int row, int count);
|
||||||
|
void plcSelectHeaderFont();
|
||||||
|
void plcSelectCellFont();
|
||||||
|
|
||||||
//ATTRIBUTES
|
//ATTRIBUTES
|
||||||
private:
|
private:
|
||||||
Ui::ElementPropertiesEditorWidget *ui;
|
Ui::ElementPropertiesEditorWidget *ui;
|
||||||
ElementData m_data;
|
ElementData m_data;
|
||||||
|
|
||||||
|
// PLC configuration widgets (created programmatically)
|
||||||
|
QGroupBox *m_plc_gb = nullptr;
|
||||||
|
QTableWidget *m_plc_table = nullptr;
|
||||||
|
QTableWidget *m_plc_terminal_table = nullptr;
|
||||||
|
QCheckBox *m_plc_break_checkboxes[4] = {nullptr, nullptr, nullptr, nullptr};
|
||||||
|
QSpinBox *m_plc_break_spinboxes[4] = {nullptr, nullptr, nullptr, nullptr};
|
||||||
|
QSpinBox *m_plc_row_height_spinbox = nullptr;
|
||||||
|
QPushButton *m_plc_header_font_btn = nullptr;
|
||||||
|
QPushButton *m_plc_cell_font_btn = nullptr;
|
||||||
|
QCheckBox *m_plc_show_headers_cb = nullptr;
|
||||||
|
QFont m_plc_header_font;
|
||||||
|
QFont m_plc_cell_font;
|
||||||
|
QList<QCheckBox *> m_plc_col_visibility_checkboxes;
|
||||||
|
QList<QSpinBox *> m_plc_col_width_spinboxes;
|
||||||
|
QList<QLineEdit *> m_plc_col_name_edits;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ELEMENTPROPERTIESEDITORWIDGET_H
|
#endif // ELEMENTPROPERTIESEDITORWIDGET_H
|
||||||
|
|||||||
@@ -93,17 +93,17 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Élément maître</string>
|
<string>Élément maître</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_3">
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Type concret</string>
|
<string>Type concret</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QComboBox" name="m_master_type_cb"/>
|
<widget class="QComboBox" name="m_master_type_cb"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QCheckBox" name="max_slaves_checkbox">
|
<widget class="QCheckBox" name="max_slaves_checkbox">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "../ElementsCollection/elementslocation.h"
|
#include "../ElementsCollection/elementslocation.h"
|
||||||
#include "../editor/graphicspart/partline.h"
|
#include "../editor/graphicspart/partline.h"
|
||||||
|
#include "../properties/elementdata.h"
|
||||||
#include "../qetapp.h"
|
#include "../qetapp.h"
|
||||||
#include "../qetversion.h"
|
#include "../qetversion.h"
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
ElementPictureFactory* ElementPictureFactory::m_factory = nullptr;
|
ElementPictureFactory* ElementPictureFactory::m_factory = nullptr;
|
||||||
|
|
||||||
@@ -206,7 +208,25 @@ bool ElementPictureFactory::build(const ElementsLocation &location,
|
|||||||
tmp.setCosmetic(true);
|
tmp.setCosmetic(true);
|
||||||
low_painter.setPen(tmp);
|
low_painter.setPen(tmp);
|
||||||
|
|
||||||
//scroll of the Children of the Definition: Parts of the Drawing
|
//scroll of the Children of the Definition: Parts of the Drawing
|
||||||
|
// Extract PLC master data for rendering plc_table parts
|
||||||
|
QDomElement plc_master_data;
|
||||||
|
for (QDomNode node = dom.firstChild() ; !node.isNull() ; node = node.nextSibling())
|
||||||
|
{
|
||||||
|
QDomElement elmts = node.toElement();
|
||||||
|
if (elmts.isNull()) continue;
|
||||||
|
if (elmts.tagName() == "kindInformations") {
|
||||||
|
for (QDomNode ki = elmts.firstChild(); !ki.isNull(); ki = ki.nextSibling()) {
|
||||||
|
QDomElement ki_el = ki.toElement();
|
||||||
|
if (!ki_el.isNull() && ki_el.tagName() == "plcMasterData") {
|
||||||
|
plc_master_data = ki_el;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (QDomNode node = dom.firstChild() ; !node.isNull() ; node = node.nextSibling())
|
for (QDomNode node = dom.firstChild() ; !node.isNull() ; node = node.nextSibling())
|
||||||
{
|
{
|
||||||
QDomElement elmts = node.toElement();
|
QDomElement elmts = node.toElement();
|
||||||
@@ -223,9 +243,17 @@ bool ElementPictureFactory::build(const ElementsLocation &location,
|
|||||||
if (qde.isNull()) {
|
if (qde.isNull()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
parseElement(qde, painter, primitives_);
|
if (qde.tagName() == "plc_table") {
|
||||||
primitives fake_prim;
|
// Skip - PLC table is rendered at runtime by
|
||||||
parseElement(qde, low_painter, fake_prim);
|
// Element::drawPlcTable() from live ElementData.
|
||||||
|
// Rendering into the cached QPicture here causes
|
||||||
|
// intermittent crashes when drawPicture() replays
|
||||||
|
// complex font/text operations.
|
||||||
|
} else {
|
||||||
|
parseElement(qde, painter, primitives_);
|
||||||
|
primitives fake_prim;
|
||||||
|
parseElement(qde, low_painter, fake_prim);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1104,3 +1132,217 @@ void ElementPictureFactory::setPainterStyle(const QDomElement &dom, QPainter &pa
|
|||||||
painter.setPen(pen);
|
painter.setPen(pen);
|
||||||
painter.setBrush(brush);
|
painter.setBrush(brush);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ElementPictureFactory::parsePlcTable(const QDomElement &dom, const QDomElement &plc_data, QPainter &painter) const
|
||||||
|
{
|
||||||
|
qreal pos_x = dom.attribute("x", "0").toDouble();
|
||||||
|
qreal pos_y = dom.attribute("y", "0").toDouble();
|
||||||
|
|
||||||
|
if (plc_data.isNull()) return;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
qreal rowHeight = plc_data.attribute("rowHeight", "8.0").toDouble();
|
||||||
|
|
||||||
|
// Parse showHeaders
|
||||||
|
bool showHeaders = true;
|
||||||
|
auto xml_sh = plc_data.firstChildElement("showHeaders");
|
||||||
|
if (!xml_sh.isNull())
|
||||||
|
showHeaders = (xml_sh.text().trimmed() != "0");
|
||||||
|
|
||||||
|
qreal header_h = showHeaders ? (rowHeight + 2.0) : 0;
|
||||||
|
|
||||||
|
// Parse column widths
|
||||||
|
QMap<int, qreal> col_widths;
|
||||||
|
auto xml_cw = plc_data.firstChildElement("columnWidths");
|
||||||
|
for (auto xml_col = xml_cw.firstChildElement("column"); !xml_col.isNull();
|
||||||
|
xml_col = xml_col.nextSiblingElement("column")) {
|
||||||
|
int idx = xml_col.attribute("index").toInt();
|
||||||
|
qreal w = xml_col.attribute("width").toDouble();
|
||||||
|
if (w > 0) col_widths[idx] = w;
|
||||||
|
}
|
||||||
|
if (col_widths.isEmpty()) {
|
||||||
|
col_widths[0] = 35; col_widths[1] = 25; col_widths[2] = 50;
|
||||||
|
col_widths[3] = 40; col_widths[4] = 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse column visibility
|
||||||
|
QMap<int, bool> col_visible;
|
||||||
|
auto xml_cv = plc_data.firstChildElement("columnVisibility");
|
||||||
|
for (auto xml_c = xml_cv.firstChildElement("column"); !xml_c.isNull();
|
||||||
|
xml_c = xml_c.nextSiblingElement("column")) {
|
||||||
|
int idx = xml_c.attribute("index").toInt();
|
||||||
|
QString vis = xml_c.attribute("visible", "true");
|
||||||
|
col_visible[idx] = (vis == "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse column order
|
||||||
|
QList<int> column_order;
|
||||||
|
auto xml_ord = plc_data.firstChildElement("columnOrder");
|
||||||
|
if (!xml_ord.isNull()) {
|
||||||
|
QStringList parts = xml_ord.text().split(",", Qt::SkipEmptyParts);
|
||||||
|
for (const QString &s : parts) {
|
||||||
|
int idx = s.trimmed().toInt();
|
||||||
|
if (idx >= 0 && idx < COL_COUNT)
|
||||||
|
column_order.append(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse custom column names
|
||||||
|
QMap<int, QString> column_names;
|
||||||
|
auto xml_names = plc_data.firstChildElement("columnNames");
|
||||||
|
for (auto xml_n = xml_names.firstChildElement("column"); !xml_n.isNull();
|
||||||
|
xml_n = xml_n.nextSiblingElement("column")) {
|
||||||
|
int idx = xml_n.attribute("index").toInt();
|
||||||
|
QString name = xml_n.text().trimmed();
|
||||||
|
if (!name.isEmpty())
|
||||||
|
column_names[idx] = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse fonts
|
||||||
|
QFont header_font;
|
||||||
|
header_font.setPointSize(7);
|
||||||
|
header_font.setBold(true);
|
||||||
|
auto xml_hfont = plc_data.firstChildElement("headerFont");
|
||||||
|
if (!xml_hfont.isNull()) {
|
||||||
|
if (!xml_hfont.attribute("family").isEmpty())
|
||||||
|
header_font.setFamily(xml_hfont.attribute("family"));
|
||||||
|
if (xml_hfont.attribute("size").toInt() > 0)
|
||||||
|
header_font.setPointSize(xml_hfont.attribute("size").toInt());
|
||||||
|
header_font.setBold(xml_hfont.attribute("bold") == "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
QFont cell_font;
|
||||||
|
cell_font.setPointSize(7);
|
||||||
|
auto xml_cfont = plc_data.firstChildElement("cellFont");
|
||||||
|
if (!xml_cfont.isNull()) {
|
||||||
|
if (!xml_cfont.attribute("family").isEmpty())
|
||||||
|
cell_font.setFamily(xml_cfont.attribute("family"));
|
||||||
|
if (xml_cfont.attribute("size").toInt() > 0)
|
||||||
|
cell_font.setPointSize(xml_cfont.attribute("size").toInt());
|
||||||
|
cell_font.setBold(xml_cfont.attribute("bold") == "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse IOs
|
||||||
|
QVector<QMap<int, QString>> ios;
|
||||||
|
auto xml_ios = plc_data.firstChildElement("plcIOs");
|
||||||
|
for (auto xml_io = xml_ios.firstChildElement("plcIO"); !xml_io.isNull();
|
||||||
|
xml_io = xml_io.nextSiblingElement("plcIO")) {
|
||||||
|
QMap<int, QString> io_data;
|
||||||
|
io_data[COL_TYPE] = ElementData::translatedPlcIOType(
|
||||||
|
ElementData::plcIOTypeFromString(xml_io.attribute("type")));
|
||||||
|
io_data[COL_ADDRESS] = xml_io.attribute("address");
|
||||||
|
io_data[COL_FUNCTION] = xml_io.attribute("functionText");
|
||||||
|
io_data[COL_COMMENT] = xml_io.attribute("comment");
|
||||||
|
io_data[COL_CROSSREF] = xml_io.attribute("crossRef");
|
||||||
|
ios.append(io_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build visible columns (respect column order)
|
||||||
|
QList<int> visible_cols;
|
||||||
|
if (!column_order.isEmpty()) {
|
||||||
|
for (int logical : column_order) {
|
||||||
|
if (logical >= 0 && logical < COL_COUNT
|
||||||
|
&& col_visible.value(logical, true)
|
||||||
|
&& !visible_cols.contains(logical))
|
||||||
|
visible_cols.append(logical);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < COL_COUNT; ++i) {
|
||||||
|
if (col_visible.value(i, true) && !visible_cols.contains(i))
|
||||||
|
visible_cols.append(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < COL_COUNT; ++i) {
|
||||||
|
if (col_visible.value(i, true))
|
||||||
|
visible_cols.append(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (visible_cols.isEmpty())
|
||||||
|
visible_cols << COL_TYPE << COL_ADDRESS << COL_FUNCTION;
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
for (auto it = column_names.constBegin(); it != column_names.constEnd(); ++it)
|
||||||
|
headers[it.key()] = it.value();
|
||||||
|
|
||||||
|
// Parse break positions
|
||||||
|
QList<int> breaks;
|
||||||
|
auto xml_breaks = plc_data.firstChildElement("breakPositions");
|
||||||
|
for (auto xml_bp = xml_breaks.firstChildElement("break"); !xml_bp.isNull();
|
||||||
|
xml_bp = xml_bp.nextSiblingElement("break")) {
|
||||||
|
int bp = xml_bp.text().toInt();
|
||||||
|
if (bp > 0 && bp < ios.size() && !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.value(col, 30);
|
||||||
|
|
||||||
|
QPen border_pen(Qt::black, 0.5);
|
||||||
|
|
||||||
|
int total_ios = ios.size();
|
||||||
|
|
||||||
|
for (int block = 0; block < block_count; ++block) {
|
||||||
|
qreal bx = pos_x + block * (total_width + 3);
|
||||||
|
qreal cx = bx;
|
||||||
|
|
||||||
|
// Draw headers
|
||||||
|
if (showHeaders) {
|
||||||
|
painter.fillRect(QRectF(cx, pos_y, total_width, header_h), QColor(220, 220, 220));
|
||||||
|
painter.setPen(border_pen);
|
||||||
|
painter.setBrush(Qt::NoBrush);
|
||||||
|
painter.setFont(header_font);
|
||||||
|
for (int col : visible_cols) {
|
||||||
|
QRectF hr(cx, pos_y, col_widths.value(col, 30), 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.value(col, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 >= ios.size()) break;
|
||||||
|
const auto &io = ios.at(io_idx);
|
||||||
|
qreal ry = pos_y + header_h + row * rowHeight;
|
||||||
|
cx = bx;
|
||||||
|
|
||||||
|
for (int col : visible_cols) {
|
||||||
|
QRectF cr(cx, ry, col_widths.value(col, 30), rowHeight);
|
||||||
|
painter.setPen(border_pen);
|
||||||
|
painter.setBrush(Qt::NoBrush);
|
||||||
|
painter.drawRect(cr);
|
||||||
|
|
||||||
|
QString cell_text = io.value(col, QString());
|
||||||
|
QRectF text_rect = cr.adjusted(1, 0, -1, 0);
|
||||||
|
painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, cell_text);
|
||||||
|
|
||||||
|
cx += col_widths.value(col, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ class ElementPictureFactory
|
|||||||
void parseArc (const QDomElement &dom, QPainter &painter, primitives &prim) const;
|
void parseArc (const QDomElement &dom, QPainter &painter, primitives &prim) const;
|
||||||
void parsePolygon(const QDomElement &dom, QPainter &painter, primitives &prim) const;
|
void parsePolygon(const QDomElement &dom, QPainter &painter, primitives &prim) const;
|
||||||
void parseText (const QDomElement &dom, QPainter &painter, primitives &prim) const;
|
void parseText (const QDomElement &dom, QPainter &painter, primitives &prim) const;
|
||||||
|
void parsePlcTable(const QDomElement &dom, const QDomElement &plc_data, QPainter &painter) const;
|
||||||
void setPainterStyle(const QDomElement &dom, QPainter &painter) const;
|
void setPainterStyle(const QDomElement &dom, QPainter &painter) const;
|
||||||
|
|
||||||
QHash<QUuid, QPicture> m_pictures_H;
|
QHash<QUuid, QPicture> m_pictures_H;
|
||||||
|
|||||||
@@ -112,6 +112,115 @@ QDomElement ElementData::kindInfoToXml(QDomDocument &document)
|
|||||||
}
|
}
|
||||||
returned_elmt.appendChild(xml_groups);
|
returned_elmt.appendChild(xml_groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save PLC master data if type is PLC
|
||||||
|
if (m_master_type == ElementData::PLC) {
|
||||||
|
auto xml_plc = document.createElement(QStringLiteral("plcMasterData"));
|
||||||
|
xml_plc.setAttribute(QStringLiteral("rowHeight"),
|
||||||
|
QString::number(m_plc_master_data.rowHeight, 'f', 2));
|
||||||
|
|
||||||
|
// Save break positions
|
||||||
|
{
|
||||||
|
auto xml_breaks = document.createElement(QStringLiteral("breakPositions"));
|
||||||
|
for (int bp : m_plc_master_data.breakPositions) {
|
||||||
|
auto xml_bp = document.createElement(QStringLiteral("break"));
|
||||||
|
xml_bp.appendChild(document.createTextNode(QString::number(bp)));
|
||||||
|
xml_breaks.appendChild(xml_bp);
|
||||||
|
}
|
||||||
|
xml_plc.appendChild(xml_breaks);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save column widths
|
||||||
|
auto xml_col_widths = document.createElement(QStringLiteral("columnWidths"));
|
||||||
|
for (auto it = m_plc_master_data.colWidths.constBegin();
|
||||||
|
it != m_plc_master_data.colWidths.constEnd(); ++it) {
|
||||||
|
auto xml_col = document.createElement(QStringLiteral("column"));
|
||||||
|
xml_col.setAttribute(QStringLiteral("index"), it.key());
|
||||||
|
xml_col.setAttribute(QStringLiteral("width"), QString::number(it.value(), 'f', 2));
|
||||||
|
xml_col_widths.appendChild(xml_col);
|
||||||
|
}
|
||||||
|
xml_plc.appendChild(xml_col_widths);
|
||||||
|
|
||||||
|
// Save column visibility
|
||||||
|
auto xml_col_vis = document.createElement(QStringLiteral("columnVisibility"));
|
||||||
|
for (auto it = m_plc_master_data.colVisible.constBegin();
|
||||||
|
it != m_plc_master_data.colVisible.constEnd(); ++it) {
|
||||||
|
auto xml_col = document.createElement(QStringLiteral("column"));
|
||||||
|
xml_col.setAttribute(QStringLiteral("index"), it.key());
|
||||||
|
xml_col.setAttribute(QStringLiteral("visible"), it.value() ? "true" : "false");
|
||||||
|
xml_col_vis.appendChild(xml_col);
|
||||||
|
}
|
||||||
|
xml_plc.appendChild(xml_col_vis);
|
||||||
|
|
||||||
|
// Save fonts
|
||||||
|
if (!m_plc_master_data.headerFont.family().isEmpty()) {
|
||||||
|
auto xml_hfont = document.createElement(QStringLiteral("headerFont"));
|
||||||
|
xml_hfont.setAttribute(QStringLiteral("family"), m_plc_master_data.headerFont.family());
|
||||||
|
xml_hfont.setAttribute(QStringLiteral("size"), m_plc_master_data.headerFont.pointSize());
|
||||||
|
xml_hfont.setAttribute(QStringLiteral("bold"), m_plc_master_data.headerFont.bold() ? "true" : "false");
|
||||||
|
xml_plc.appendChild(xml_hfont);
|
||||||
|
}
|
||||||
|
if (!m_plc_master_data.cellFont.family().isEmpty()) {
|
||||||
|
auto xml_cfont = document.createElement(QStringLiteral("cellFont"));
|
||||||
|
xml_cfont.setAttribute(QStringLiteral("family"), m_plc_master_data.cellFont.family());
|
||||||
|
xml_cfont.setAttribute(QStringLiteral("size"), m_plc_master_data.cellFont.pointSize());
|
||||||
|
xml_cfont.setAttribute(QStringLiteral("bold"), m_plc_master_data.cellFont.bold() ? "true" : "false");
|
||||||
|
xml_plc.appendChild(xml_cfont);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save custom column names
|
||||||
|
if (!m_plc_master_data.columnNames.isEmpty()) {
|
||||||
|
auto xml_names = document.createElement(QStringLiteral("columnNames"));
|
||||||
|
for (int i = 0; i < m_plc_master_data.columnNames.size(); ++i) {
|
||||||
|
auto xml_name = document.createElement(QStringLiteral("column"));
|
||||||
|
xml_name.setAttribute(QStringLiteral("index"), i);
|
||||||
|
xml_name.appendChild(document.createTextNode(m_plc_master_data.columnNames.at(i)));
|
||||||
|
xml_names.appendChild(xml_name);
|
||||||
|
}
|
||||||
|
xml_plc.appendChild(xml_names);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save column order
|
||||||
|
if (!m_plc_master_data.columnOrder.isEmpty()) {
|
||||||
|
auto xml_order = document.createElement(QStringLiteral("columnOrder"));
|
||||||
|
QString order_str;
|
||||||
|
for (int i = 0; i < m_plc_master_data.columnOrder.size(); ++i) {
|
||||||
|
if (i > 0) order_str += QStringLiteral(",");
|
||||||
|
order_str += QString::number(m_plc_master_data.columnOrder.at(i));
|
||||||
|
}
|
||||||
|
xml_order.appendChild(document.createTextNode(order_str));
|
||||||
|
xml_plc.appendChild(xml_order);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save showHeaders
|
||||||
|
{
|
||||||
|
auto xml_sh = document.createElement(QStringLiteral("showHeaders"));
|
||||||
|
xml_sh.appendChild(document.createTextNode(
|
||||||
|
m_plc_master_data.showHeaders ? QStringLiteral("1") : QStringLiteral("0")));
|
||||||
|
xml_plc.appendChild(xml_sh);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save IO entries
|
||||||
|
auto xml_ios = document.createElement(QStringLiteral("plcIOs"));
|
||||||
|
for (const auto &io : m_plc_master_data.ios) {
|
||||||
|
auto xml_io = document.createElement(QStringLiteral("plcIO"));
|
||||||
|
xml_io.setAttribute(QStringLiteral("type"), plcIOTypeToString(io.type));
|
||||||
|
xml_io.setAttribute(QStringLiteral("address"), io.address);
|
||||||
|
xml_io.setAttribute(QStringLiteral("functionText"), io.functionText);
|
||||||
|
xml_io.setAttribute(QStringLiteral("comment"), io.comment);
|
||||||
|
xml_io.setAttribute(QStringLiteral("crossRef"), io.crossRef);
|
||||||
|
xml_io.setAttribute(QStringLiteral("terminalCount"), io.terminalCount);
|
||||||
|
for (const auto &t : io.terminals) {
|
||||||
|
auto xml_term = document.createElement(QStringLiteral("terminal"));
|
||||||
|
xml_term.appendChild(document.createTextNode(t));
|
||||||
|
xml_io.appendChild(xml_term);
|
||||||
|
}
|
||||||
|
xml_ios.appendChild(xml_io);
|
||||||
|
}
|
||||||
|
xml_plc.appendChild(xml_ios);
|
||||||
|
|
||||||
|
returned_elmt.appendChild(xml_plc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (m_type == ElementData::Slave)
|
else if (m_type == ElementData::Slave)
|
||||||
{
|
{
|
||||||
@@ -270,6 +379,11 @@ bool ElementData::operator==(const ElementData &data) const
|
|||||||
if (data.m_slave_contact_groups != m_slave_contact_groups) {
|
if (data.m_slave_contact_groups != m_slave_contact_groups) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (data.m_master_type == ElementData::PLC) {
|
||||||
|
if (data.m_plc_master_data != m_plc_master_data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (data.m_type == ElementData::Slave) {
|
else if (data.m_type == ElementData::Slave) {
|
||||||
if (data.m_slave_state != m_slave_state ||
|
if (data.m_slave_state != m_slave_state ||
|
||||||
@@ -410,6 +524,8 @@ QString ElementData::masterTypeToString(ElementData::MasterType type)
|
|||||||
return QStringLiteral("protection");
|
return QStringLiteral("protection");
|
||||||
case ElementData::Commutator:
|
case ElementData::Commutator:
|
||||||
return QStringLiteral("commutator");
|
return QStringLiteral("commutator");
|
||||||
|
case ElementData::PLC:
|
||||||
|
return QStringLiteral("plc");
|
||||||
}
|
}
|
||||||
return QStringLiteral("coil");
|
return QStringLiteral("coil");
|
||||||
}
|
}
|
||||||
@@ -422,6 +538,8 @@ ElementData::MasterType ElementData::masterTypeFromString(const QString &string)
|
|||||||
return ElementData::Protection;
|
return ElementData::Protection;
|
||||||
} else if (string == QLatin1String("commutator")) {
|
} else if (string == QLatin1String("commutator")) {
|
||||||
return ElementData::Commutator;
|
return ElementData::Commutator;
|
||||||
|
} else if (string == QLatin1String("plc")) {
|
||||||
|
return ElementData::PLC;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "ElementData::masterTypeFromString : string "
|
qDebug() << "ElementData::masterTypeFromString : string "
|
||||||
@@ -443,6 +561,8 @@ QString ElementData::slaveTypeToString(ElementData::SlaveType type)
|
|||||||
return QStringLiteral("delayOff");
|
return QStringLiteral("delayOff");
|
||||||
case ElementData::delayOnOff:
|
case ElementData::delayOnOff:
|
||||||
return QStringLiteral("delayOnOff");
|
return QStringLiteral("delayOnOff");
|
||||||
|
case ElementData::PLCSlave:
|
||||||
|
return QStringLiteral("plc");
|
||||||
}
|
}
|
||||||
return QStringLiteral("simple");
|
return QStringLiteral("simple");
|
||||||
}
|
}
|
||||||
@@ -459,6 +579,8 @@ ElementData::SlaveType ElementData::slaveTypeFromString(const QString &string)
|
|||||||
return ElementData::DelayOff;
|
return ElementData::DelayOff;
|
||||||
} else if (string == QLatin1String("delayOnOff")) {
|
} else if (string == QLatin1String("delayOnOff")) {
|
||||||
return ElementData::delayOnOff;
|
return ElementData::delayOnOff;
|
||||||
|
} else if (string == QLatin1String("plc")) {
|
||||||
|
return ElementData::PLCSlave;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "ElementData::slaveTypeFromSting : string "
|
qDebug() << "ElementData::slaveTypeFromSting : string "
|
||||||
@@ -658,6 +780,108 @@ void ElementData::kindInfoFromXml(const QDomElement &xml_element)
|
|||||||
m_slave_contact_groups.append(group);
|
m_slave_contact_groups.append(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse PLC master data
|
||||||
|
auto xml_plc = xml_kind.firstChildElement(QStringLiteral("plcMasterData"));
|
||||||
|
if (!xml_plc.isNull()) {
|
||||||
|
m_plc_master_data.rowHeight = xml_plc.attribute(
|
||||||
|
QStringLiteral("rowHeight"), QStringLiteral("8.00")).toDouble();
|
||||||
|
|
||||||
|
// Parse break positions (new format: child elements)
|
||||||
|
auto xml_breaks = xml_plc.firstChildElement(QStringLiteral("breakPositions"));
|
||||||
|
if (!xml_breaks.isNull()) {
|
||||||
|
for (const auto &xml_bp : QETXML::findInDomElement(xml_breaks, QStringLiteral("break"))) {
|
||||||
|
m_plc_master_data.breakPositions.append(xml_bp.text().toInt());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Backward compat: old format was breakAfter attribute
|
||||||
|
int old_break = xml_plc.attribute(
|
||||||
|
QStringLiteral("breakAfter"), QStringLiteral("0")).toInt();
|
||||||
|
m_plc_master_data.breakPositions.append(old_break > 0 ? old_break : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse column widths
|
||||||
|
auto xml_col_widths = xml_plc.firstChildElement(QStringLiteral("columnWidths"));
|
||||||
|
for (const auto &xml_col : QETXML::findInDomElement(xml_col_widths, QStringLiteral("column"))) {
|
||||||
|
int index = xml_col.attribute(QStringLiteral("index")).toInt();
|
||||||
|
qreal width = xml_col.attribute(QStringLiteral("width")).toDouble();
|
||||||
|
m_plc_master_data.colWidths[index] = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse column visibility
|
||||||
|
auto xml_col_vis = xml_plc.firstChildElement(QStringLiteral("columnVisibility"));
|
||||||
|
for (const auto &xml_col : QETXML::findInDomElement(xml_col_vis, QStringLiteral("column"))) {
|
||||||
|
int index = xml_col.attribute(QStringLiteral("index")).toInt();
|
||||||
|
bool visible = xml_col.attribute(QStringLiteral("visible")) == QLatin1String("true");
|
||||||
|
m_plc_master_data.colVisible[index] = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse fonts
|
||||||
|
auto xml_hfont = xml_plc.firstChildElement(QStringLiteral("headerFont"));
|
||||||
|
if (!xml_hfont.isNull()) {
|
||||||
|
m_plc_master_data.headerFont.setFamily(xml_hfont.attribute(QStringLiteral("family")));
|
||||||
|
m_plc_master_data.headerFont.setPointSize(xml_hfont.attribute(QStringLiteral("size"), QStringLiteral("8")).toInt());
|
||||||
|
m_plc_master_data.headerFont.setBold(xml_hfont.attribute(QStringLiteral("bold")) == QLatin1String("true"));
|
||||||
|
}
|
||||||
|
auto xml_cfont = xml_plc.firstChildElement(QStringLiteral("cellFont"));
|
||||||
|
if (!xml_cfont.isNull()) {
|
||||||
|
m_plc_master_data.cellFont.setFamily(xml_cfont.attribute(QStringLiteral("family")));
|
||||||
|
m_plc_master_data.cellFont.setPointSize(xml_cfont.attribute(QStringLiteral("size"), QStringLiteral("8")).toInt());
|
||||||
|
m_plc_master_data.cellFont.setBold(xml_cfont.attribute(QStringLiteral("bold")) == QLatin1String("true"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse custom column names
|
||||||
|
auto xml_names = xml_plc.firstChildElement(QStringLiteral("columnNames"));
|
||||||
|
if (!xml_names.isNull()) {
|
||||||
|
for (const auto &xml_col : QETXML::findInDomElement(xml_names, QStringLiteral("column"))) {
|
||||||
|
int index = xml_col.attribute(QStringLiteral("index")).toInt();
|
||||||
|
if (index < 0 || index >= 5)
|
||||||
|
continue;
|
||||||
|
while (m_plc_master_data.columnNames.size() <= index)
|
||||||
|
m_plc_master_data.columnNames.append(QString());
|
||||||
|
m_plc_master_data.columnNames[index] = xml_col.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse column order
|
||||||
|
auto xml_order = xml_plc.firstChildElement(QStringLiteral("columnOrder"));
|
||||||
|
if (!xml_order.isNull()) {
|
||||||
|
QStringList parts = xml_order.text().split(',');
|
||||||
|
for (const auto &p : parts) {
|
||||||
|
if (m_plc_master_data.columnOrder.size() >= 5)
|
||||||
|
break;
|
||||||
|
bool ok;
|
||||||
|
int val = p.trimmed().toInt(&ok);
|
||||||
|
if (ok)
|
||||||
|
m_plc_master_data.columnOrder.append(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse showHeaders
|
||||||
|
auto xml_sh = xml_plc.firstChildElement(QStringLiteral("showHeaders"));
|
||||||
|
if (!xml_sh.isNull())
|
||||||
|
m_plc_master_data.showHeaders = (xml_sh.text().trimmed() != QStringLiteral("0"));
|
||||||
|
|
||||||
|
// Parse IO entries
|
||||||
|
auto xml_ios = xml_plc.firstChildElement(QStringLiteral("plcIOs"));
|
||||||
|
for (const auto &xml_io : QETXML::findInDomElement(xml_ios, QStringLiteral("plcIO"))) {
|
||||||
|
if (m_plc_master_data.ios.size() >= 128)
|
||||||
|
break;
|
||||||
|
PlcIO io;
|
||||||
|
io.type = plcIOTypeFromString(
|
||||||
|
xml_io.attribute(QStringLiteral("type"), QStringLiteral("entree_digitale")));
|
||||||
|
io.address = xml_io.attribute(QStringLiteral("address"));
|
||||||
|
io.functionText = xml_io.attribute(QStringLiteral("functionText"));
|
||||||
|
io.comment = xml_io.attribute(QStringLiteral("comment"));
|
||||||
|
io.crossRef = xml_io.attribute(QStringLiteral("crossRef"));
|
||||||
|
io.terminalCount = xml_io.attribute(
|
||||||
|
QStringLiteral("terminalCount"), QStringLiteral("0")).toInt();
|
||||||
|
for (const auto &xml_term : QETXML::findInDomElement(xml_io, QStringLiteral("terminal"))) {
|
||||||
|
io.terminals.append(xml_term.text());
|
||||||
|
}
|
||||||
|
m_plc_master_data.ios.append(io);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -697,3 +921,90 @@ ElementData::SlaveType ElementData::slaveContactGroupSubtypeFromString(const QSt
|
|||||||
{
|
{
|
||||||
return slaveTypeFromString(string);
|
return slaveTypeFromString(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementData::plcIOTypeToString
|
||||||
|
* Convert a PlcIOType to string for XML storage.
|
||||||
|
*/
|
||||||
|
QString ElementData::plcIOTypeToString(PlcIOType type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case EntreeDigitale:
|
||||||
|
return QStringLiteral("entree_digitale");
|
||||||
|
case SortieDigitale:
|
||||||
|
return QStringLiteral("sortie_digitale");
|
||||||
|
case EntreeAnalogique:
|
||||||
|
return QStringLiteral("entree_analogique");
|
||||||
|
case SortieAnalogique:
|
||||||
|
return QStringLiteral("sortie_analogique");
|
||||||
|
case EntreeUniverselle:
|
||||||
|
return QStringLiteral("entree_universelle");
|
||||||
|
case SortieUniverselle:
|
||||||
|
return QStringLiteral("sortie_universelle");
|
||||||
|
}
|
||||||
|
return QStringLiteral("entree_digitale");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementData::plcIOTypeFromString
|
||||||
|
* Convert a string from XML to PlcIOType.
|
||||||
|
*/
|
||||||
|
ElementData::PlcIOType ElementData::plcIOTypeFromString(const QString &string)
|
||||||
|
{
|
||||||
|
if (string == QLatin1String("entree_digitale")) {
|
||||||
|
return ElementData::EntreeDigitale;
|
||||||
|
} else if (string == QLatin1String("sortie_digitale")) {
|
||||||
|
return ElementData::SortieDigitale;
|
||||||
|
} else if (string == QLatin1String("entree_analogique")) {
|
||||||
|
return ElementData::EntreeAnalogique;
|
||||||
|
} else if (string == QLatin1String("sortie_analogique")) {
|
||||||
|
return ElementData::SortieAnalogique;
|
||||||
|
} else if (string == QLatin1String("entree_universelle")) {
|
||||||
|
return ElementData::EntreeUniverselle;
|
||||||
|
} else if (string == QLatin1String("sortie_universelle")) {
|
||||||
|
return ElementData::SortieUniverselle;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "ElementData::plcIOTypeFromString : string "
|
||||||
|
<< string
|
||||||
|
<< " don't exist, return failsafe value 'entree_digitale'";
|
||||||
|
return ElementData::EntreeDigitale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementData::translatedPlcIOType
|
||||||
|
* Return translated string for PlcIOType.
|
||||||
|
*/
|
||||||
|
QString ElementData::translatedPlcIOType(PlcIOType type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case EntreeDigitale:
|
||||||
|
return QObject::tr("Entrée digitale");
|
||||||
|
case SortieDigitale:
|
||||||
|
return QObject::tr("Sortie digitale");
|
||||||
|
case EntreeAnalogique:
|
||||||
|
return QObject::tr("Entrée analogique");
|
||||||
|
case SortieAnalogique:
|
||||||
|
return QObject::tr("Sortie analogique");
|
||||||
|
case EntreeUniverselle:
|
||||||
|
return QObject::tr("Entrée universelle");
|
||||||
|
case SortieUniverselle:
|
||||||
|
return QObject::tr("Sortie universelle");
|
||||||
|
}
|
||||||
|
return QObject::tr("Entrée digitale");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ElementData::plcIOTypeList
|
||||||
|
* Return list of all translated PLC IO type strings (for combo boxes).
|
||||||
|
*/
|
||||||
|
QStringList ElementData::plcIOTypeList()
|
||||||
|
{
|
||||||
|
return QStringList()
|
||||||
|
<< translatedPlcIOType(EntreeDigitale)
|
||||||
|
<< translatedPlcIOType(SortieDigitale)
|
||||||
|
<< translatedPlcIOType(EntreeAnalogique)
|
||||||
|
<< translatedPlcIOType(SortieAnalogique)
|
||||||
|
<< translatedPlcIOType(EntreeUniverselle)
|
||||||
|
<< translatedPlcIOType(SortieUniverselle);
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
#include <QFont>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The ElementData class
|
* @brief The ElementData class
|
||||||
@@ -52,7 +53,8 @@ class ElementData : public PropertiesInterface
|
|||||||
enum MasterType {
|
enum MasterType {
|
||||||
Coil,
|
Coil,
|
||||||
Protection,
|
Protection,
|
||||||
Commutator
|
Commutator,
|
||||||
|
PLC
|
||||||
};
|
};
|
||||||
Q_ENUM(MasterType)
|
Q_ENUM(MasterType)
|
||||||
|
|
||||||
@@ -61,7 +63,8 @@ class ElementData : public PropertiesInterface
|
|||||||
Power,
|
Power,
|
||||||
DelayOn,
|
DelayOn,
|
||||||
DelayOff,
|
DelayOff,
|
||||||
delayOnOff
|
delayOnOff,
|
||||||
|
PLCSlave
|
||||||
};
|
};
|
||||||
Q_ENUM(SlaveType)
|
Q_ENUM(SlaveType)
|
||||||
|
|
||||||
@@ -73,6 +76,82 @@ class ElementData : public PropertiesInterface
|
|||||||
};
|
};
|
||||||
Q_ENUM(SlaveState)
|
Q_ENUM(SlaveState)
|
||||||
|
|
||||||
|
enum PlcIOType {
|
||||||
|
EntreeDigitale,
|
||||||
|
SortieDigitale,
|
||||||
|
EntreeAnalogique,
|
||||||
|
SortieAnalogique,
|
||||||
|
EntreeUniverselle,
|
||||||
|
SortieUniverselle
|
||||||
|
};
|
||||||
|
Q_ENUM(PlcIOType)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The PlcIO struct
|
||||||
|
* Represents a single IO entry in a PLC master element.
|
||||||
|
*/
|
||||||
|
struct PlcIO {
|
||||||
|
PlcIOType type = EntreeDigitale;
|
||||||
|
QString address; ///< e.g. "1.0", "1.1"
|
||||||
|
QString functionText; ///< e.g. "Motor K1"
|
||||||
|
QString comment; ///< Optional comment
|
||||||
|
QString crossRef; ///< Auto-filled: slave reference
|
||||||
|
int terminalCount = 1; ///< Number of terminals for this IO (1-4)
|
||||||
|
QStringList terminals; ///< Terminal values T1, T2, ... (size = terminalCount)
|
||||||
|
|
||||||
|
bool operator==(const PlcIO &other) const {
|
||||||
|
return type == other.type
|
||||||
|
&& address == other.address
|
||||||
|
&& functionText == other.functionText
|
||||||
|
&& comment == other.comment
|
||||||
|
&& crossRef == other.crossRef
|
||||||
|
&& terminalCount == other.terminalCount
|
||||||
|
&& terminals == other.terminals;
|
||||||
|
}
|
||||||
|
bool operator!=(const PlcIO &other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The PlcMasterData struct
|
||||||
|
* Stores all PLC master configuration: IO table, display settings, column visibility.
|
||||||
|
* All data is stored in the .elmt file for easy sharing between users.
|
||||||
|
*/
|
||||||
|
struct PlcMasterData {
|
||||||
|
QVector<PlcIO> ios; ///< All IO entries
|
||||||
|
QList<int> breakPositions; ///< Up to 4 break positions (IO index after which to break, 0 = disabled)
|
||||||
|
QMap<int, qreal> colWidths; ///< Column widths (key: column index)
|
||||||
|
qreal rowHeight = 8.0; ///< Row height in mm
|
||||||
|
QMap<int, bool> colVisible; ///< Column visibility (key: column index)
|
||||||
|
QFont headerFont; ///< Font for column headers
|
||||||
|
QFont cellFont; ///< Font for cell content
|
||||||
|
QStringList columnNames; ///< Custom column header names
|
||||||
|
QList<int> columnOrder; ///< Column display order (logical indices)
|
||||||
|
bool showHeaders = true; ///< Show column headers on sheet
|
||||||
|
|
||||||
|
bool operator==(const PlcMasterData &other) const {
|
||||||
|
return ios == other.ios
|
||||||
|
&& breakPositions == other.breakPositions
|
||||||
|
&& colWidths == other.colWidths
|
||||||
|
&& qFuzzyCompare(rowHeight, other.rowHeight)
|
||||||
|
&& colVisible == other.colVisible
|
||||||
|
&& headerFont == other.headerFont
|
||||||
|
&& cellFont == other.cellFont
|
||||||
|
&& columnNames == other.columnNames
|
||||||
|
&& columnOrder == other.columnOrder
|
||||||
|
&& showHeaders == other.showHeaders;
|
||||||
|
}
|
||||||
|
bool operator!=(const PlcMasterData &other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static QString plcIOTypeToString(PlcIOType type);
|
||||||
|
static PlcIOType plcIOTypeFromString(const QString &string);
|
||||||
|
static QString translatedPlcIOType(PlcIOType type);
|
||||||
|
static QStringList plcIOTypeList();
|
||||||
|
|
||||||
enum TerminalType {
|
enum TerminalType {
|
||||||
TTGeneric,
|
TTGeneric,
|
||||||
TTFuse,
|
TTFuse,
|
||||||
@@ -163,6 +242,9 @@ class ElementData : public PropertiesInterface
|
|||||||
static QString slaveContactGroupSubtypeToString(ElementData::SlaveType type);
|
static QString slaveContactGroupSubtypeToString(ElementData::SlaveType type);
|
||||||
static ElementData::SlaveType slaveContactGroupSubtypeFromString(const QString &string);
|
static ElementData::SlaveType slaveContactGroupSubtypeFromString(const QString &string);
|
||||||
|
|
||||||
|
PlcMasterData plcMasterData() const { return m_plc_master_data; }
|
||||||
|
void setPlcMasterData(const PlcMasterData &data) { m_plc_master_data = data; }
|
||||||
|
|
||||||
// must be public, because this class is a private member
|
// must be public, because this class is a private member
|
||||||
// of Element/ element editor and they must access this data
|
// of Element/ element editor and they must access this data
|
||||||
ElementData::Type m_type = ElementData::Simple;
|
ElementData::Type m_type = ElementData::Simple;
|
||||||
@@ -171,6 +253,7 @@ class ElementData : public PropertiesInterface
|
|||||||
int m_max_slaves{-1};
|
int m_max_slaves{-1};
|
||||||
bool m_slave_contact_groups_enabled{false}; ///< Whether slave contact groups table is active
|
bool m_slave_contact_groups_enabled{false}; ///< Whether slave contact groups table is active
|
||||||
QVector<SlaveContactGroup> m_slave_contact_groups; ///< Contact groups defined by master
|
QVector<SlaveContactGroup> m_slave_contact_groups; ///< Contact groups defined by master
|
||||||
|
PlcMasterData m_plc_master_data; ///< PLC master IO table and display settings
|
||||||
|
|
||||||
ElementData::SlaveType m_slave_type = ElementData::SSimple;
|
ElementData::SlaveType m_slave_type = ElementData::SSimple;
|
||||||
ElementData::SlaveState m_slave_state = ElementData::NO;
|
ElementData::SlaveState m_slave_state = ElementData::NO;
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ QHash<QString, XRefProperties> XRefProperties::defaultProperties()
|
|||||||
{
|
{
|
||||||
QHash <QString, XRefProperties> hash;
|
QHash <QString, XRefProperties> hash;
|
||||||
QStringList keys;
|
QStringList keys;
|
||||||
keys << "coil" << "protection" << "commutator";
|
keys << "coil" << "protection" << "commutator" << "plc";
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "elementtextitemgroup.h"
|
#include "elementtextitemgroup.h"
|
||||||
#include "qgraphicsitemutility.h"
|
#include "qgraphicsitemutility.h"
|
||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
|
#include "../properties/elementdata.h"
|
||||||
|
|
||||||
//define the height of the header.
|
//define the height of the header.
|
||||||
static int header = 5;
|
static int header = 5;
|
||||||
@@ -235,8 +236,11 @@ void CrossRefItem::updateLabel()
|
|||||||
qp.setPen(pen_);
|
qp.setPen(pen_);
|
||||||
qp.setFont(QETApp::diagramTextsFont(5));
|
qp.setFont(QETApp::diagramTextsFont(5));
|
||||||
|
|
||||||
//Draw cross or contact, only if master element is linked.
|
// PLC table is drawn by Element::drawPlcTable(), not by CrossRefItem
|
||||||
if (! m_element->linkedElements().isEmpty())
|
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;
|
m_update_map = true;
|
||||||
XRefProperties::DisplayHas dh = m_properties.displayHas();
|
XRefProperties::DisplayHas dh = m_properties.displayHas();
|
||||||
@@ -325,6 +329,11 @@ void CrossRefItem::paint(
|
|||||||
// caused a use-after-free crash (QRegion::begin, Qt5Gui+0x49af60)
|
// caused a use-after-free crash (QRegion::begin, Qt5Gui+0x49af60)
|
||||||
// confirmed by analysis of 19+ coredumps.
|
// confirmed by analysis of 19+ coredumps.
|
||||||
// m_update_map=false: draw functions do not overwrite m_hovered_contacts_map.
|
// 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;
|
if (m_element->linkedElements().isEmpty()) return;
|
||||||
|
|
||||||
QPen pen_;
|
QPen pen_;
|
||||||
@@ -1336,3 +1345,211 @@ QList<Element *> CrossRefItem::NCElements() const
|
|||||||
|
|
||||||
return nc_list;
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ class CrossRefItem : public QGraphicsObject
|
|||||||
void setUpCrossBoundingRect(QPainter &painter);
|
void setUpCrossBoundingRect(QPainter &painter);
|
||||||
void drawAsCross(QPainter &painter);
|
void drawAsCross(QPainter &painter);
|
||||||
void drawAsContacts(QPainter &painter);
|
void drawAsContacts(QPainter &painter);
|
||||||
|
void drawAsPlcTable(QPainter &painter);
|
||||||
QRectF drawContact(QPainter &painter, int flags, Element *elmt, int pole_index = 0);
|
QRectF drawContact(QPainter &painter, int flags, Element *elmt, int pole_index = 0);
|
||||||
void fillCrossRef(QPainter &painter);
|
void fillCrossRef(QPainter &painter);
|
||||||
void AddExtraInfo(QPainter &painter, const QString&);
|
void AddExtraInfo(QPainter &painter, const QString&);
|
||||||
|
|||||||
@@ -280,6 +280,10 @@ Element *DynamicElementTextItem::elementUseForInfo() const
|
|||||||
return elmt;
|
return elmt;
|
||||||
case Element::Slave:
|
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())
|
if(elmt->linkedElements().isEmpty())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
else
|
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::ElementInfo && m_info_name == "label") ||
|
||||||
(m_text_from == DynamicElementTextItem::CompositeText && m_composite_text.contains("%{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();
|
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)
|
if(!m_slave_Xref_item)
|
||||||
{
|
{
|
||||||
@@ -1367,9 +1373,9 @@ void DynamicElementTextItem::updateXref()
|
|||||||
m_slave_Xref_item->setDefaultTextColor(Qt::black);
|
m_slave_Xref_item->setDefaultTextColor(Qt::black);
|
||||||
m_slave_Xref_item->installSceneEventFilter(this);
|
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(master_elmt, &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(master_elmt, &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::elementInfoChange, this, &DynamicElementTextItem::updateXref);
|
||||||
m_update_slave_Xref_connection << connect(diagram(), &Diagram::diagramInformationChanged, 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::projectDiagramsOrderChanged, this, &DynamicElementTextItem::updateXref);
|
||||||
m_update_slave_Xref_connection << connect(diagram()->project(), &QETProject::diagramRemoved, 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)
|
else if (m_slave_Xref_item)
|
||||||
{
|
{
|
||||||
|
Element *master_elmt = m_parent_element.data()->linkedElements().isEmpty()
|
||||||
XRefProperties xrp = diagram()->project()->defaultXRefProperties(m_master_element.data()->kindInformations()["type"].toString());
|
? nullptr : m_parent_element.data()->linkedElements().first();
|
||||||
setXref_item(xrp.getXrefPos());
|
if (master_elmt) {
|
||||||
|
XRefProperties xrp = diagram()->project()->defaultXRefProperties(master_elmt->kindInformations()["type"].toString());
|
||||||
|
setXref_item(xrp.getXrefPos());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "element.h"
|
#include "element.h"
|
||||||
#include "../qetproject.h"
|
#include "../qetproject.h"
|
||||||
#include "../PropertiesEditor/propertieseditordialog.h"
|
#include "../PropertiesEditor/propertieseditordialog.h"
|
||||||
|
#include "../autoNum/assignvariables.h"
|
||||||
#include "../autoNum/numerotationcontextcommands.h"
|
#include "../autoNum/numerotationcontextcommands.h"
|
||||||
#include "../diagram.h"
|
#include "../diagram.h"
|
||||||
#include "../diagramcommands.h"
|
#include "../diagramcommands.h"
|
||||||
@@ -26,6 +27,8 @@
|
|||||||
#include "../elementprovider.h"
|
#include "../elementprovider.h"
|
||||||
#include "../factory/elementpicturefactory.h"
|
#include "../factory/elementpicturefactory.h"
|
||||||
#include "../properties/terminaldata.h"
|
#include "../properties/terminaldata.h"
|
||||||
|
#include "../properties/xrefproperties.h"
|
||||||
|
#include "../qetinformation.h"
|
||||||
#include "../qetgraphicsitem/conductor.h"
|
#include "../qetgraphicsitem/conductor.h"
|
||||||
#include "../qetgraphicsitem/terminal.h"
|
#include "../qetgraphicsitem/terminal.h"
|
||||||
#include "../ui/elementpropertieswidget.h"
|
#include "../ui/elementpropertieswidget.h"
|
||||||
@@ -229,13 +232,25 @@ void Element::paint(
|
|||||||
painter->setBrush(brush);
|
painter->setBrush(brush);
|
||||||
if (options && options->levelOfDetailFromTransform(painter->worldTransform()) < 0.5)
|
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 {
|
} 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
|
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
|
//Draw the selection rectangle
|
||||||
if ( isSelected() || m_mouse_over ) {
|
if ( isSelected() || m_mouse_over ) {
|
||||||
QGIUtility::drawBoundingRectSelection(this, painter);
|
QGIUtility::drawBoundingRectSelection(this, painter);
|
||||||
@@ -486,6 +501,13 @@ bool Element::buildFromXml(const QDomElement &xml_def_elmt, int *state)
|
|||||||
if (qde.isNull())
|
if (qde.isNull())
|
||||||
continue;
|
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)) {
|
if (parseElement(qde)) {
|
||||||
++ parsed_elements_count;
|
++ parsed_elements_count;
|
||||||
}
|
}
|
||||||
@@ -502,8 +524,8 @@ bool Element::buildFromXml(const QDomElement &xml_def_elmt, int *state)
|
|||||||
|
|
||||||
ElementPictureFactory *epf = ElementPictureFactory::instance();
|
ElementPictureFactory *epf = ElementPictureFactory::instance();
|
||||||
epf->getPictures(m_location,
|
epf->getPictures(m_location,
|
||||||
const_cast<QPicture&>(m_picture),
|
m_picture,
|
||||||
const_cast<QPicture&>(m_low_zoom_picture));
|
m_low_zoom_picture);
|
||||||
|
|
||||||
if(!m_picture.isNull())
|
if(!m_picture.isNull())
|
||||||
++ parsed_elements_count;
|
++ parsed_elements_count;
|
||||||
@@ -1394,6 +1416,8 @@ ElementData Element::elementData() const
|
|||||||
void Element::setElementData(ElementData data)
|
void Element::setElementData(ElementData data)
|
||||||
{
|
{
|
||||||
auto old_info = m_data.m_informations;
|
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;
|
m_data = data;
|
||||||
|
|
||||||
if (old_info != m_data.m_informations) {
|
if (old_info != m_data.m_informations) {
|
||||||
@@ -1403,6 +1427,42 @@ void Element::setElementData(ElementData data)
|
|||||||
}
|
}
|
||||||
emit elementInfoChange(old_info, m_data.m_informations);
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -256,11 +256,14 @@ class Element : public QetGraphicsItem
|
|||||||
bool m_freeze_label = false;
|
bool m_freeze_label = false;
|
||||||
QString m_F_str;
|
QString m_F_str;
|
||||||
|
|
||||||
ElementsLocation m_location;
|
ElementsLocation m_location;
|
||||||
QList <Terminal *> m_terminals;
|
QList <Terminal *> m_terminals;
|
||||||
const QPicture m_picture;
|
QPicture m_picture;
|
||||||
const QPicture m_low_zoom_picture;
|
QPicture m_low_zoom_picture;
|
||||||
ElementData m_data;
|
ElementData m_data;
|
||||||
|
QList<QPointF> m_plc_table_positions; // Positions of plc_table parts in the element definition
|
||||||
|
|
||||||
|
void drawPlcTable(QPainter *painter);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_must_highlight = false;
|
bool m_must_highlight = false;
|
||||||
|
|||||||
@@ -18,8 +18,10 @@
|
|||||||
#include "masterelement.h"
|
#include "masterelement.h"
|
||||||
#include "../qetproject.h"
|
#include "../qetproject.h"
|
||||||
#include "../diagram.h"
|
#include "../diagram.h"
|
||||||
|
#include "../qetinformation.h"
|
||||||
#include "crossrefitem.h"
|
#include "crossrefitem.h"
|
||||||
#include "dynamicelementtextitem.h"
|
#include "dynamicelementtextitem.h"
|
||||||
|
#include "../properties/elementdata.h"
|
||||||
|
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
|
||||||
@@ -61,9 +63,14 @@ void MasterElement::linkToElement(Element *elmt)
|
|||||||
elmt->linkToElement(this);
|
elmt->linkToElement(this);
|
||||||
|
|
||||||
XRefProperties xrp = diagram()->project()->defaultXRefProperties(kindInformations()["type"].toString());
|
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
|
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();
|
emit linkedElementChanged();
|
||||||
aboutDeleteXref();
|
aboutDeleteXref();
|
||||||
}
|
}
|
||||||
@@ -94,10 +101,28 @@ void MasterElement::unlinkElement(Element *elmt)
|
|||||||
//Ensure elmt is linked to this element
|
//Ensure elmt is linked to this element
|
||||||
if (connected_elements.contains(elmt))
|
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);
|
connected_elements.removeOne(elmt);
|
||||||
elmt -> unlinkElement (this);
|
elmt -> unlinkElement (this);
|
||||||
elmt -> setHighlighted (false);
|
elmt -> setHighlighted (false);
|
||||||
|
|
||||||
|
// Disconnect slave position updates for PLC masters
|
||||||
|
disconnectSlavePositionUpdates(elmt);
|
||||||
|
|
||||||
aboutDeleteXref();
|
aboutDeleteXref();
|
||||||
emit linkedElementChanged();
|
emit linkedElementChanged();
|
||||||
}
|
}
|
||||||
@@ -141,7 +166,7 @@ void MasterElement::xrefPropertiesChanged()
|
|||||||
{
|
{
|
||||||
if(!diagram())
|
if(!diagram())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
XRefProperties xrp = diagram()->project()->defaultXRefProperties(kindInformations()["type"].toString());
|
XRefProperties xrp = diagram()->project()->defaultXRefProperties(kindInformations()["type"].toString());
|
||||||
if(xrp.snapTo() == XRefProperties::Bottom)
|
if(xrp.snapTo() == XRefProperties::Bottom)
|
||||||
{
|
{
|
||||||
@@ -150,6 +175,12 @@ void MasterElement::xrefPropertiesChanged()
|
|||||||
if(!m_Xref_item)
|
if(!m_Xref_item)
|
||||||
m_Xref_item = new CrossRefItem(this);
|
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();
|
aboutDeleteXref();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +198,11 @@ void MasterElement::aboutDeleteXref()
|
|||||||
{
|
{
|
||||||
if(!m_Xref_item)
|
if(!m_Xref_item)
|
||||||
return;
|
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());
|
XRefProperties xrp = diagram()->project()->defaultXRefProperties(kindInformations()["type"].toString());
|
||||||
if (xrp.snapTo() != XRefProperties::Bottom && m_Xref_item)
|
if (xrp.snapTo() != XRefProperties::Bottom && m_Xref_item)
|
||||||
{
|
{
|
||||||
@@ -175,7 +210,7 @@ void MasterElement::aboutDeleteXref()
|
|||||||
m_Xref_item = nullptr;
|
m_Xref_item = nullptr;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_Xref_item->boundingRect().isNull())
|
if (m_Xref_item->boundingRect().isNull())
|
||||||
{
|
{
|
||||||
delete m_Xref_item;
|
delete m_Xref_item;
|
||||||
@@ -207,3 +242,36 @@ bool MasterElement::isFull() const
|
|||||||
// Return true if current connected elements reached or exceeded the limit
|
// Return true if current connected elements reached or exceeded the limit
|
||||||
return connected_elements.size() >= max_slaves;
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
#define MASTERELEMENT_H
|
#define MASTERELEMENT_H
|
||||||
|
|
||||||
#include "element.h"
|
#include "element.h"
|
||||||
|
#include <QHash>
|
||||||
|
#include <QMetaObject>
|
||||||
|
|
||||||
class CrossRefItem;
|
class CrossRefItem;
|
||||||
|
|
||||||
@@ -55,10 +57,14 @@ class MasterElement : public Element
|
|||||||
private:
|
private:
|
||||||
void xrefPropertiesChanged();
|
void xrefPropertiesChanged();
|
||||||
void aboutDeleteXref ();
|
void aboutDeleteXref ();
|
||||||
|
void connectSlavePositionUpdates(Element *slave);
|
||||||
|
void disconnectSlavePositionUpdates(Element *slave);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CrossRefItem *m_Xref_item = nullptr;
|
CrossRefItem *m_Xref_item = nullptr;
|
||||||
bool m_first_scene_change = true;
|
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
|
#endif // MASTERELEMENT_H
|
||||||
|
|||||||
@@ -190,6 +190,11 @@ QStringList QETInformation::elementInfoKeys()
|
|||||||
ELMT_SUPPLIER_AUX4,
|
ELMT_SUPPLIER_AUX4,
|
||||||
ELMT_QUANTITY_AUX4,
|
ELMT_QUANTITY_AUX4,
|
||||||
ELMT_UNITY_AUX4,
|
ELMT_UNITY_AUX4,
|
||||||
|
ELMT_PLC_TYPE,
|
||||||
|
ELMT_PLC_ADDRESS,
|
||||||
|
ELMT_PLC_FUNCTION,
|
||||||
|
ELMT_PLC_COMMENT,
|
||||||
|
ELMT_PLC_CROSSREF,
|
||||||
"exclude_from_bom" };
|
"exclude_from_bom" };
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
@@ -306,6 +311,11 @@ QString QETInformation::translatedInfoKey(const QString &info)
|
|||||||
else if (info == ELMT_SUPPLIER_AUX4) return QObject::tr("Fournisseur auxiliaire 4");
|
else if (info == ELMT_SUPPLIER_AUX4) return QObject::tr("Fournisseur auxiliaire 4");
|
||||||
else if (info == ELMT_QUANTITY_AUX4) return QObject::tr("Quantité auxiliaire 4");
|
else if (info == ELMT_QUANTITY_AUX4) return QObject::tr("Quantité auxiliaire 4");
|
||||||
else if (info == ELMT_UNITY_AUX4) return QObject::tr("Unité auxiliaire 4");
|
else if (info == ELMT_UNITY_AUX4) return QObject::tr("Unité auxiliaire 4");
|
||||||
|
else if (info == ELMT_PLC_TYPE) return QObject::tr("Type PLC");
|
||||||
|
else if (info == ELMT_PLC_ADDRESS) return QObject::tr("Adresse PLC");
|
||||||
|
else if (info == ELMT_PLC_FUNCTION) return QObject::tr("Fonction PLC");
|
||||||
|
else if (info == ELMT_PLC_COMMENT) return QObject::tr("Commentaire PLC");
|
||||||
|
else if (info == ELMT_PLC_CROSSREF) return QObject::tr("Réf. croisée PLC");
|
||||||
else return QString();
|
else return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -124,6 +124,13 @@ namespace QETInformation
|
|||||||
/** Default information related to QElectroTech **/
|
/** Default information related to QElectroTech **/
|
||||||
static QString QET_VERSION = "version";
|
static QString QET_VERSION = "version";
|
||||||
|
|
||||||
|
/** PLC-specific information for slave elements **/
|
||||||
|
static QString ELMT_PLC_TYPE = "plc_type";
|
||||||
|
static QString ELMT_PLC_ADDRESS = "plc_address";
|
||||||
|
static QString ELMT_PLC_FUNCTION = "plc_function";
|
||||||
|
static QString ELMT_PLC_COMMENT = "plc_comment";
|
||||||
|
static QString ELMT_PLC_CROSSREF = "plc_crossref";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "elementinfowidget.h"
|
#include "elementinfowidget.h"
|
||||||
#include "linksingleelementwidget.h"
|
#include "linksingleelementwidget.h"
|
||||||
#include "masterpropertieswidget.h"
|
#include "masterpropertieswidget.h"
|
||||||
|
#include "plclinkwidget.h"
|
||||||
|
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QUndoStack>
|
#include <QUndoStack>
|
||||||
@@ -303,7 +304,10 @@ void ElementPropertiesWidget::updateUi()
|
|||||||
m_list_editor << new ElementInfoWidget(m_element, this);
|
m_list_editor << new ElementInfoWidget(m_element, this);
|
||||||
break;
|
break;
|
||||||
case Element::Slave:
|
case Element::Slave:
|
||||||
m_list_editor << new LinkSingleElementWidget(m_element, this);
|
if (m_element->elementData().m_slave_type == ElementData::PLCSlave)
|
||||||
|
m_list_editor << new PlcLinkWidget(m_element, this);
|
||||||
|
else
|
||||||
|
m_list_editor << new LinkSingleElementWidget(m_element, this);
|
||||||
break;
|
break;
|
||||||
case Element::Terminale:
|
case Element::Terminale:
|
||||||
m_list_editor << new ElementInfoWidget(m_element, this);
|
m_list_editor << new ElementInfoWidget(m_element, this);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "../ui_linksingleelementwidget.h"
|
#include "../ui_linksingleelementwidget.h"
|
||||||
|
|
||||||
#include <QTreeWidgetItem>
|
#include <QTreeWidgetItem>
|
||||||
|
#include <QInputDialog>
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -393,17 +394,24 @@ QVector <QPointer<Element>> LinkSingleElementWidget::availableElements()
|
|||||||
|
|
||||||
//If element is linked, remove is parent from the list
|
//If element is linked, remove is parent from the list
|
||||||
if(!m_element->isFree()) elmt_vector.removeAll(m_element->linkedElements().first());
|
if(!m_element->isFree()) elmt_vector.removeAll(m_element->linkedElements().first());
|
||||||
// Filter out all master elements from the list
|
|
||||||
|
// Filter out incompatible elements: PLC and non-PLC must not mix
|
||||||
|
const bool element_is_plc = (m_element->elementData().m_type == ElementData::Slave &&
|
||||||
|
m_element->elementData().m_slave_type == ElementData::PLCSlave);
|
||||||
for (int i = elmt_vector.size() - 1; i >= 0; --i) {
|
for (int i = elmt_vector.size() - 1; i >= 0; --i) {
|
||||||
Element *elmt = elmt_vector.at(i);
|
Element *elmt = elmt_vector.at(i);
|
||||||
|
|
||||||
// If the item in the list is a master
|
|
||||||
if (elmt->linkType() == Element::Master) {
|
if (elmt->linkType() == Element::Master) {
|
||||||
|
const bool master_is_plc = (elmt->elementData().m_master_type == ElementData::PLC);
|
||||||
|
|
||||||
// We convert the generic element pointer into a MasterElement pointer
|
// PLC slave can only link to PLC master, and vice versa
|
||||||
MasterElement *master = static_cast<MasterElement*>(elmt);
|
if (element_is_plc != master_is_plc) {
|
||||||
|
elmt_vector.removeAt(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// If the master is full, we'll remove it from the list!
|
// If the master is full, we'll remove it from the list!
|
||||||
|
MasterElement *master = static_cast<MasterElement*>(elmt);
|
||||||
if (master->isFull()) {
|
if (master->isFull()) {
|
||||||
elmt_vector.removeAt(i);
|
elmt_vector.removeAt(i);
|
||||||
}
|
}
|
||||||
@@ -536,7 +544,7 @@ void LinkSingleElementWidget::linkTriggered()
|
|||||||
{
|
{
|
||||||
if(!m_qtwi_at_context_menu)
|
if(!m_qtwi_at_context_menu)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_element_to_link = m_qtwi_elmt_hash.value(m_qtwi_at_context_menu);
|
m_element_to_link = m_qtwi_elmt_hash.value(m_qtwi_at_context_menu);
|
||||||
m_pending_group_index = -1;
|
m_pending_group_index = -1;
|
||||||
|
|
||||||
@@ -545,25 +553,73 @@ void LinkSingleElementWidget::linkTriggered()
|
|||||||
&& m_element_to_link
|
&& m_element_to_link
|
||||||
&& m_element_to_link->linkType() == Element::Master)
|
&& m_element_to_link->linkType() == Element::Master)
|
||||||
{
|
{
|
||||||
const auto &groups = m_element_to_link->elementData().m_slave_contact_groups;
|
// Check if this is a PLC master
|
||||||
if (!groups.isEmpty())
|
if (m_element_to_link->elementData().m_master_type == ElementData::PLC)
|
||||||
{
|
{
|
||||||
// Collect already-used group indices from the master
|
// Show PLC IO selection dialog
|
||||||
QSet<int> used_indices;
|
const auto &plc_data = m_element_to_link->elementData().plcMasterData();
|
||||||
for (Element *linked : m_element_to_link->linkedElements()) {
|
if (!plc_data.ios.isEmpty())
|
||||||
int idx = m_element_to_link->groupIndexForElement(linked);
|
{
|
||||||
if (idx >= 0) {
|
// Collect already-used IO indices from the master
|
||||||
used_indices.insert(idx);
|
QSet<int> used_indices;
|
||||||
|
for (Element *linked : m_element_to_link->linkedElements()) {
|
||||||
|
int idx = m_element_to_link->groupIndexForElement(linked);
|
||||||
|
if (idx >= 0) {
|
||||||
|
used_indices.insert(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build selection dialog
|
||||||
|
QStringList items;
|
||||||
|
for (int i = 0; i < plc_data.ios.size(); ++i) {
|
||||||
|
const auto &io = plc_data.ios.at(i);
|
||||||
|
QString label = QString("[%1] %2 - %3")
|
||||||
|
.arg(i + 1)
|
||||||
|
.arg(io.address)
|
||||||
|
.arg(io.functionText);
|
||||||
|
if (used_indices.contains(i))
|
||||||
|
label += tr(" (déjà utilisé)");
|
||||||
|
items << label;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
int selected = QInputDialog::getInt(
|
||||||
|
this,
|
||||||
|
tr("Sélectionner un IO PLC"),
|
||||||
|
tr("IO disponible:"),
|
||||||
|
0, 0, plc_data.ios.size() - 1, 1, &ok);
|
||||||
|
|
||||||
|
if (ok && selected >= 0) {
|
||||||
|
m_pending_group_index = selected;
|
||||||
|
} else {
|
||||||
|
m_element_to_link = nullptr;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Normal contact group selection for non-PLC masters
|
||||||
|
const auto &groups = m_element_to_link->elementData().m_slave_contact_groups;
|
||||||
|
if (!groups.isEmpty())
|
||||||
|
{
|
||||||
|
// Collect already-used group indices from the master
|
||||||
|
QSet<int> used_indices;
|
||||||
|
for (Element *linked : m_element_to_link->linkedElements()) {
|
||||||
|
int idx = m_element_to_link->groupIndexForElement(linked);
|
||||||
|
if (idx >= 0) {
|
||||||
|
used_indices.insert(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ContactGroupSelectionDialog dlg(groups, used_indices,
|
ContactGroupSelectionDialog dlg(groups, used_indices,
|
||||||
m_element->elementData(), this);
|
m_element->elementData(), this);
|
||||||
if (dlg.exec() == QDialog::Accepted && dlg.selectedIndex() >= 0) {
|
if (dlg.exec() == QDialog::Accepted && dlg.selectedIndex() >= 0) {
|
||||||
m_pending_group_index = dlg.selectedIndex();
|
m_pending_group_index = dlg.selectedIndex();
|
||||||
} else {
|
} else {
|
||||||
m_element_to_link = nullptr;
|
m_element_to_link = nullptr;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -588,7 +644,7 @@ void LinkSingleElementWidget::linkTriggered()
|
|||||||
Qt::NoBrush));
|
Qt::NoBrush));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i=0 ; i<6 ; i++)
|
for (int i=0 ; i<6 ; i++)
|
||||||
{
|
{
|
||||||
m_qtwi_at_context_menu->setBackground(i,
|
m_qtwi_at_context_menu->setBackground(i,
|
||||||
@@ -597,7 +653,7 @@ void LinkSingleElementWidget::linkTriggered()
|
|||||||
}
|
}
|
||||||
m_pending_qtwi = m_qtwi_at_context_menu;
|
m_pending_qtwi = m_qtwi_at_context_menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,9 +24,21 @@
|
|||||||
#include "../qetgraphicsitem/element.h"
|
#include "../qetgraphicsitem/element.h"
|
||||||
#include "../undocommand/linkelementcommand.h"
|
#include "../undocommand/linkelementcommand.h"
|
||||||
#include "ui_masterpropertieswidget.h"
|
#include "ui_masterpropertieswidget.h"
|
||||||
|
#include "../properties/elementdata.h"
|
||||||
|
|
||||||
#include <QListWidgetItem>
|
#include <QListWidgetItem>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QTableWidget>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QShortcut>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -260,98 +272,7 @@ bool MasterPropertiesWidget::setLiveEdit(bool live_edit)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief MasterPropertiesWidget::updateUi
|
|
||||||
* Build the interface of the widget
|
|
||||||
*/
|
|
||||||
void MasterPropertiesWidget::updateUi()
|
|
||||||
{
|
|
||||||
ui->m_free_tree_widget->clear();
|
|
||||||
ui->m_link_tree_widget->clear();
|
|
||||||
m_qtwi_hash.clear();
|
|
||||||
|
|
||||||
if (Q_UNLIKELY(!m_project))
|
|
||||||
return;
|
|
||||||
|
|
||||||
ElementProvider elmt_prov(m_project);
|
|
||||||
QSettings settings;
|
|
||||||
|
|
||||||
//Build the list of free available element
|
|
||||||
QList <QTreeWidgetItem *> items_list;
|
|
||||||
for(const auto &elmt : elmt_prov.freeElement(ElementData::Slave))
|
|
||||||
{
|
|
||||||
QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_free_tree_widget);
|
|
||||||
qtwi->setIcon(0, elmt->pixmap());
|
|
||||||
|
|
||||||
if(settings.value("genericpanel/folio", false).toBool())
|
|
||||||
{
|
|
||||||
autonum::sequentialNumbers seq;
|
|
||||||
QString F =autonum::AssignVariables::formulaToLabel(
|
|
||||||
elmt->diagram()->border_and_titleblock.folio(),
|
|
||||||
seq,
|
|
||||||
elmt->diagram(),
|
|
||||||
elmt);
|
|
||||||
qtwi->setText(1, F);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qtwi->setText(1, QString::number(
|
|
||||||
elmt->diagram()->folioIndex()
|
|
||||||
+ 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
qtwi->setText(2, elmt->diagram()->title());
|
|
||||||
qtwi->setText(4, elmt->diagram()->convertPosition(
|
|
||||||
elmt->scenePos()).toString());
|
|
||||||
items_list.append(qtwi);
|
|
||||||
m_qtwi_hash.insert(qtwi, elmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui->m_free_tree_widget->addTopLevelItems(items_list);
|
|
||||||
items_list.clear();
|
|
||||||
|
|
||||||
//Build the list of already linked element
|
|
||||||
const QList<Element *> link_list = m_element->linkedElements();
|
|
||||||
for(Element *elmt : link_list)
|
|
||||||
{
|
|
||||||
QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_link_tree_widget);
|
|
||||||
qtwi->setIcon(0, elmt->pixmap());
|
|
||||||
|
|
||||||
if(settings.value("genericpanel/folio", false).toBool())
|
|
||||||
{
|
|
||||||
autonum::sequentialNumbers seq;
|
|
||||||
QString F =autonum::AssignVariables::formulaToLabel(
|
|
||||||
elmt->diagram()->border_and_titleblock.folio(),
|
|
||||||
seq,
|
|
||||||
elmt->diagram(),
|
|
||||||
elmt);
|
|
||||||
qtwi->setText(1, F);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qtwi->setText(1, QString::number(
|
|
||||||
elmt->diagram()->folioIndex()
|
|
||||||
+ 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
qtwi->setText(2, elmt->diagram()->title());
|
|
||||||
qtwi->setText(3, elmt->diagram()->convertPosition(
|
|
||||||
elmt->scenePos()).toString());
|
|
||||||
items_list.append(qtwi);
|
|
||||||
m_qtwi_hash.insert(qtwi, elmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(items_list.count())
|
|
||||||
ui->m_link_tree_widget->addTopLevelItems(items_list);
|
|
||||||
|
|
||||||
QVariant v = settings.value("link-element-widget/master-state");
|
|
||||||
if(!v.isNull())
|
|
||||||
{
|
|
||||||
ui->m_free_tree_widget->header()->restoreState(v.toByteArray());
|
|
||||||
ui->m_link_tree_widget->header()->restoreState(v.toByteArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief MasterPropertiesWidget::headerCustomContextMenuRequested
|
* @brief MasterPropertiesWidget::headerCustomContextMenuRequested
|
||||||
@@ -548,3 +469,564 @@ void MasterPropertiesWidget::customContextMenu(const QPoint &pos, int i)
|
|||||||
m_context_menu->addAction(m_show_element);
|
m_context_menu->addAction(m_show_element);
|
||||||
m_context_menu->popup(point);
|
m_context_menu->popup(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MasterPropertiesWidget::updateUi
|
||||||
|
* Build the interface of the widget
|
||||||
|
*/
|
||||||
|
void MasterPropertiesWidget::updateUi()
|
||||||
|
{
|
||||||
|
ui->m_free_tree_widget->clear();
|
||||||
|
ui->m_link_tree_widget->clear();
|
||||||
|
m_qtwi_hash.clear();
|
||||||
|
|
||||||
|
// Check if this is a PLC master
|
||||||
|
bool is_plc = m_element &&
|
||||||
|
m_element->elementData().m_type == ElementData::Master &&
|
||||||
|
m_element->elementData().m_master_type == ElementData::PLC;
|
||||||
|
|
||||||
|
// Show/hide normal master UI and PLC UI
|
||||||
|
ui->m_free_tree_widget->setVisible(!is_plc);
|
||||||
|
ui->m_link_tree_widget->setVisible(!is_plc);
|
||||||
|
ui->label->setVisible(!is_plc);
|
||||||
|
ui->label_2->setVisible(!is_plc);
|
||||||
|
ui->link_button->setVisible(!is_plc);
|
||||||
|
ui->unlink_button->setVisible(!is_plc);
|
||||||
|
|
||||||
|
// In PLC mode, make hidden widgets take no space in the grid layout
|
||||||
|
if (is_plc) {
|
||||||
|
for (QWidget *w : {static_cast<QWidget*>(ui->m_free_tree_widget),
|
||||||
|
static_cast<QWidget*>(ui->m_link_tree_widget),
|
||||||
|
static_cast<QWidget*>(ui->label),
|
||||||
|
static_cast<QWidget*>(ui->label_2),
|
||||||
|
static_cast<QWidget*>(ui->link_button),
|
||||||
|
static_cast<QWidget*>(ui->unlink_button)}) {
|
||||||
|
w->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
|
||||||
|
w->setMinimumSize(0, 0);
|
||||||
|
w->setMaximumSize(0, 0);
|
||||||
|
}
|
||||||
|
for (int col = 0; col < ui->gridLayout->columnCount(); ++col)
|
||||||
|
ui->gridLayout->setColumnStretch(col, col < 2 ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_plc) {
|
||||||
|
// Create PLC widget if not yet created
|
||||||
|
if (!m_plc_widget) {
|
||||||
|
m_plc_widget = new QWidget(ui->gridLayout->parentWidget());
|
||||||
|
|
||||||
|
auto *plc_layout = new QVBoxLayout(m_plc_widget);
|
||||||
|
plc_layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
|
// Table
|
||||||
|
m_plc_table = new QTableWidget(m_plc_widget);
|
||||||
|
m_plc_table->setColumnCount(5);
|
||||||
|
m_plc_table->setHorizontalHeaderLabels({
|
||||||
|
tr("Type"), tr("Adresse"), tr("Fonction"),
|
||||||
|
tr("Commentaire"), tr("Réf. croisée")
|
||||||
|
});
|
||||||
|
m_plc_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||||
|
m_plc_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
m_plc_table->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
m_plc_table->setMinimumHeight(200);
|
||||||
|
|
||||||
|
plc_layout->addWidget(m_plc_table);
|
||||||
|
|
||||||
|
connect(m_plc_table, &QTableWidget::cellChanged, this, &MasterPropertiesWidget::plcIOCellChanged);
|
||||||
|
|
||||||
|
// Context menu for the PLC table
|
||||||
|
m_plc_table->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
connect(m_plc_table, &QTableWidget::customContextMenuRequested,
|
||||||
|
this, &MasterPropertiesWidget::plcShowTableContextMenu);
|
||||||
|
|
||||||
|
// Ctrl+V shortcut for paste
|
||||||
|
auto *paste_shortcut = new QShortcut(QKeySequence::Paste, m_plc_table);
|
||||||
|
connect(paste_shortcut, &QShortcut::activated, this, &MasterPropertiesWidget::plcPasteFromClipboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->gridLayout->addWidget(m_plc_widget, 0, 0, 1, 5);
|
||||||
|
m_plc_widget->setVisible(true);
|
||||||
|
|
||||||
|
// Load PLC data into table (block signals to prevent cellChanged during population)
|
||||||
|
m_plc_updating = true;
|
||||||
|
m_plc_table->blockSignals(true);
|
||||||
|
|
||||||
|
// Clear existing rows
|
||||||
|
m_plc_table->setRowCount(0);
|
||||||
|
|
||||||
|
ElementData::PlcMasterData plc_data = m_element->elementData().plcMasterData();
|
||||||
|
|
||||||
|
m_plc_table->setRowCount(plc_data.ios.size());
|
||||||
|
for (int row = 0; row < plc_data.ios.size(); ++row) {
|
||||||
|
const ElementData::PlcIO &io = plc_data.ios.at(row);
|
||||||
|
|
||||||
|
// Type combo (read-only — type is defined in the editor)
|
||||||
|
auto *type_cb = new QComboBox(m_plc_table);
|
||||||
|
type_cb->setEnabled(false);
|
||||||
|
type_cb->setStyleSheet("QComboBox { background-color: #f0f0f0; }");
|
||||||
|
QStringList plc_types = ElementData::plcIOTypeList();
|
||||||
|
for (int t = 0; t < plc_types.size(); ++t) {
|
||||||
|
type_cb->addItem(plc_types.at(t), t);
|
||||||
|
}
|
||||||
|
type_cb->setCurrentIndex(static_cast<int>(io.type));
|
||||||
|
m_plc_table->setCellWidget(row, 0, type_cb);
|
||||||
|
|
||||||
|
// Address
|
||||||
|
auto *addr_item = new QTableWidgetItem(io.address);
|
||||||
|
m_plc_table->setItem(row, 1, addr_item);
|
||||||
|
|
||||||
|
// Function text
|
||||||
|
auto *func_item = new QTableWidgetItem(io.functionText);
|
||||||
|
m_plc_table->setItem(row, 2, func_item);
|
||||||
|
|
||||||
|
// Comment
|
||||||
|
auto *comment_item = new QTableWidgetItem(io.comment);
|
||||||
|
m_plc_table->setItem(row, 3, comment_item);
|
||||||
|
|
||||||
|
// CrossRef (read-only)
|
||||||
|
auto *crossref_item = new QTableWidgetItem(io.crossRef);
|
||||||
|
crossref_item->setFlags(crossref_item->flags() & ~Qt::ItemIsEditable);
|
||||||
|
m_plc_table->setItem(row, 4, crossref_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_plc_table->blockSignals(false);
|
||||||
|
m_plc_updating = false;
|
||||||
|
} else {
|
||||||
|
// Hide PLC widget if it was shown before
|
||||||
|
if (m_plc_widget)
|
||||||
|
m_plc_widget->setVisible(false);
|
||||||
|
|
||||||
|
// Restore normal widget size policies
|
||||||
|
for (QWidget *w : {static_cast<QWidget*>(ui->m_free_tree_widget),
|
||||||
|
static_cast<QWidget*>(ui->m_link_tree_widget),
|
||||||
|
static_cast<QWidget*>(ui->label),
|
||||||
|
static_cast<QWidget*>(ui->label_2),
|
||||||
|
static_cast<QWidget*>(ui->link_button),
|
||||||
|
static_cast<QWidget*>(ui->unlink_button)}) {
|
||||||
|
w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
w->setMinimumSize(0, 0);
|
||||||
|
w->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
||||||
|
}
|
||||||
|
for (int col = 0; col < ui->gridLayout->columnCount(); ++col)
|
||||||
|
ui->gridLayout->setColumnStretch(col, 0);
|
||||||
|
|
||||||
|
if (Q_UNLIKELY(!m_project))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ElementProvider elmt_prov(m_project);
|
||||||
|
QSettings settings;
|
||||||
|
|
||||||
|
//Build the list of free available element
|
||||||
|
QList <QTreeWidgetItem *> items_list;
|
||||||
|
for(const auto &elmt : elmt_prov.freeElement(ElementData::Slave))
|
||||||
|
{
|
||||||
|
// Filter out PLC-Slave elements — they should only link to PLC-Master
|
||||||
|
if (elmt->elementData().m_slave_type == ElementData::PLCSlave)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_free_tree_widget);
|
||||||
|
qtwi->setIcon(0, elmt->pixmap());
|
||||||
|
|
||||||
|
if(settings.value("genericpanel/folio", false).toBool())
|
||||||
|
{
|
||||||
|
autonum::sequentialNumbers seq;
|
||||||
|
QString F =autonum::AssignVariables::formulaToLabel(
|
||||||
|
elmt->diagram()->border_and_titleblock.folio(),
|
||||||
|
seq,
|
||||||
|
elmt->diagram(),
|
||||||
|
elmt);
|
||||||
|
qtwi->setText(1, F);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qtwi->setText(1, QString::number(
|
||||||
|
elmt->diagram()->folioIndex()
|
||||||
|
+ 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
qtwi->setText(2, elmt->diagram()->title());
|
||||||
|
qtwi->setText(4, elmt->diagram()->convertPosition(
|
||||||
|
elmt->scenePos()).toString());
|
||||||
|
items_list.append(qtwi);
|
||||||
|
m_qtwi_hash.insert(qtwi, elmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->m_free_tree_widget->addTopLevelItems(items_list);
|
||||||
|
items_list.clear();
|
||||||
|
|
||||||
|
//Build the list of already linked element
|
||||||
|
const QList<Element *> link_list = m_element->linkedElements();
|
||||||
|
for(Element *elmt : link_list)
|
||||||
|
{
|
||||||
|
QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_link_tree_widget);
|
||||||
|
qtwi->setIcon(0, elmt->pixmap());
|
||||||
|
|
||||||
|
if(settings.value("genericpanel/folio", false).toBool())
|
||||||
|
{
|
||||||
|
autonum::sequentialNumbers seq;
|
||||||
|
QString F =autonum::AssignVariables::formulaToLabel(
|
||||||
|
elmt->diagram()->border_and_titleblock.folio(),
|
||||||
|
seq,
|
||||||
|
elmt->diagram(),
|
||||||
|
elmt);
|
||||||
|
qtwi->setText(1, F);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qtwi->setText(1, QString::number(
|
||||||
|
elmt->diagram()->folioIndex()
|
||||||
|
+ 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
qtwi->setText(2, elmt->diagram()->title());
|
||||||
|
qtwi->setText(3, elmt->diagram()->convertPosition(
|
||||||
|
elmt->scenePos()).toString());
|
||||||
|
items_list.append(qtwi);
|
||||||
|
m_qtwi_hash.insert(qtwi, elmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(items_list.count())
|
||||||
|
ui->m_link_tree_widget->addTopLevelItems(items_list);
|
||||||
|
|
||||||
|
QVariant v = settings.value("link-element-widget/master-state");
|
||||||
|
if(!v.isNull())
|
||||||
|
{
|
||||||
|
ui->m_free_tree_widget->header()->restoreState(v.toByteArray());
|
||||||
|
ui->m_link_tree_widget->header()->restoreState(v.toByteArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PLC IO Table methods
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MasterPropertiesWidget::plcPasteFromClipboard
|
||||||
|
* Paste IO data from clipboard.
|
||||||
|
*
|
||||||
|
* Two modes:
|
||||||
|
* 1. Single-column data (no tabs, e.g. copied vertically from A1:A6):
|
||||||
|
* Values are pasted vertically down the current column.
|
||||||
|
* 2. Multi-column data (tab-separated, e.g. copied from a row in Excel):
|
||||||
|
* Each line becomes a separate IO row.
|
||||||
|
*/
|
||||||
|
void MasterPropertiesWidget::plcPasteFromClipboard()
|
||||||
|
{
|
||||||
|
if (!m_plc_table || !m_element)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString clipboard_text = QApplication::clipboard()->text();
|
||||||
|
if (clipboard_text.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QStringList lines = clipboard_text.split('\n', Qt::SkipEmptyParts);
|
||||||
|
if (lines.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool has_tabs = false;
|
||||||
|
for (const QString &line : lines) {
|
||||||
|
if (line.contains('\t')) {
|
||||||
|
has_tabs = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_plc_updating = true;
|
||||||
|
|
||||||
|
if (!has_tabs) {
|
||||||
|
// Vertical paste: values go down the same column, within existing rows only
|
||||||
|
int target_col = m_plc_table->currentColumn();
|
||||||
|
if (target_col < 0) target_col = 0;
|
||||||
|
int target_row = m_plc_table->currentRow();
|
||||||
|
if (target_row < 0) target_row = 0;
|
||||||
|
int max_rows = m_plc_table->rowCount();
|
||||||
|
|
||||||
|
for (int i = 0; i < lines.size(); ++i) {
|
||||||
|
int row = target_row + i;
|
||||||
|
if (row >= max_rows) break;
|
||||||
|
setCellFromValue(row, target_col, lines.at(i).trimmed());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Horizontal paste: each line is a separate IO row, within existing rows only
|
||||||
|
int target_row = m_plc_table->currentRow();
|
||||||
|
if (target_row < 0) target_row = 0;
|
||||||
|
int max_rows = m_plc_table->rowCount();
|
||||||
|
|
||||||
|
for (int i = 0; i < lines.size(); ++i) {
|
||||||
|
int row = target_row + i;
|
||||||
|
if (row >= max_rows) break;
|
||||||
|
QStringList cells = lines.at(i).split('\t');
|
||||||
|
for (int c = 0; c < cells.size(); ++c) {
|
||||||
|
if (c > 6) break;
|
||||||
|
setCellFromValue(row, c, cells.at(c).trimmed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_plc_updating = false;
|
||||||
|
plcUpdateDisplaySettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MasterPropertiesWidget::setCellFromValue
|
||||||
|
* Set a single table cell value, respecting the column widget type.
|
||||||
|
*/
|
||||||
|
void MasterPropertiesWidget::setCellFromValue(int row, int col, const QString &val)
|
||||||
|
{
|
||||||
|
if (!m_plc_table || row < 0 || col < 0 || col > 6)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (col == 0) {
|
||||||
|
// Type combo
|
||||||
|
auto *type_cb = qobject_cast<QComboBox*>(m_plc_table->cellWidget(row, col));
|
||||||
|
if (!type_cb) {
|
||||||
|
type_cb = new QComboBox(m_plc_table);
|
||||||
|
QStringList plc_types = ElementData::plcIOTypeList();
|
||||||
|
for (int t = 0; t < plc_types.size(); ++t)
|
||||||
|
type_cb->addItem(plc_types.at(t), t);
|
||||||
|
m_plc_table->setCellWidget(row, col, type_cb);
|
||||||
|
connect(type_cb, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||||
|
this, [this, row](int) { plcIOCellChanged(row, 0); });
|
||||||
|
}
|
||||||
|
if (!val.isEmpty()) {
|
||||||
|
QStringList plc_types = ElementData::plcIOTypeList();
|
||||||
|
for (int t = 0; t < plc_types.size(); ++t) {
|
||||||
|
if (plc_types.at(t).compare(val, Qt::CaseInsensitive) == 0) {
|
||||||
|
type_cb->setCurrentIndex(t);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (col == 5) {
|
||||||
|
// Terminal count spinbox
|
||||||
|
auto *tc_sb = qobject_cast<QSpinBox*>(m_plc_table->cellWidget(row, col));
|
||||||
|
if (!tc_sb) {
|
||||||
|
tc_sb = new QSpinBox(m_plc_table);
|
||||||
|
tc_sb->setMinimum(1);
|
||||||
|
tc_sb->setMaximum(4);
|
||||||
|
m_plc_table->setCellWidget(row, col, tc_sb);
|
||||||
|
connect(tc_sb, QOverload<int>::of(&QSpinBox::valueChanged),
|
||||||
|
this, [this, row](int) { plcIOCellChanged(row, 5); });
|
||||||
|
}
|
||||||
|
bool ok;
|
||||||
|
int v = val.toInt(&ok);
|
||||||
|
if (ok && v >= 1 && v <= 4)
|
||||||
|
tc_sb->setValue(v);
|
||||||
|
}
|
||||||
|
else if (col == 6) {
|
||||||
|
// CrossRef - read-only
|
||||||
|
auto *item = new QTableWidgetItem(val);
|
||||||
|
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
||||||
|
m_plc_table->setItem(row, col, item);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Text columns: Address, Function, Comment
|
||||||
|
m_plc_table->setItem(row, col, new QTableWidgetItem(val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MasterPropertiesWidget::plcAddRow
|
||||||
|
* Add a new empty row to the PLC IO table
|
||||||
|
*/
|
||||||
|
void MasterPropertiesWidget::plcAddRow()
|
||||||
|
{
|
||||||
|
if (!m_plc_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int row = m_plc_table->rowCount();
|
||||||
|
m_plc_table->insertRow(row);
|
||||||
|
|
||||||
|
// Type combo
|
||||||
|
auto *type_cb = new QComboBox(m_plc_table);
|
||||||
|
QStringList plc_types = ElementData::plcIOTypeList();
|
||||||
|
for (int t = 0; t < plc_types.size(); ++t) {
|
||||||
|
type_cb->addItem(plc_types.at(t), t);
|
||||||
|
}
|
||||||
|
m_plc_table->setCellWidget(row, 0, type_cb);
|
||||||
|
connect(type_cb, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||||
|
this, [this, row](int) { plcIOCellChanged(row, 0); });
|
||||||
|
|
||||||
|
m_plc_table->setItem(row, 1, new QTableWidgetItem());
|
||||||
|
m_plc_table->setItem(row, 2, new QTableWidgetItem());
|
||||||
|
m_plc_table->setItem(row, 3, new QTableWidgetItem());
|
||||||
|
m_plc_table->setItem(row, 4, new QTableWidgetItem());
|
||||||
|
|
||||||
|
auto *tc_sb = new QSpinBox(m_plc_table);
|
||||||
|
tc_sb->setMinimum(1);
|
||||||
|
tc_sb->setMaximum(4);
|
||||||
|
tc_sb->setValue(1);
|
||||||
|
m_plc_table->setCellWidget(row, 5, tc_sb);
|
||||||
|
connect(tc_sb, QOverload<int>::of(&QSpinBox::valueChanged),
|
||||||
|
this, [this, row](int) { plcIOCellChanged(row, 5); });
|
||||||
|
|
||||||
|
auto *crossref_item = new QTableWidgetItem();
|
||||||
|
crossref_item->setFlags(crossref_item->flags() & ~Qt::ItemIsEditable);
|
||||||
|
m_plc_table->setItem(row, 6, crossref_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MasterPropertiesWidget::plcRemoveRow
|
||||||
|
* Remove selected rows from the PLC IO table
|
||||||
|
*/
|
||||||
|
void MasterPropertiesWidget::plcRemoveRow()
|
||||||
|
{
|
||||||
|
if (!m_plc_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QModelIndexList selected = m_plc_table->selectionModel()->selectedRows();
|
||||||
|
if (selected.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Remove from bottom to top to preserve indices
|
||||||
|
std::sort(selected.begin(), selected.end(),
|
||||||
|
[](const QModelIndex &a, const QModelIndex &b) { return a.row() > b.row(); });
|
||||||
|
|
||||||
|
for (const QModelIndex &idx : selected) {
|
||||||
|
m_plc_table->removeRow(idx.row());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MasterPropertiesWidget::plcMoveRowUp
|
||||||
|
* Move selected row up
|
||||||
|
*/
|
||||||
|
void MasterPropertiesWidget::plcMoveRowUp()
|
||||||
|
{
|
||||||
|
if (!m_plc_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int row = m_plc_table->currentRow();
|
||||||
|
if (row <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Swap with row above
|
||||||
|
for (int col = 0; col < m_plc_table->columnCount(); ++col) {
|
||||||
|
QWidget *w1 = m_plc_table->cellWidget(row, col);
|
||||||
|
QWidget *w2 = m_plc_table->cellWidget(row - 1, col);
|
||||||
|
m_plc_table->setCellWidget(row, col, w2);
|
||||||
|
m_plc_table->setCellWidget(row - 1, col, w1);
|
||||||
|
|
||||||
|
QTableWidgetItem *i1 = m_plc_table->item(row, col);
|
||||||
|
QTableWidgetItem *i2 = m_plc_table->item(row - 1, col);
|
||||||
|
m_plc_table->setItem(row, col, i2);
|
||||||
|
m_plc_table->setItem(row - 1, col, i1);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_plc_table->setCurrentCell(row - 1, m_plc_table->currentColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MasterPropertiesWidget::plcMoveRowDown
|
||||||
|
* Move selected row down
|
||||||
|
*/
|
||||||
|
void MasterPropertiesWidget::plcMoveRowDown()
|
||||||
|
{
|
||||||
|
if (!m_plc_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int row = m_plc_table->currentRow();
|
||||||
|
if (row < 0 || row >= m_plc_table->rowCount() - 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Swap with row below
|
||||||
|
for (int col = 0; col < m_plc_table->columnCount(); ++col) {
|
||||||
|
QWidget *w1 = m_plc_table->cellWidget(row, col);
|
||||||
|
QWidget *w2 = m_plc_table->cellWidget(row + 1, col);
|
||||||
|
m_plc_table->setCellWidget(row, col, w2);
|
||||||
|
m_plc_table->setCellWidget(row + 1, col, w1);
|
||||||
|
|
||||||
|
QTableWidgetItem *i1 = m_plc_table->item(row, col);
|
||||||
|
QTableWidgetItem *i2 = m_plc_table->item(row + 1, col);
|
||||||
|
m_plc_table->setItem(row, col, i2);
|
||||||
|
m_plc_table->setItem(row + 1, col, i1);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_plc_table->setCurrentCell(row + 1, m_plc_table->currentColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MasterPropertiesWidget::plcIOCellChanged
|
||||||
|
* Called when a cell in the PLC IO table changes
|
||||||
|
*/
|
||||||
|
void MasterPropertiesWidget::plcIOCellChanged(int row, int column)
|
||||||
|
{
|
||||||
|
Q_UNUSED(row)
|
||||||
|
Q_UNUSED(column)
|
||||||
|
if (m_plc_updating || !m_element || !m_plc_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Save immediately
|
||||||
|
plcUpdateDisplaySettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MasterPropertiesWidget::plcUpdateDisplaySettings
|
||||||
|
* Update the IO data from the table (display settings are managed by the editor only)
|
||||||
|
*/
|
||||||
|
void MasterPropertiesWidget::plcUpdateDisplaySettings()
|
||||||
|
{
|
||||||
|
if (m_plc_updating || !m_element || !m_plc_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_plc_updating = true;
|
||||||
|
|
||||||
|
// Preserve existing display settings
|
||||||
|
ElementData ed = m_element->elementData();
|
||||||
|
ElementData::PlcMasterData plc_data = ed.plcMasterData();
|
||||||
|
plc_data.ios.clear();
|
||||||
|
|
||||||
|
// Read IOs from table
|
||||||
|
for (int row = 0; row < m_plc_table->rowCount(); ++row) {
|
||||||
|
ElementData::PlcIO io;
|
||||||
|
|
||||||
|
auto *type_cb = qobject_cast<QComboBox *>(m_plc_table->cellWidget(row, 0));
|
||||||
|
if (type_cb)
|
||||||
|
io.type = static_cast<ElementData::PlcIOType>(type_cb->currentData().toInt());
|
||||||
|
|
||||||
|
auto *addr_item = m_plc_table->item(row, 1);
|
||||||
|
if (addr_item)
|
||||||
|
io.address = addr_item->text();
|
||||||
|
|
||||||
|
auto *func_item = m_plc_table->item(row, 2);
|
||||||
|
if (func_item)
|
||||||
|
io.functionText = func_item->text();
|
||||||
|
|
||||||
|
auto *comment_item = m_plc_table->item(row, 3);
|
||||||
|
if (comment_item)
|
||||||
|
io.comment = comment_item->text();
|
||||||
|
|
||||||
|
auto *crossref_item = m_plc_table->item(row, 4);
|
||||||
|
if (crossref_item)
|
||||||
|
io.crossRef = crossref_item->text();
|
||||||
|
|
||||||
|
plc_data.ios.append(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
ed.setPlcMasterData(plc_data);
|
||||||
|
m_element->setElementData(ed);
|
||||||
|
|
||||||
|
// Trigger update of the cross ref item
|
||||||
|
if (m_element->scene())
|
||||||
|
m_element->update();
|
||||||
|
|
||||||
|
m_plc_updating = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MasterPropertiesWidget::plcShowTableContextMenu
|
||||||
|
* Show context menu for the PLC IO table
|
||||||
|
*/
|
||||||
|
void MasterPropertiesWidget::plcShowTableContextMenu(const QPoint &pos)
|
||||||
|
{
|
||||||
|
Q_UNUSED(pos)
|
||||||
|
if (!m_plc_table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QMenu menu;
|
||||||
|
menu.addAction(tr("Coller depuis le presse-papiers"), this, &MasterPropertiesWidget::plcPasteFromClipboard);
|
||||||
|
|
||||||
|
menu.exec(m_plc_table->mapToGlobal(pos));
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ class Diagram;
|
|||||||
class QTreeWidgetItem;
|
class QTreeWidgetItem;
|
||||||
class QMenu;
|
class QMenu;
|
||||||
class QAction;
|
class QAction;
|
||||||
|
class QTableWidget;
|
||||||
|
class QSpinBox;
|
||||||
|
class QCheckBox;
|
||||||
|
class QComboBox;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class MasterPropertiesWidget;
|
class MasterPropertiesWidget;
|
||||||
@@ -75,6 +79,17 @@ class MasterPropertiesWidget : public AbstractElementPropertiesEditorWidget
|
|||||||
void diagramWasdeletedFromProject();
|
void diagramWasdeletedFromProject();
|
||||||
void customContextMenu(const QPoint &pos, int i=0);
|
void customContextMenu(const QPoint &pos, int i=0);
|
||||||
|
|
||||||
|
// PLC IO table slots
|
||||||
|
void plcPasteFromClipboard();
|
||||||
|
void setCellFromValue(int row, int col, const QString &val);
|
||||||
|
void plcAddRow();
|
||||||
|
void plcRemoveRow();
|
||||||
|
void plcMoveRowUp();
|
||||||
|
void plcMoveRowDown();
|
||||||
|
void plcIOCellChanged(int row, int column);
|
||||||
|
void plcUpdateDisplaySettings();
|
||||||
|
void plcShowTableContextMenu(const QPoint &pos);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MasterPropertiesWidget *ui;
|
Ui::MasterPropertiesWidget *ui;
|
||||||
QHash <QTreeWidgetItem *, Element *> m_qtwi_hash;
|
QHash <QTreeWidgetItem *, Element *> m_qtwi_hash;
|
||||||
@@ -88,6 +103,16 @@ class MasterPropertiesWidget : public AbstractElementPropertiesEditorWidget
|
|||||||
*m_show_qtwi,
|
*m_show_qtwi,
|
||||||
*m_show_element,
|
*m_show_element,
|
||||||
*m_save_header_state;
|
*m_save_header_state;
|
||||||
|
|
||||||
|
// PLC-specific members
|
||||||
|
QWidget *m_plc_widget = nullptr;
|
||||||
|
QTableWidget *m_plc_table = nullptr;
|
||||||
|
QCheckBox *m_plc_break_checkboxes[4] = {nullptr, nullptr, nullptr, nullptr};
|
||||||
|
QSpinBox *m_plc_break_spinboxes[4] = {nullptr, nullptr, nullptr, nullptr};
|
||||||
|
QSpinBox *m_plc_row_height_spinbox = nullptr;
|
||||||
|
QList<QCheckBox *> m_plc_col_visibility_checkboxes;
|
||||||
|
QList<QSpinBox *> m_plc_col_width_spinboxes;
|
||||||
|
bool m_plc_updating = false; // Guard against recursive updates
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MASTERPROPERTIESWIDGET_H
|
#endif // MASTERPROPERTIESWIDGET_H
|
||||||
|
|||||||
@@ -0,0 +1,371 @@
|
|||||||
|
/*
|
||||||
|
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 "plclinkwidget.h"
|
||||||
|
|
||||||
|
#include "../qetgraphicsitem/masterelement.h"
|
||||||
|
#include "../qetgraphicsitem/element.h"
|
||||||
|
#include "../elementprovider.h"
|
||||||
|
#include "../undocommand/linkelementcommand.h"
|
||||||
|
#include "../diagram.h"
|
||||||
|
#include "../qetproject.h"
|
||||||
|
#include "../properties/elementdata.h"
|
||||||
|
|
||||||
|
#include <QTreeWidget>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QFont>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QAction>
|
||||||
|
|
||||||
|
PlcLinkWidget::PlcLinkWidget(Element *elmt, QWidget *parent)
|
||||||
|
: AbstractElementPropertiesEditorWidget(parent)
|
||||||
|
{
|
||||||
|
auto *main_layout = new QGridLayout(this);
|
||||||
|
|
||||||
|
// Row 0: Status label + buttons (shown when already linked)
|
||||||
|
m_label = new QLabel(tr("Cet élément est déjà lié"), this);
|
||||||
|
m_unlink_pb = new QPushButton(tr("Délier"), this);
|
||||||
|
m_show_this_pb = new QPushButton(tr("Voir cet élément"), this);
|
||||||
|
|
||||||
|
main_layout->addWidget(m_label, 0, 0);
|
||||||
|
main_layout->addWidget(m_unlink_pb, 0, 1);
|
||||||
|
main_layout->addWidget(m_show_this_pb, 0, 2);
|
||||||
|
|
||||||
|
// Row 1: Search field
|
||||||
|
m_search_field = new QLineEdit(this);
|
||||||
|
m_search_field->setPlaceholderText(tr("Recherche"));
|
||||||
|
main_layout->addWidget(m_search_field, 1, 0, 1, 3);
|
||||||
|
|
||||||
|
// Row 2: Tree widget
|
||||||
|
m_tree_widget = new QTreeWidget(this);
|
||||||
|
m_tree_widget->setHeaderLabels({
|
||||||
|
tr("Label"), tr("Type"), tr("Adresse"),
|
||||||
|
tr("Fonction"), tr("Commentaire"), tr("Anschlüsse")
|
||||||
|
});
|
||||||
|
m_tree_widget->setRootIsDecorated(true);
|
||||||
|
m_tree_widget->setIndentation(20);
|
||||||
|
m_tree_widget->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
m_tree_widget->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
m_tree_widget->header()->setStretchLastSection(false);
|
||||||
|
m_tree_widget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||||
|
m_tree_widget->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
||||||
|
m_tree_widget->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
|
||||||
|
m_tree_widget->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
|
||||||
|
m_tree_widget->header()->setSectionResizeMode(4, QHeaderView::ResizeToContents);
|
||||||
|
m_tree_widget->header()->setSectionResizeMode(5, QHeaderView::ResizeToContents);
|
||||||
|
main_layout->addWidget(m_tree_widget, 2, 0, 1, 3);
|
||||||
|
|
||||||
|
// Row 3: Hidden masters note
|
||||||
|
m_hidden_masters_label = new QLabel(
|
||||||
|
tr("Remarque : les éléments maîtres ayant atteint leur nombre maximal "
|
||||||
|
"d'esclaves sont masqués."), this);
|
||||||
|
m_hidden_masters_label->setWordWrap(true);
|
||||||
|
QFont italic_font = m_hidden_masters_label->font();
|
||||||
|
italic_font.setItalic(true);
|
||||||
|
m_hidden_masters_label->setFont(italic_font);
|
||||||
|
m_hidden_masters_label->hide();
|
||||||
|
main_layout->addWidget(m_hidden_masters_label, 3, 0, 1, 3);
|
||||||
|
|
||||||
|
main_layout->setRowStretch(2, 1);
|
||||||
|
|
||||||
|
setMinimumWidth(500);
|
||||||
|
|
||||||
|
// Connections
|
||||||
|
connect(m_unlink_pb, &QPushButton::clicked,
|
||||||
|
this, &PlcLinkWidget::on_m_unlink_pb_clicked);
|
||||||
|
connect(m_show_this_pb, &QPushButton::clicked,
|
||||||
|
this, &PlcLinkWidget::on_m_show_this_pb_clicked);
|
||||||
|
connect(m_search_field, &QLineEdit::textEdited,
|
||||||
|
this, &PlcLinkWidget::on_m_search_field_textEdited);
|
||||||
|
connect(m_tree_widget, &QTreeWidget::customContextMenuRequested,
|
||||||
|
this, &PlcLinkWidget::on_m_tree_widget_customContextMenuRequested);
|
||||||
|
|
||||||
|
if (elmt)
|
||||||
|
setElement(elmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlcLinkWidget::setElement(Element *element)
|
||||||
|
{
|
||||||
|
if (m_element == element)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_element = element;
|
||||||
|
updateUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlcLinkWidget::apply()
|
||||||
|
{
|
||||||
|
QUndoCommand *undo = associatedUndo();
|
||||||
|
if (undo)
|
||||||
|
m_element->diagram()->undoStack().push(undo);
|
||||||
|
|
||||||
|
m_element_to_link = nullptr;
|
||||||
|
m_pending_io_index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUndoCommand *PlcLinkWidget::associatedUndo() const
|
||||||
|
{
|
||||||
|
if (!m_element_to_link || m_pending_io_index < 0)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
LinkElementCommand *undo = new LinkElementCommand(m_element);
|
||||||
|
undo->setLink(m_element_to_link);
|
||||||
|
undo->setGroupIndex(m_pending_io_index);
|
||||||
|
return undo;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PlcLinkWidget::title() const
|
||||||
|
{
|
||||||
|
return tr("Automate (PLC)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlcLinkWidget::updateUi()
|
||||||
|
{
|
||||||
|
if (m_element->isFree())
|
||||||
|
hideButtons();
|
||||||
|
else
|
||||||
|
showButtons();
|
||||||
|
|
||||||
|
buildPlcTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief PlcLinkWidget::buildPlcTree
|
||||||
|
Build the tree showing all PLC-Masters with their IO entries.
|
||||||
|
IO entries already linked to a slave are shown as struck-through and greyed out.
|
||||||
|
*/
|
||||||
|
void PlcLinkWidget::buildPlcTree()
|
||||||
|
{
|
||||||
|
m_tree_widget->clear();
|
||||||
|
m_io_entry_hash.clear();
|
||||||
|
|
||||||
|
if (!m_element || !m_element->diagram() || !m_element->diagram()->project())
|
||||||
|
return;
|
||||||
|
|
||||||
|
ElementProvider ep(m_element->diagram()->project());
|
||||||
|
QVector<QPointer<Element>> masters = ep.find(ElementData::Master);
|
||||||
|
|
||||||
|
QSet<int> used_io_indices;
|
||||||
|
|
||||||
|
for (Element *elmt : masters) {
|
||||||
|
if (!elmt || elmt->elementData().m_master_type != ElementData::PLC)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto &plc_data = elmt->elementData().plcMasterData();
|
||||||
|
if (plc_data.ios.isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Skip full masters
|
||||||
|
MasterElement *me = static_cast<MasterElement*>(elmt);
|
||||||
|
if (me->isFull()) {
|
||||||
|
m_hidden_masters_label->show();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect used IO indices for this master
|
||||||
|
used_io_indices.clear();
|
||||||
|
for (Element *linked : elmt->linkedElements()) {
|
||||||
|
int idx = elmt->groupIndexForElement(linked);
|
||||||
|
if (idx >= 0)
|
||||||
|
used_io_indices.insert(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create parent item for this PLC-Master
|
||||||
|
auto *parent_item = new QTreeWidgetItem(m_tree_widget);
|
||||||
|
|
||||||
|
// Show master label + diagram info
|
||||||
|
QString master_label = elmt->actualLabel();
|
||||||
|
if (master_label.isEmpty())
|
||||||
|
master_label = elmt->name();
|
||||||
|
|
||||||
|
QString folio_info;
|
||||||
|
if (elmt->diagram()) {
|
||||||
|
folio_info = QString::number(elmt->diagram()->folioIndex() + 1);
|
||||||
|
}
|
||||||
|
parent_item->setText(0, QString("%1 (%2)")
|
||||||
|
.arg(master_label)
|
||||||
|
.arg(folio_info));
|
||||||
|
QFont bold_font = parent_item->font(0);
|
||||||
|
bold_font.setBold(true);
|
||||||
|
parent_item->setFont(0, bold_font);
|
||||||
|
parent_item->setExpanded(false);
|
||||||
|
|
||||||
|
// Add child items for each IO entry
|
||||||
|
for (int i = 0; i < plc_data.ios.size(); ++i) {
|
||||||
|
const auto &io = plc_data.ios.at(i);
|
||||||
|
auto *child_item = new QTreeWidgetItem(parent_item);
|
||||||
|
|
||||||
|
child_item->setText(1, ElementData::translatedPlcIOType(io.type));
|
||||||
|
child_item->setText(2, io.address);
|
||||||
|
child_item->setText(3, io.functionText);
|
||||||
|
child_item->setText(4, io.comment);
|
||||||
|
|
||||||
|
// Terminal count
|
||||||
|
child_item->setText(5, QString::number(io.terminalCount));
|
||||||
|
|
||||||
|
PlcIoEntry entry;
|
||||||
|
entry.master = elmt;
|
||||||
|
entry.ioIndex = i;
|
||||||
|
m_io_entry_hash.insert(child_item, entry);
|
||||||
|
|
||||||
|
// If this IO is already linked to a slave, grey it out and strike through
|
||||||
|
if (used_io_indices.contains(i)) {
|
||||||
|
QFont strike_font = child_item->font(0);
|
||||||
|
strike_font.setStrikeOut(true);
|
||||||
|
child_item->setFont(0, strike_font);
|
||||||
|
child_item->setFont(1, strike_font);
|
||||||
|
child_item->setFont(2, strike_font);
|
||||||
|
child_item->setFont(3, strike_font);
|
||||||
|
child_item->setFont(4, strike_font);
|
||||||
|
child_item->setFont(5, strike_font);
|
||||||
|
|
||||||
|
QBrush grey_brush(Qt::gray);
|
||||||
|
for (int col = 0; col < 6; ++col)
|
||||||
|
child_item->setForeground(col, grey_brush);
|
||||||
|
|
||||||
|
// Show which slave is linked
|
||||||
|
for (Element *linked : elmt->linkedElements()) {
|
||||||
|
if (elmt->groupIndexForElement(linked) == i) {
|
||||||
|
child_item->setToolTip(0,
|
||||||
|
tr("Lié à: %1").arg(linked->actualLabel()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
child_item->setFlags(child_item->flags() & ~Qt::ItemIsSelectable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlcLinkWidget::hideButtons()
|
||||||
|
{
|
||||||
|
m_label->hide();
|
||||||
|
m_unlink_pb->hide();
|
||||||
|
m_show_this_pb->hide();
|
||||||
|
m_search_field->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlcLinkWidget::showButtons()
|
||||||
|
{
|
||||||
|
m_label->show();
|
||||||
|
m_unlink_pb->show();
|
||||||
|
m_show_this_pb->show();
|
||||||
|
m_search_field->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlcLinkWidget::on_m_search_field_textEdited(const QString &text)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_tree_widget->topLevelItemCount(); ++i) {
|
||||||
|
QTreeWidgetItem *parent = m_tree_widget->topLevelItem(i);
|
||||||
|
bool any_child_visible = false;
|
||||||
|
|
||||||
|
for (int j = 0; j < parent->childCount(); ++j) {
|
||||||
|
QTreeWidgetItem *child = parent->child(j);
|
||||||
|
bool match = text.isEmpty();
|
||||||
|
if (!match) {
|
||||||
|
for (int col = 0; col < child->columnCount(); ++col) {
|
||||||
|
if (child->text(col).contains(text, Qt::CaseInsensitive)) {
|
||||||
|
match = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child->setHidden(!match);
|
||||||
|
if (match) any_child_visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check if parent label matches
|
||||||
|
if (!text.isEmpty() && parent->text(0).contains(text, Qt::CaseInsensitive)) {
|
||||||
|
any_child_visible = true;
|
||||||
|
for (int j = 0; j < parent->childCount(); ++j)
|
||||||
|
parent->child(j)->setHidden(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent->setHidden(!any_child_visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlcLinkWidget::on_m_tree_widget_customContextMenuRequested(const QPoint &pos)
|
||||||
|
{
|
||||||
|
QTreeWidgetItem *item = m_tree_widget->itemAt(pos);
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Only show context menu for IO child items (not parent master items)
|
||||||
|
if (!m_io_entry_hash.contains(item))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const PlcIoEntry &entry = m_io_entry_hash.value(item);
|
||||||
|
|
||||||
|
// Check if this IO is already linked
|
||||||
|
Element *master = entry.master;
|
||||||
|
if (!master)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QSet<int> used_indices;
|
||||||
|
for (Element *linked : master->linkedElements()) {
|
||||||
|
int idx = master->groupIndexForElement(linked);
|
||||||
|
if (idx >= 0)
|
||||||
|
used_indices.insert(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (used_indices.contains(entry.ioIndex))
|
||||||
|
return; // Already linked, no actions
|
||||||
|
|
||||||
|
QMenu menu;
|
||||||
|
QAction *link_action = menu.addAction(tr("Connecter"));
|
||||||
|
|
||||||
|
QAction *selected_action = menu.exec(m_tree_widget->viewport()->mapToGlobal(pos));
|
||||||
|
if (selected_action == link_action) {
|
||||||
|
m_element_to_link = master;
|
||||||
|
m_pending_io_index = entry.ioIndex;
|
||||||
|
apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlcLinkWidget::on_m_unlink_pb_clicked()
|
||||||
|
{
|
||||||
|
if (!m_element || m_element->isFree())
|
||||||
|
return;
|
||||||
|
|
||||||
|
LinkElementCommand *undo = new LinkElementCommand(m_element);
|
||||||
|
undo->unlinkAll();
|
||||||
|
m_element->diagram()->undoStack().push(undo);
|
||||||
|
|
||||||
|
updateUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlcLinkWidget::on_m_show_this_pb_clicked()
|
||||||
|
{
|
||||||
|
if (!m_element || !m_element->diagram())
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_element->diagram()->showMe();
|
||||||
|
m_element->setHighlighted(true);
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2006-2026 The QElectroTech Team
|
||||||
|
This file is part of QElectroTech.
|
||||||
|
|
||||||
|
QElectroTech is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
QElectroTech is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifndef PLCLINKWIDGET_H
|
||||||
|
#define PLCLINKWIDGET_H
|
||||||
|
|
||||||
|
#include "abstractelementpropertieseditorwidget.h"
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
|
class QTreeWidgetItem;
|
||||||
|
class QTreeWidget;
|
||||||
|
class QLineEdit;
|
||||||
|
class QPushButton;
|
||||||
|
class QLabel;
|
||||||
|
class Element;
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief The PlcLinkWidget class
|
||||||
|
Provides a dedicated widget for linking a PLC-Slave to a PLC-Master IO entry.
|
||||||
|
Displays all PLC-Masters as expandable items, each showing their IO entries.
|
||||||
|
Each IO entry can only be linked to one slave. Already-linked entries are
|
||||||
|
shown as struck-through and greyed out.
|
||||||
|
*/
|
||||||
|
class PlcLinkWidget : public AbstractElementPropertiesEditorWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PlcLinkWidget(Element *elmt, QWidget *parent = nullptr);
|
||||||
|
~PlcLinkWidget() override = default;
|
||||||
|
|
||||||
|
void setElement(Element *element) override;
|
||||||
|
void apply() override;
|
||||||
|
QUndoCommand *associatedUndo() const override;
|
||||||
|
QString title() const override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateUi() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void buildPlcTree();
|
||||||
|
void hideButtons();
|
||||||
|
void showButtons();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void on_m_search_field_textEdited(const QString &text);
|
||||||
|
void on_m_tree_widget_customContextMenuRequested(const QPoint &pos);
|
||||||
|
void on_m_unlink_pb_clicked();
|
||||||
|
void on_m_show_this_pb_clicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLabel *m_label{nullptr};
|
||||||
|
QPushButton *m_unlink_pb{nullptr};
|
||||||
|
QPushButton *m_show_this_pb{nullptr};
|
||||||
|
QLineEdit *m_search_field{nullptr};
|
||||||
|
QTreeWidget *m_tree_widget{nullptr};
|
||||||
|
QLabel *m_hidden_masters_label{nullptr};
|
||||||
|
|
||||||
|
// Maps IO child items to (master, io_index)
|
||||||
|
struct PlcIoEntry {
|
||||||
|
QPointer<Element> master;
|
||||||
|
int ioIndex = -1;
|
||||||
|
};
|
||||||
|
QHash<QTreeWidgetItem*, PlcIoEntry> m_io_entry_hash;
|
||||||
|
|
||||||
|
Element *m_element_to_link = nullptr;
|
||||||
|
int m_pending_io_index = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PLCLINKWIDGET_H
|
||||||
@@ -101,6 +101,7 @@ void XRefPropertiesWidget::buildUi()
|
|||||||
ui -> m_type_cb -> addItem(tr("Bobine"), "coil");
|
ui -> m_type_cb -> addItem(tr("Bobine"), "coil");
|
||||||
ui -> m_type_cb -> addItem(tr("Organe de protection"), "protection");
|
ui -> m_type_cb -> addItem(tr("Organe de protection"), "protection");
|
||||||
ui -> m_type_cb -> addItem(tr("Commutateur / bouton"), "commutator");
|
ui -> m_type_cb -> addItem(tr("Commutateur / bouton"), "commutator");
|
||||||
|
ui -> m_type_cb -> addItem(tr("Automate (PLC)"), "plc");
|
||||||
|
|
||||||
ui -> m_snap_to_cb -> addItem(tr("En bas de page"), "bottom");
|
ui -> m_snap_to_cb -> addItem(tr("En bas de page"), "bottom");
|
||||||
ui -> m_snap_to_cb -> addItem(tr("Sous le label de l'élément"), "label");
|
ui -> m_snap_to_cb -> addItem(tr("Sous le label de l'élément"), "label");
|
||||||
|
|||||||
@@ -19,10 +19,35 @@
|
|||||||
|
|
||||||
#include "../conductorautonumerotation.h"
|
#include "../conductorautonumerotation.h"
|
||||||
#include "../diagram.h"
|
#include "../diagram.h"
|
||||||
|
#include "../diagramposition.h"
|
||||||
|
#include "../qetproject.h"
|
||||||
#include "../qetgraphicsitem/conductor.h"
|
#include "../qetgraphicsitem/conductor.h"
|
||||||
#include "../qetgraphicsitem/element.h"
|
#include "../qetgraphicsitem/element.h"
|
||||||
#include "../qetgraphicsitem/terminal.h"
|
#include "../qetgraphicsitem/terminal.h"
|
||||||
#include "../ui/potentialselectordialog.h"
|
#include "../ui/potentialselectordialog.h"
|
||||||
|
#include "../qetinformation.h"
|
||||||
|
#include "../properties/elementdata.h"
|
||||||
|
#include "../properties/xrefproperties.h"
|
||||||
|
#include "../autoNum/assignvariables.h"
|
||||||
|
#include "../autoNum/numerotationcontextcommands.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
@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 LinkElementCommand::LinkElementCommand
|
@brief LinkElementCommand::LinkElementCommand
|
||||||
@@ -99,6 +124,14 @@ bool LinkElementCommand::isLinkable(Element *element_a, Element *element_b, bool
|
|||||||
{
|
{
|
||||||
//Type isn't good
|
//Type isn't good
|
||||||
if (element_b->linkType() != Element::Slave) return false;
|
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
|
//element_b is free
|
||||||
if (element_b->isFree()) return true;
|
if (element_b->isFree()) return true;
|
||||||
//element_b isn't free but already linked to element_a and already_linked is true
|
//element_b isn't free but already linked to element_a and already_linked is true
|
||||||
@@ -111,6 +144,14 @@ bool LinkElementCommand::isLinkable(Element *element_a, Element *element_b, bool
|
|||||||
{
|
{
|
||||||
//Type isn't good
|
//Type isn't good
|
||||||
if (element_b->linkType() != Element::Master) return false;
|
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
|
//Element_a is free
|
||||||
if (element_a->isFree()) return true;
|
if (element_a->isFree()) return true;
|
||||||
//element_a isn't free but already linked to element_b and already_linked is true;
|
//element_a isn't free but already linked to element_b and already_linked is true;
|
||||||
@@ -195,6 +236,16 @@ void LinkElementCommand::undo()
|
|||||||
t->setMasterLabelIndex(0);
|
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);
|
||||||
|
m_element->setElementInformations(ctx);
|
||||||
|
|
||||||
foreach(Element *elmt, m_element->linkedElements())
|
foreach(Element *elmt, m_element->linkedElements())
|
||||||
{
|
{
|
||||||
if (elmt->linkType() == Element::Master)
|
if (elmt->linkType() == Element::Master)
|
||||||
@@ -220,6 +271,19 @@ void LinkElementCommand::undo()
|
|||||||
t->setMasterLabelIndex(0);
|
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);
|
||||||
|
slave->setElementInformations(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
m_element->setGroupIndexForElement(slave, -1);
|
m_element->setGroupIndexForElement(slave, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,6 +425,28 @@ void LinkElementCommand::makeLink(const QList<Element *> &element_list)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Populate PLC variables on the slave if master is PLC type
|
||||||
|
if (elmt->elementData().m_master_type == ElementData::PLC)
|
||||||
|
{
|
||||||
|
const auto &plc_data = elmt->elementData().plcMasterData();
|
||||||
|
if (group_idx < plc_data.ios.size())
|
||||||
|
{
|
||||||
|
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());
|
||||||
|
m_element->setElementInformations(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -397,6 +483,27 @@ void LinkElementCommand::makeLink(const QList<Element *> &element_list)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
slave->setElementInformations(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -415,6 +522,33 @@ void LinkElementCommand::makeLink(const QList<Element *> &element_list)
|
|||||||
{
|
{
|
||||||
foreach(Element *elmt, to_unlink)
|
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);
|
||||||
|
slave->setElementInformations(ctx);
|
||||||
|
}
|
||||||
m_element->unlinkElement(elmt);
|
m_element->unlinkElement(elmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user