mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-08-03 10:34:14 +02:00
PLC Manager
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user