mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-08-03 10:34:14 +02:00
372 lines
10 KiB
C++
372 lines
10 KiB
C++
/*
|
|
Copyright 2006-2026 The QElectroTech Team
|
|
This file is part of QElectroTech.
|
|
|
|
QElectroTech is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
QElectroTech is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "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);
|
|
}
|