mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-08-03 10:34:14 +02:00
@@ -28,6 +28,7 @@
|
||||
#include "elementinfowidget.h"
|
||||
#include "linksingleelementwidget.h"
|
||||
#include "masterpropertieswidget.h"
|
||||
#include "plclinkwidget.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QUndoStack>
|
||||
@@ -303,7 +304,10 @@ void ElementPropertiesWidget::updateUi()
|
||||
m_list_editor << new ElementInfoWidget(m_element, this);
|
||||
break;
|
||||
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;
|
||||
case Element::Terminale:
|
||||
m_list_editor << new ElementInfoWidget(m_element, this);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "../ui_linksingleelementwidget.h"
|
||||
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QInputDialog>
|
||||
|
||||
|
||||
/**
|
||||
@@ -393,17 +394,24 @@ QVector <QPointer<Element>> LinkSingleElementWidget::availableElements()
|
||||
|
||||
//If element is linked, remove is parent from the list
|
||||
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) {
|
||||
Element *elmt = elmt_vector.at(i);
|
||||
|
||||
// If the item in the list is a 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
|
||||
MasterElement *master = static_cast<MasterElement*>(elmt);
|
||||
// PLC slave can only link to PLC master, and vice versa
|
||||
if (element_is_plc != master_is_plc) {
|
||||
elmt_vector.removeAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the master is full, we'll remove it from the list!
|
||||
MasterElement *master = static_cast<MasterElement*>(elmt);
|
||||
if (master->isFull()) {
|
||||
elmt_vector.removeAt(i);
|
||||
}
|
||||
@@ -536,7 +544,7 @@ void LinkSingleElementWidget::linkTriggered()
|
||||
{
|
||||
if(!m_qtwi_at_context_menu)
|
||||
return;
|
||||
|
||||
|
||||
m_element_to_link = m_qtwi_elmt_hash.value(m_qtwi_at_context_menu);
|
||||
m_pending_group_index = -1;
|
||||
|
||||
@@ -545,25 +553,73 @@ void LinkSingleElementWidget::linkTriggered()
|
||||
&& m_element_to_link
|
||||
&& m_element_to_link->linkType() == Element::Master)
|
||||
{
|
||||
const auto &groups = m_element_to_link->elementData().m_slave_contact_groups;
|
||||
if (!groups.isEmpty())
|
||||
// Check if this is a PLC master
|
||||
if (m_element_to_link->elementData().m_master_type == ElementData::PLC)
|
||||
{
|
||||
// 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);
|
||||
// Show PLC IO selection dialog
|
||||
const auto &plc_data = m_element_to_link->elementData().plcMasterData();
|
||||
if (!plc_data.ios.isEmpty())
|
||||
{
|
||||
// Collect already-used IO 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
m_element->elementData(), this);
|
||||
if (dlg.exec() == QDialog::Accepted && dlg.selectedIndex() >= 0) {
|
||||
m_pending_group_index = dlg.selectedIndex();
|
||||
} else {
|
||||
m_element_to_link = nullptr;
|
||||
return;
|
||||
ContactGroupSelectionDialog dlg(groups, used_indices,
|
||||
m_element->elementData(), this);
|
||||
if (dlg.exec() == QDialog::Accepted && dlg.selectedIndex() >= 0) {
|
||||
m_pending_group_index = dlg.selectedIndex();
|
||||
} else {
|
||||
m_element_to_link = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -588,7 +644,7 @@ void LinkSingleElementWidget::linkTriggered()
|
||||
Qt::NoBrush));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (int i=0 ; i<6 ; i++)
|
||||
{
|
||||
m_qtwi_at_context_menu->setBackground(i,
|
||||
@@ -597,7 +653,7 @@ void LinkSingleElementWidget::linkTriggered()
|
||||
}
|
||||
m_pending_qtwi = m_qtwi_at_context_menu;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,9 +24,21 @@
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "../undocommand/linkelementcommand.h"
|
||||
#include "ui_masterpropertieswidget.h"
|
||||
#include "../properties/elementdata.h"
|
||||
|
||||
#include <QListWidgetItem>
|
||||
#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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
@@ -548,3 +469,564 @@ void MasterPropertiesWidget::customContextMenu(const QPoint &pos, int i)
|
||||
m_context_menu->addAction(m_show_element);
|
||||
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 QMenu;
|
||||
class QAction;
|
||||
class QTableWidget;
|
||||
class QSpinBox;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
|
||||
namespace Ui {
|
||||
class MasterPropertiesWidget;
|
||||
@@ -75,6 +79,17 @@ class MasterPropertiesWidget : public AbstractElementPropertiesEditorWidget
|
||||
void diagramWasdeletedFromProject();
|
||||
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:
|
||||
Ui::MasterPropertiesWidget *ui;
|
||||
QHash <QTreeWidgetItem *, Element *> m_qtwi_hash;
|
||||
@@ -88,6 +103,16 @@ class MasterPropertiesWidget : public AbstractElementPropertiesEditorWidget
|
||||
*m_show_qtwi,
|
||||
*m_show_element,
|
||||
*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
|
||||
|
||||
@@ -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("Organe de protection"), "protection");
|
||||
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("Sous le label de l'élément"), "label");
|
||||
|
||||
Reference in New Issue
Block a user