mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-08-03 10:34:14 +02:00
@@ -31,6 +31,7 @@
|
||||
#include "graphicspart/partline.h"
|
||||
#include "graphicspart/partpolygon.h"
|
||||
#include "graphicspart/partrectangle.h"
|
||||
#include "graphicspart/partplctable.h"
|
||||
#include "graphicspart/partterminal.h"
|
||||
#include "graphicspart/parttext.h"
|
||||
#include "ui/qetelementeditor.h"
|
||||
@@ -84,12 +85,12 @@ ElementData ElementScene::elementData() {
|
||||
|
||||
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;
|
||||
|
||||
m_element_data = data;
|
||||
|
||||
if (emit_)
|
||||
if (emit_info)
|
||||
emit elementInfoChanged();
|
||||
if (type_changed)
|
||||
emit elementTypeChanged();
|
||||
@@ -926,9 +927,41 @@ void ElementScene::slot_editProperties()
|
||||
|
||||
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,
|
||||
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;
|
||||
PartDynamicTextField *pdtf = nullptr;
|
||||
|
||||
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() == "ellipse") 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() == "terminal") cep = new PartTerminal (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);
|
||||
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() == "ellipse") 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() == "terminal") cep = new PartTerminal (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() == "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
|
||||
else if (qde.tagName() == "input") cep = pdtf = new PartDynamicTextField(m_element_editor);
|
||||
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::compositeTextChanged,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()
|
||||
@@ -218,13 +222,34 @@ void DynamicTextFieldEditor::fillInfoComboBox()
|
||||
ui -> m_elmt_info_cb -> clear();
|
||||
|
||||
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)) {
|
||||
strl = QETInformation::folioReportInfoKeys();
|
||||
}
|
||||
else {
|
||||
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) {
|
||||
|
||||
@@ -28,6 +28,19 @@
|
||||
#include <QSignalBlocker>
|
||||
#include <QTableWidgetItem>
|
||||
#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
|
||||
@@ -91,9 +104,16 @@ void ElementPropertiesEditorWidget::upDateInterface()
|
||||
|
||||
if (m_data.m_type == ElementData::Slave)
|
||||
{
|
||||
ui->m_state_cb->setCurrentIndex(
|
||||
ui->m_state_cb->findData(
|
||||
m_data.m_slave_state));
|
||||
// If PLC Slave, select "Esclave PLC" in state combo (not in type combo)
|
||||
if (m_data.m_slave_type == ElementData::PLCSlave) {
|
||||
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->findData(
|
||||
m_data.m_slave_type));
|
||||
@@ -120,6 +140,26 @@ void ElementPropertiesEditorWidget::upDateInterface()
|
||||
if (m_data.m_slave_contact_groups_enabled) {
|
||||
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) {
|
||||
ui->m_terminal_type_cb->setCurrentIndex(
|
||||
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("Inverseur"), ElementData::SW);
|
||||
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("Puissance"), ElementData::Power);
|
||||
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("Organe de protection"), ElementData::Protection);
|
||||
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
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -231,6 +306,30 @@ void ElementPropertiesEditorWidget::updateTree()
|
||||
void ElementPropertiesEditorWidget::populateTree()
|
||||
{
|
||||
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)
|
||||
{
|
||||
QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_tree);
|
||||
@@ -254,21 +353,31 @@ void ElementPropertiesEditorWidget::on_m_buttonBox_accepted()
|
||||
|
||||
if (m_data.m_type == ElementData::Slave)
|
||||
{
|
||||
m_data.m_slave_state = ui->m_state_cb->currentData().value<ElementData::SlaveState>();
|
||||
m_data.m_slave_type = ui->m_type_cb->currentData().value<ElementData::SlaveType>();
|
||||
auto state_val = ui->m_state_cb->currentData();
|
||||
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();
|
||||
}
|
||||
else if (m_data.m_type == ElementData::Master) {
|
||||
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 (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();
|
||||
} else {
|
||||
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)
|
||||
{
|
||||
@@ -532,3 +641,601 @@ void ElementPropertiesEditorWidget::readSlaveGroupsFromTable()
|
||||
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 <QDialog>
|
||||
|
||||
class QTableWidget;
|
||||
class QSpinBox;
|
||||
class QCheckBox;
|
||||
class QGroupBox;
|
||||
class QPushButton;
|
||||
class QLineEdit;
|
||||
|
||||
namespace Ui {
|
||||
class ElementPropertiesEditorWidget;
|
||||
}
|
||||
@@ -51,6 +58,9 @@ class ElementPropertiesEditorWidget : public QDialog
|
||||
void populateTree();
|
||||
void populateSlaveGroupsTable();
|
||||
void readSlaveGroupsFromTable();
|
||||
void createPlcConfigWidgets();
|
||||
void populatePlcTable();
|
||||
void readPlcTable();
|
||||
|
||||
//SLOTS
|
||||
private slots:
|
||||
@@ -58,11 +68,33 @@ class ElementPropertiesEditorWidget : public QDialog
|
||||
void on_m_base_type_cb_currentIndexChanged(int index);
|
||||
void on_m_slave_groups_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
|
||||
private:
|
||||
Ui::ElementPropertiesEditorWidget *ui;
|
||||
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
|
||||
|
||||
@@ -93,17 +93,17 @@
|
||||
<property name="title">
|
||||
<string>Élément maître</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Type concret</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="m_master_type_cb"/>
|
||||
</item>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Type concret</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="m_master_type_cb"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="max_slaves_checkbox">
|
||||
<property name="text">
|
||||
|
||||
Reference in New Issue
Block a user