mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-08-07 13:54:12 +02:00
Merge branch 'master' into qt6_cmake_joshua
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
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 "backupdialog.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
/**
|
||||
@brief BackupDialog::BackupDialog
|
||||
@param parent parent widget
|
||||
*/
|
||||
BackupDialog::BackupDialog(QWidget *parent) :
|
||||
QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Créer une copie de sauvegarde ?", "window title"));
|
||||
setFixedSize(450, 100);
|
||||
|
||||
auto main_layout = new QVBoxLayout(this);
|
||||
|
||||
auto label = new QLabel(
|
||||
tr("Souhaitez-vous créer une copie de sauvegarde ?",
|
||||
"dialog message"));
|
||||
label->setWordWrap(true);
|
||||
main_layout->addWidget(label);
|
||||
|
||||
main_layout->addStretch();
|
||||
|
||||
auto button_layout = new QHBoxLayout();
|
||||
button_layout->addStretch();
|
||||
|
||||
auto yes_button = new QPushButton(tr("Oui", "yes button"));
|
||||
auto no_button = new QPushButton(tr("Non", "no button"));
|
||||
|
||||
button_layout->addWidget(yes_button);
|
||||
button_layout->addWidget(no_button);
|
||||
main_layout->addLayout(button_layout);
|
||||
|
||||
connect(yes_button, &QPushButton::clicked, this, &QDialog::accept);
|
||||
connect(no_button, &QPushButton::clicked, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BackupDialog::~BackupDialog
|
||||
*/
|
||||
BackupDialog::~BackupDialog() = default;
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
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 BACKUPDIALOG_H
|
||||
#define BACKUPDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class BackupDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit BackupDialog(QWidget *parent = nullptr);
|
||||
~BackupDialog() override;
|
||||
};
|
||||
|
||||
#endif // BACKUPDIALOG_H
|
||||
@@ -30,7 +30,7 @@
|
||||
#include "../reportpropertiewidget.h"
|
||||
#include "../titleblockpropertieswidget.h"
|
||||
#include "../xrefpropertieswidget.h"
|
||||
|
||||
#include "guidespropertieswidget.h"
|
||||
#include <QFont>
|
||||
#include <QFontDialog>
|
||||
#include <QSizePolicy>
|
||||
@@ -73,6 +73,33 @@ NewDiagramPage::NewDiagramPage(QETProject *project,
|
||||
rpw = new ReportPropertieWidget(ReportProperties::defaultProperties());
|
||||
// default properties of xref
|
||||
xrefpw = new XRefPropertiesWidget(XRefProperties::defaultProperties(), this);
|
||||
// default guides properties
|
||||
m_gpw = new GuidesPropertiesWidget(this);
|
||||
|
||||
QSettings settings;
|
||||
QList<Diagram::Guide> loaded_guides;
|
||||
if (m_project) {
|
||||
for (const auto &pg : m_project->defaultGuides()) {
|
||||
Diagram::Guide g;
|
||||
g.orientation = static_cast<Diagram::Guide::Orientation>(pg.orientation);
|
||||
g.position = pg.position;
|
||||
g.color = pg.color;
|
||||
loaded_guides.append(g);
|
||||
}
|
||||
} else {
|
||||
QSettings settings;
|
||||
int size = settings.beginReadArray(QStringLiteral("diagrameditor/defaultguides"));
|
||||
for (int i = 0; i < size; ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
Diagram::Guide g;
|
||||
g.orientation = static_cast<Diagram::Guide::Orientation>(settings.value(QStringLiteral("orientation"), 0).toInt());
|
||||
g.position = settings.value(QStringLiteral("position"), 0.0).toReal();
|
||||
g.color = QColor(settings.value(QStringLiteral("color"), QStringLiteral("#ff0000")).toString());
|
||||
loaded_guides.append(g);
|
||||
}
|
||||
settings.endArray();
|
||||
}
|
||||
m_gpw->setGuides(loaded_guides);
|
||||
|
||||
//If there is a project, we edit his properties
|
||||
if (m_project) {
|
||||
@@ -98,6 +125,7 @@ NewDiagramPage::NewDiagramPage(QETProject *project,
|
||||
tab_widget -> addTab (m_cpw, tr("Conducteur"));
|
||||
tab_widget -> addTab (rpw, tr("Reports de folio"));
|
||||
tab_widget -> addTab (xrefpw, tr("Références croisées"));
|
||||
tab_widget -> addTab (m_gpw, tr("Guides"));
|
||||
|
||||
QVBoxLayout *vlayout1 = new QVBoxLayout();
|
||||
vlayout1->addWidget(tab_widget);
|
||||
@@ -155,6 +183,17 @@ void NewDiagramPage::applyConf()
|
||||
modified_project = true;
|
||||
}
|
||||
|
||||
QList<GuideProperties> proj_guides;
|
||||
for (const auto &g : m_gpw->guides()) {
|
||||
GuideProperties pg;
|
||||
pg.orientation = static_cast<int>(g.orientation);
|
||||
pg.position = g.position;
|
||||
pg.color = g.color;
|
||||
proj_guides.append(pg);
|
||||
}
|
||||
m_project->setDefaultGuides(proj_guides);
|
||||
modified_project = true;
|
||||
|
||||
if (modified_project) {
|
||||
m_project -> setModified(modified_project);
|
||||
}
|
||||
@@ -176,13 +215,18 @@ void NewDiagramPage::applyConf()
|
||||
|
||||
// default xref properties
|
||||
QHash <QString, XRefProperties> hash_xrp = xrefpw -> properties();
|
||||
foreach (QString key, hash_xrp.keys()) {
|
||||
XRefProperties xrp = hash_xrp[key];
|
||||
QString str("diagrameditor/defaultxref");
|
||||
xrp.toSettings(settings, str += key);
|
||||
}
|
||||
}
|
||||
|
||||
// Global in QSettings speichern
|
||||
QList<Diagram::Guide> current_guides = m_gpw->guides();
|
||||
settings.beginWriteArray(QStringLiteral("diagrameditor/defaultguides"));
|
||||
for (int i = 0; i < current_guides.size(); ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
settings.setValue(QStringLiteral("orientation"), static_cast<int>(current_guides[i].orientation));
|
||||
settings.setValue(QStringLiteral("position"), current_guides[i].position);
|
||||
settings.setValue(QStringLiteral("color"), current_guides[i].color.name());
|
||||
}
|
||||
settings.endArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,6 +29,7 @@ class TitleBlockPropertiesWidget;
|
||||
class ExportPropertiesWidget;
|
||||
class ReportPropertieWidget;
|
||||
class XRefPropertiesWidget;
|
||||
class GuidesPropertiesWidget;
|
||||
class QETProject;
|
||||
class TitleBlockProperties;
|
||||
|
||||
@@ -69,6 +70,7 @@ public slots:
|
||||
ConductorPropertiesWidget *m_cpw; ///< Widget to edit default conductor properties
|
||||
ReportPropertieWidget *rpw; ///< Widget to edit default report label
|
||||
XRefPropertiesWidget *xrefpw; ///< Widget to edit default xref properties
|
||||
GuidesPropertiesWidget *m_gpw; ///< Widget to edit guides
|
||||
TitleBlockProperties savedTbp; ///< Used to save current TBP and retrieve later
|
||||
|
||||
};
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
#include "../../qeticons.h"
|
||||
#include "ui_generalconfigurationpage.h"
|
||||
#include "../../utils/qetsettings.h"
|
||||
#include "../../utils/qetutils.h"
|
||||
#include "../../qetmessagebox.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QFontDialog>
|
||||
#include <QSettings>
|
||||
#include <sources/ui/configpage/ui_generalconfigurationpage.h>
|
||||
|
||||
/**
|
||||
@brief GeneralConfigurationPage::GeneralConfigurationPage
|
||||
@@ -62,6 +63,9 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
|
||||
ui->m_hdpi_round_policy_cb->setCurrentIndex(4);
|
||||
break;
|
||||
}
|
||||
|
||||
ui->grid_startup_cb->setChecked(settings.value("diagrameditor/grid_display_startup", true).toBool());
|
||||
ui->guides_startup_cb->setChecked(settings.value("diagrameditor/guides_display_startup", false).toBool());
|
||||
ui->DiagramEditor_xGrid_sb->setValue(settings.value("diagrameditor/Xgrid", 10).toInt());
|
||||
ui->DiagramEditor_yGrid_sb->setValue(settings.value("diagrameditor/Ygrid", 10).toInt());
|
||||
ui->DiagramEditor_xKeyGrid_sb->setValue(settings.value("diagrameditor/key_Xgrid", 10).toInt());
|
||||
@@ -95,7 +99,7 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
|
||||
if (settings.contains("diagrameditor/dynamic_text_font"))
|
||||
{
|
||||
QFont font;
|
||||
font.fromString(settings.value("diagrameditor/dynamic_text_font").toString());
|
||||
QETUtils::fontFromString(font, settings.value("diagrameditor/dynamic_text_font").toString());
|
||||
|
||||
QString fontInfos = font.family() + " " +
|
||||
QString::number(font.pointSize()) + " (" +
|
||||
@@ -108,7 +112,7 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
|
||||
if (settings.contains("diagrameditor/independent_text_font"))
|
||||
{
|
||||
QFont font;
|
||||
font.fromString(settings.value("diagrameditor/independent_text_font").toString());
|
||||
QETUtils::fontFromString(font, settings.value("diagrameditor/independent_text_font").toString());
|
||||
|
||||
QString fontInfos = font.family() + " " +
|
||||
QString::number(font.pointSize()) + " (" +
|
||||
@@ -234,6 +238,9 @@ void GeneralConfigurationPage::applyConf()
|
||||
settings.setValue("diagrameditor/highlight-integrated-elements", ui->m_highlight_integrated_elements->isChecked());
|
||||
settings.setValue("diagrameditor/zoom-out-beyond-of-folio", ui->m_zoom_out_beyond_folio->isChecked());
|
||||
settings.setValue("diagrameditor/autosave-interval", ui->m_autosave_sb->value());
|
||||
|
||||
settings.setValue("diagrameditor/grid_display_startup", ui->grid_startup_cb->isChecked());
|
||||
settings.setValue("diagrameditor/guides_display_startup", ui->guides_startup_cb->isChecked());
|
||||
//Grid step and key navigation
|
||||
settings.setValue("diagrameditor/Xgrid", ui->DiagramEditor_xGrid_sb->value());
|
||||
settings.setValue("diagrameditor/Ygrid", ui->DiagramEditor_yGrid_sb->value());
|
||||
@@ -445,11 +452,11 @@ void GeneralConfigurationPage::on_m_dyn_text_font_pb_clicked()
|
||||
bool ok;
|
||||
QSettings settings;
|
||||
QFont curFont;
|
||||
curFont.fromString(settings.value("diagrameditor/dynamic_text_font", "Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular").toString());
|
||||
QETUtils::fontFromString(curFont, settings.value("diagrameditor/dynamic_text_font", "Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular").toString());
|
||||
QFont font = QFontDialog::getFont(&ok, curFont, this);
|
||||
if (ok)
|
||||
{
|
||||
settings.setValue("diagrameditor/dynamic_text_font", font.toString());
|
||||
settings.setValue("diagrameditor/dynamic_text_font", QETUtils::fontToString(font));
|
||||
QString fontInfos = font.family() + " " +
|
||||
QString::number(font.pointSize()) + " (" +
|
||||
font.styleName() + ")";
|
||||
@@ -549,11 +556,11 @@ void GeneralConfigurationPage::on_m_indi_text_font_pb_clicked()
|
||||
bool ok;
|
||||
QSettings settings;
|
||||
QFont curFont;
|
||||
curFont.fromString(settings.value("diagrameditor/independent_text_font", "Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular").toString());
|
||||
QETUtils::fontFromString(curFont, settings.value("diagrameditor/independent_text_font", "Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular").toString());
|
||||
QFont font = QFontDialog::getFont(&ok, curFont, this);
|
||||
if (ok)
|
||||
{
|
||||
settings.setValue("diagrameditor/independent_text_font", font.toString());
|
||||
settings.setValue("diagrameditor/independent_text_font", QETUtils::fontToString(font));
|
||||
QString fontInfos = font.family() + " " +
|
||||
QString::number(font.pointSize()) + " (" +
|
||||
font.styleName() + ")";
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>955</width>
|
||||
<height>556</height>
|
||||
<height>570</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -17,7 +17,7 @@
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
@@ -59,6 +59,27 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="grid_startup_cb">
|
||||
<property name="text">
|
||||
<string>Afficher la grille par défaut (appliqué au prochain lancement)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="guides_startup_cb">
|
||||
<property name="text">
|
||||
<string>Afficher les guides par défaut (appliqué au prochain lancement)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="m_use_windows_mode_rb">
|
||||
<property name="text">
|
||||
@@ -350,7 +371,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<widget class="QLabel" name="label_23">
|
||||
<property name="text">
|
||||
<string>Répertoire des Macros utilisateur</string>
|
||||
</property>
|
||||
@@ -911,30 +932,10 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
|
||||
<string>Affichage Grille</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="1">
|
||||
<spacer name="horizontalSpacer_10">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>555</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="text">
|
||||
<string>max:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="Label_Diagram_Grid_PointSize">
|
||||
<property name="text">
|
||||
<string>Taille des points de la grille de Diagram-Editor : 1 - 5</string>
|
||||
<string>min:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -954,10 +955,39 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_19">
|
||||
<item row="0" column="1">
|
||||
<spacer name="horizontalSpacer_10">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>555</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QSpinBox" name="ElementEditor_Grid_PointSize_min_sb">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="Label_Diagram_Grid_PointSize">
|
||||
<property name="text">
|
||||
<string>min:</string>
|
||||
<string>Taille des points de la grille de Diagram-Editor : 1 - 5</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -977,13 +1007,6 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="text">
|
||||
<string>Taille des points de la grille de l'éditeur d'éléments : 1 - 5</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="5">
|
||||
<widget class="QSpinBox" name="ElementEditor_Grid_PointSize_max_sb">
|
||||
<property name="minimumSize">
|
||||
@@ -1007,19 +1030,10 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QSpinBox" name="ElementEditor_Grid_PointSize_min_sb">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>5</number>
|
||||
<item row="0" column="4">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>max:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -1030,6 +1044,13 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="text">
|
||||
<string>Taille des points de la grille de l'éditeur d'éléments : 1 - 5</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -1110,7 +1131,6 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>m_use_system_color_cb</tabstop>
|
||||
<tabstop>m_use_gesture_trackpad</tabstop>
|
||||
<tabstop>m_zoom_out_beyond_folio</tabstop>
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#include "guidespropertieswidget.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QTableWidget>
|
||||
#include <QHeaderView>
|
||||
#include <QComboBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QColorDialog>
|
||||
|
||||
GuidesPropertiesWidget::GuidesPropertiesWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setupUi();
|
||||
}
|
||||
|
||||
GuidesPropertiesWidget::~GuidesPropertiesWidget() {}
|
||||
|
||||
void GuidesPropertiesWidget::setupUi() {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
|
||||
m_table = new QTableWidget(0, 3, this);
|
||||
m_table->setHorizontalHeaderLabels({tr("Orientation"), tr("Position"), tr("Couleur")});
|
||||
m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
|
||||
m_add_btn = new QPushButton(tr("Ajouter"), this);
|
||||
m_remove_btn = new QPushButton(tr("Supprimer"), this);
|
||||
|
||||
QHBoxLayout *btn_layout = new QHBoxLayout();
|
||||
btn_layout->addWidget(m_add_btn);
|
||||
btn_layout->addWidget(m_remove_btn);
|
||||
btn_layout->addStretch();
|
||||
|
||||
main_layout->addWidget(m_table);
|
||||
main_layout->addLayout(btn_layout);
|
||||
|
||||
connect(m_add_btn, &QPushButton::clicked, this, &GuidesPropertiesWidget::addGuide);
|
||||
connect(m_remove_btn, &QPushButton::clicked, this, &GuidesPropertiesWidget::removeGuide);
|
||||
}
|
||||
|
||||
QList<Diagram::Guide> GuidesPropertiesWidget::guides() const {
|
||||
QList<Diagram::Guide> list;
|
||||
for (int row = 0; row < m_table->rowCount(); ++row) {
|
||||
QComboBox *combo = qobject_cast<QComboBox*>(m_table->cellWidget(row, 0));
|
||||
QDoubleSpinBox *spin = qobject_cast<QDoubleSpinBox*>(m_table->cellWidget(row, 1));
|
||||
QPushButton *colorBtn = qobject_cast<QPushButton*>(m_table->cellWidget(row, 2));
|
||||
|
||||
if (combo && spin && colorBtn) {
|
||||
Diagram::Guide g;
|
||||
g.orientation = (combo->currentIndex() == 0) ? Diagram::Guide::Horizontal : Diagram::Guide::Vertical;
|
||||
g.position = spin->value();
|
||||
g.color = colorBtn->property("color").value<QColor>();
|
||||
list.append(g);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void GuidesPropertiesWidget::setGuides(const QList<Diagram::Guide> &guides) {
|
||||
m_table->setRowCount(0);
|
||||
for (const auto &g : guides) {
|
||||
addGuide();
|
||||
int row = m_table->rowCount() - 1;
|
||||
|
||||
QComboBox *combo = qobject_cast<QComboBox*>(m_table->cellWidget(row, 0));
|
||||
QDoubleSpinBox *spin = qobject_cast<QDoubleSpinBox*>(m_table->cellWidget(row, 1));
|
||||
QPushButton *colorBtn = qobject_cast<QPushButton*>(m_table->cellWidget(row, 2));
|
||||
|
||||
if (combo && spin && colorBtn) {
|
||||
combo->setCurrentIndex(g.orientation == Diagram::Guide::Horizontal ? 0 : 1);
|
||||
spin->setValue(g.position);
|
||||
colorBtn->setProperty("color", g.color);
|
||||
colorBtn->setStyleSheet(QString("background-color: %1; color: white; font-weight: bold;").arg(g.color.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GuidesPropertiesWidget::addGuide() {
|
||||
int row = m_table->rowCount();
|
||||
m_table->insertRow(row);
|
||||
|
||||
QComboBox *combo = new QComboBox(this);
|
||||
combo->addItems({tr("Horizontal"), tr("Vertical")});
|
||||
m_table->setCellWidget(row, 0, combo);
|
||||
|
||||
QDoubleSpinBox *spin = new QDoubleSpinBox(this);
|
||||
spin->setRange(-10000.0, 10000.0);
|
||||
spin->setDecimals(2);
|
||||
spin->setValue(100.0);
|
||||
m_table->setCellWidget(row, 1, spin);
|
||||
|
||||
QPushButton *colorBtn = new QPushButton(tr("Couleur"), this);
|
||||
QColor defaultColor = Qt::lightGray;
|
||||
colorBtn->setProperty("color", defaultColor);
|
||||
colorBtn->setStyleSheet(QString("background-color: %1; color: white; font-weight: bold;").arg(defaultColor.name()));
|
||||
|
||||
connect(colorBtn, &QPushButton::clicked, [this, colorBtn]() {
|
||||
QColor c = QColorDialog::getColor(colorBtn->property("color").value<QColor>(), this);
|
||||
if (c.isValid()) {
|
||||
colorBtn->setProperty("color", c);
|
||||
colorBtn->setStyleSheet(QString("background-color: %1; color: white; font-weight: bold;").arg(c.name()));
|
||||
}
|
||||
});
|
||||
m_table->setCellWidget(row, 2, colorBtn);
|
||||
}
|
||||
|
||||
void GuidesPropertiesWidget::removeGuide() {
|
||||
int row = m_table->currentRow();
|
||||
if (row >= 0) {
|
||||
m_table->removeRow(row);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef GUIDESPROPERTIESWIDGET_H
|
||||
#define GUIDESPROPERTIESWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QList>
|
||||
#include "../../diagram.h"
|
||||
|
||||
class QTableWidget;
|
||||
class QPushButton;
|
||||
|
||||
class GuidesPropertiesWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GuidesPropertiesWidget(QWidget *parent = nullptr);
|
||||
~GuidesPropertiesWidget() override;
|
||||
|
||||
QList<Diagram::Guide> guides() const;
|
||||
void setGuides(const QList<Diagram::Guide> &guides);
|
||||
|
||||
private slots:
|
||||
void addGuide();
|
||||
void removeGuide();
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
|
||||
QTableWidget *m_table;
|
||||
QPushButton *m_add_btn;
|
||||
QPushButton *m_remove_btn;
|
||||
};
|
||||
|
||||
#endif // GUIDESPROPERTIESWIDGET_H
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "../autoNum/ui/folioautonumbering.h"
|
||||
#include "../autoNum/ui/formulaautonumberingw.h"
|
||||
#include "../autoNum/ui/selectautonumw.h"
|
||||
#include "../project/projectpropertieshandler.h"
|
||||
#include "../qeticons.h"
|
||||
#include "../qetproject.h"
|
||||
#include "../borderpropertieswidget.h"
|
||||
@@ -161,6 +162,13 @@ void ProjectMainConfigPage::applyProjectConf()
|
||||
m_project -> setProjectProperties(new_properties);
|
||||
modified_project = true;
|
||||
}
|
||||
|
||||
ProjectUsageTracker &usage_tracker = m_project -> projectPropertiesHandler().usageTracker();
|
||||
if (usage_tracker.isEnabled() != usage_enabled_cb_ -> isChecked()) {
|
||||
usage_tracker.setEnabled(usage_enabled_cb_ -> isChecked());
|
||||
modified_project = true;
|
||||
}
|
||||
|
||||
if (modified_project) {
|
||||
m_project -> setModified(true);
|
||||
}
|
||||
@@ -191,6 +199,12 @@ void ProjectMainConfigPage::initWidgets()
|
||||
project_variables_label_ -> setWordWrap(true);
|
||||
project_variables_ = new DiagramContextWidget();
|
||||
project_variables_ -> setContext(DiagramContext());
|
||||
|
||||
usage_label_ = new QLabel(tr("Temps passé sur ce projet :", "label when configuring"));
|
||||
usage_value_ = new QLabel();
|
||||
usage_enabled_cb_ = new QCheckBox(tr("Suivre le temps passé sur ce projet (uniquement enregistré localement dans ce fichier)", "checkbox label"));
|
||||
usage_reset_pb_ = new QPushButton(tr("Réinitialiser", "button label"));
|
||||
connect(usage_reset_pb_, &QPushButton::clicked, this, &ProjectMainConfigPage::resetUsageTracker);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,6 +221,16 @@ void ProjectMainConfigPage::initLayout()
|
||||
main_layout0 -> addSpacing(10);
|
||||
main_layout0 -> addWidget(project_variables_label_);
|
||||
main_layout0 -> addWidget(project_variables_);
|
||||
main_layout0 -> addSpacing(10);
|
||||
|
||||
QHBoxLayout *usage_layout0 = new QHBoxLayout();
|
||||
usage_layout0 -> addWidget(usage_label_);
|
||||
usage_layout0 -> addWidget(usage_value_);
|
||||
usage_layout0 -> addStretch();
|
||||
usage_layout0 -> addWidget(usage_reset_pb_);
|
||||
main_layout0 -> addLayout(usage_layout0);
|
||||
main_layout0 -> addWidget(usage_enabled_cb_);
|
||||
|
||||
setLayout(main_layout0);
|
||||
this -> setMinimumWidth(680);
|
||||
|
||||
@@ -219,6 +243,27 @@ void ProjectMainConfigPage::readValuesFromProject()
|
||||
{
|
||||
title_value_ -> setText(m_project -> title());
|
||||
project_variables_ -> setContext(m_project -> projectProperties());
|
||||
|
||||
const ProjectUsageTracker &usage_tracker = m_project -> projectPropertiesHandler().usageTracker();
|
||||
const qint64 total_seconds = usage_tracker.secondsSpent();
|
||||
usage_value_ -> setText(tr("%1 h %2 min", "hours and minutes of time spent on a project")
|
||||
.arg(total_seconds / 3600)
|
||||
.arg((total_seconds % 3600) / 60));
|
||||
usage_enabled_cb_ -> setChecked(usage_tracker.isEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ProjectMainConfigPage::resetUsageTracker
|
||||
Reset the accumulated "time spent on this project" counter to zero and
|
||||
refresh its displayed value. Applies immediately (not staged behind
|
||||
OK/Cancel like the other fields on this page), since it isn't
|
||||
destructive to any actual project content.
|
||||
*/
|
||||
void ProjectMainConfigPage::resetUsageTracker()
|
||||
{
|
||||
m_project -> projectPropertiesHandler().usageTracker().resetSecondsSpent();
|
||||
m_project -> setModified(true);
|
||||
usage_value_ -> setText(tr("%1 h %2 min", "hours and minutes of time spent on a project").arg(0).arg(0));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,6 +274,8 @@ void ProjectMainConfigPage::adjustReadOnly()
|
||||
{
|
||||
bool is_read_only = m_project -> isReadOnly();
|
||||
title_value_ -> setReadOnly(is_read_only);
|
||||
usage_enabled_cb_ -> setDisabled(is_read_only);
|
||||
usage_reset_pb_ -> setDisabled(is_read_only);
|
||||
}
|
||||
|
||||
//######################################################################################//
|
||||
@@ -419,7 +466,7 @@ void ProjectAutoNumConfigPage::saveContextElement()
|
||||
m_saw_element->contextComboBox()->addItem(tr("Sans nom"));
|
||||
}
|
||||
// If the text isn't yet to the autonum of the project, add this new item to the combo box.
|
||||
else if ( !m_project -> elementAutoNum().keys().contains( m_saw_element->contextComboBox()->currentText()))
|
||||
else if ( !m_project -> elementAutoNum().contains( m_saw_element->contextComboBox()->currentText()))
|
||||
{
|
||||
m_project->addElementAutoNum(m_saw_element->contextComboBox()->currentText(), m_saw_element->toNumContext());
|
||||
m_project->setCurrrentElementAutonum(m_saw_element->contextComboBox()->currentText());
|
||||
@@ -461,7 +508,7 @@ void ProjectAutoNumConfigPage::saveContextConductor()
|
||||
m_saw_conductor->contextComboBox()-> addItem(tr("Sans nom"));
|
||||
}
|
||||
// If the text isn't yet to the autonum of the project, add this new item to the combo box.
|
||||
else if ( !m_project -> conductorAutoNum().keys().contains( m_saw_conductor->contextComboBox()->currentText()))
|
||||
else if ( !m_project -> conductorAutoNum().contains( m_saw_conductor->contextComboBox()->currentText()))
|
||||
{
|
||||
project()->addConductorAutoNum(m_saw_conductor->contextComboBox()->currentText(), m_saw_conductor->toNumContext());
|
||||
project()->setCurrentConductorAutoNum(m_saw_conductor->contextComboBox()->currentText());
|
||||
@@ -489,7 +536,7 @@ void ProjectAutoNumConfigPage::saveContextFolio()
|
||||
m_saw_folio->contextComboBox() -> addItem(tr("Sans nom"));
|
||||
}
|
||||
// If the text isn't yet to the autonum of the project, add this new item to the combo box.
|
||||
else if ( !m_project -> folioAutoNum().keys().contains( m_saw_folio->contextComboBox()->currentText())) {
|
||||
else if ( !m_project -> folioAutoNum().contains( m_saw_folio->contextComboBox()->currentText())) {
|
||||
project()->addFolioAutoNum(m_saw_folio->contextComboBox()->currentText(), m_saw_folio->toNumContext());
|
||||
m_saw_folio->contextComboBox() -> addItem(m_saw_folio->contextComboBox()->currentText());
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QCheckBox;
|
||||
class QPushButton;
|
||||
class QETProject;
|
||||
class BorderPropertiesWidget;
|
||||
class ConductorPropertiesWidget;
|
||||
@@ -108,7 +110,10 @@ class ProjectMainConfigPage : public ProjectConfigPage {
|
||||
void initLayout() override;
|
||||
void readValuesFromProject() override;
|
||||
void adjustReadOnly() override;
|
||||
|
||||
|
||||
private slots:
|
||||
void resetUsageTracker();
|
||||
|
||||
// attributes
|
||||
protected:
|
||||
QLabel *title_label_;
|
||||
@@ -116,6 +121,10 @@ class ProjectMainConfigPage : public ProjectConfigPage {
|
||||
QLabel *title_information_;
|
||||
QLabel *project_variables_label_;
|
||||
DiagramContextWidget *project_variables_;
|
||||
QLabel *usage_label_;
|
||||
QLabel *usage_value_;
|
||||
QCheckBox *usage_enabled_cb_;
|
||||
QPushButton *usage_reset_pb_;
|
||||
};
|
||||
|
||||
class ProjectAutoNumConfigPage : public ProjectConfigPage {
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
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 "contactgroupselectiondialog.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QTableWidget>
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QHeaderView>
|
||||
#include <QItemSelectionModel>
|
||||
|
||||
ContactGroupSelectionDialog::ContactGroupSelectionDialog(
|
||||
const QVector<ElementData::SlaveContactGroup> &groups,
|
||||
const QSet<int> &usedGroupIndices,
|
||||
const ElementData &slaveData,
|
||||
QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
m_used_indices = usedGroupIndices;
|
||||
setWindowTitle(tr("Sélectionner un groupe de contacts"));
|
||||
|
||||
auto *main_layout = new QVBoxLayout(this);
|
||||
|
||||
auto *info_label = new QLabel(
|
||||
tr("Sélectionnez le groupe de contacts à assigner à cet élément esclave :"));
|
||||
main_layout->addWidget(info_label);
|
||||
|
||||
// Determine max terminal count for dynamic columns
|
||||
int max_terminals = 0;
|
||||
for (const auto &g : groups) {
|
||||
if (g.terminalCount > max_terminals)
|
||||
max_terminals = g.terminalCount;
|
||||
}
|
||||
|
||||
// Build column headers: #, Type, Sous-type, Contacts, Bornes, T1, T2, ...
|
||||
QStringList headers;
|
||||
headers << tr("#")
|
||||
<< tr("Type")
|
||||
<< tr("Sous-type")
|
||||
<< tr("Contacts")
|
||||
<< tr("Bornes");
|
||||
for (int t = 0; t < max_terminals; ++t) {
|
||||
headers << tr("T%1").arg(t + 1);
|
||||
}
|
||||
|
||||
m_table = new QTableWidget(groups.size(), headers.size(), this);
|
||||
m_table->setHorizontalHeaderLabels(headers);
|
||||
m_table->setSelectionBehavior(QTableWidget::SelectRows);
|
||||
m_table->setSelectionMode(QTableWidget::SingleSelection);
|
||||
m_table->setEditTriggers(QTableWidget::NoEditTriggers);
|
||||
m_table->verticalHeader()->setVisible(false);
|
||||
m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
m_table->horizontalHeader()->setStretchLastSection(false);
|
||||
m_table->verticalHeader()->setDefaultSectionSize(24);
|
||||
|
||||
// Populate table rows
|
||||
for (int row = 0; row < groups.size(); ++row) {
|
||||
const auto &g = groups.at(row);
|
||||
|
||||
auto *num_item = new QTableWidgetItem(QString::number(row + 1));
|
||||
num_item->setTextAlignment(Qt::AlignCenter);
|
||||
m_table->setItem(row, 0, num_item);
|
||||
|
||||
m_table->setItem(row, 1, new QTableWidgetItem(typeToString(g.type)));
|
||||
m_table->setItem(row, 2, new QTableWidgetItem(subtypeToString(g.subtype)));
|
||||
|
||||
auto *ctc_item = new QTableWidgetItem(QString::number(g.contactCount));
|
||||
ctc_item->setTextAlignment(Qt::AlignCenter);
|
||||
m_table->setItem(row, 3, ctc_item);
|
||||
|
||||
auto *term_item = new QTableWidgetItem(QString::number(g.terminalCount));
|
||||
term_item->setTextAlignment(Qt::AlignCenter);
|
||||
m_table->setItem(row, 4, term_item);
|
||||
|
||||
// Fill T1..TN label columns
|
||||
for (int t = 0; t < max_terminals; ++t) {
|
||||
int col = 5 + t;
|
||||
QString label;
|
||||
if (t < g.labels.size()) {
|
||||
label = g.labels.at(t);
|
||||
} else if (t < g.terminalCount) {
|
||||
label = tr("T%1").arg(t + 1);
|
||||
}
|
||||
auto *label_item = new QTableWidgetItem(label);
|
||||
label_item->setTextAlignment(Qt::AlignCenter);
|
||||
m_table->setItem(row, col, label_item);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark used group rows as disabled (grayed out)
|
||||
QFont disabled_font;
|
||||
disabled_font.setStrikeOut(true);
|
||||
QColor disabled_color(Qt::gray);
|
||||
|
||||
for (int row = 0; row < groups.size(); ++row) {
|
||||
bool disabled = false;
|
||||
QString reason;
|
||||
|
||||
if (m_used_indices.contains(row)) {
|
||||
disabled = true;
|
||||
reason = tr("(déjà assigné)");
|
||||
} else {
|
||||
const auto &g = groups.at(row);
|
||||
if (g.type != slaveData.m_slave_state) {
|
||||
disabled = true;
|
||||
reason = tr("(état ne correspond pas)");
|
||||
} else if (g.subtype != slaveData.m_slave_type) {
|
||||
disabled = true;
|
||||
reason = tr("(sous-type ne correspond pas)");
|
||||
} else if (g.contactCount != slaveData.m_contact_count) {
|
||||
disabled = true;
|
||||
reason = tr("(nombre de contacts ne correspond pas)");
|
||||
}
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
m_disabled_rows.insert(row);
|
||||
for (int col = 0; col < m_table->columnCount(); ++col) {
|
||||
auto *item = m_table->item(row, col);
|
||||
if (item) {
|
||||
item->setForeground(disabled_color);
|
||||
item->setFont(disabled_font);
|
||||
item->setFlags(item->flags() & ~Qt::ItemIsSelectable);
|
||||
item->setToolTip(reason);
|
||||
}
|
||||
}
|
||||
// Show circled "?" as a widget in the first column
|
||||
auto *num_item = m_table->item(row, 0);
|
||||
if (num_item) {
|
||||
num_item->setToolTip(reason);
|
||||
auto *question_label = new QLabel(this);
|
||||
question_label->setText("?");
|
||||
question_label->setAlignment(Qt::AlignCenter);
|
||||
question_label->setStyleSheet(
|
||||
"QLabel {"
|
||||
" color: #1a73e8;"
|
||||
" font-weight: bold;"
|
||||
" font-size: 8px;"
|
||||
" border: 1.5px solid #1a73e8;"
|
||||
" border-radius: 7px;"
|
||||
" min-width: 12px; max-width: 12px;"
|
||||
" min-height: 12px; max-height: 12px;"
|
||||
"}");
|
||||
question_label->setToolTip(reason);
|
||||
m_table->setCellWidget(row, 0, question_label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Select first available (non-disabled) row
|
||||
int first_available = 0;
|
||||
for (int row = 0; row < groups.size(); ++row) {
|
||||
if (!m_disabled_rows.contains(row)) {
|
||||
first_available = row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_table->selectRow(first_available);
|
||||
main_layout->addWidget(m_table);
|
||||
|
||||
// Calculate width based on column count: base + ~60px per column
|
||||
int table_width = 50 + headers.size() * 65;
|
||||
int table_height = 60 + groups.size() * 26;
|
||||
m_table->setMinimumWidth(table_width);
|
||||
m_table->setMinimumHeight(table_height);
|
||||
|
||||
// Buttons
|
||||
auto *button_layout = new QHBoxLayout();
|
||||
button_layout->addStretch();
|
||||
|
||||
m_ok_button = new QPushButton(tr("OK"), this);
|
||||
button_layout->addWidget(m_ok_button);
|
||||
|
||||
auto *cancel_button = new QPushButton(tr("Annuler"), this);
|
||||
button_layout->addWidget(cancel_button);
|
||||
|
||||
main_layout->addLayout(button_layout);
|
||||
|
||||
// Connections
|
||||
connect(m_ok_button, &QPushButton::clicked, this, [this]() {
|
||||
auto *item = m_table->currentItem();
|
||||
if (item && !m_disabled_rows.contains(item->row())) {
|
||||
m_selected_index = item->row();
|
||||
accept();
|
||||
}
|
||||
});
|
||||
connect(cancel_button, &QPushButton::clicked, this, &QDialog::reject);
|
||||
connect(m_table, &QTableWidget::cellDoubleClicked, this, [this](int row, int) {
|
||||
if (!m_disabled_rows.contains(row)) {
|
||||
m_selected_index = row;
|
||||
accept();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int ContactGroupSelectionDialog::selectedIndex() const
|
||||
{
|
||||
return m_selected_index;
|
||||
}
|
||||
|
||||
QString ContactGroupSelectionDialog::typeToString(ElementData::SlaveState type)
|
||||
{
|
||||
switch (type) {
|
||||
case ElementData::NO: return tr("Normalement ouvert");
|
||||
case ElementData::NC: return tr("Normalement fermé");
|
||||
case ElementData::SW: return tr("Inverseur");
|
||||
case ElementData::Other: return tr("Autre");
|
||||
default: return tr("Inconnu");
|
||||
}
|
||||
}
|
||||
|
||||
QString ContactGroupSelectionDialog::subtypeToString(ElementData::SlaveType subtype)
|
||||
{
|
||||
switch (subtype) {
|
||||
case ElementData::SSimple: return tr("Simple");
|
||||
case ElementData::Power: return tr("Puissance");
|
||||
case ElementData::DelayOn: return tr("Temporisé travail");
|
||||
case ElementData::DelayOff: return tr("Temporisé repos");
|
||||
case ElementData::delayOnOff: return tr("Temporisé travail & repos");
|
||||
default: return tr("Inconnu");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
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 CONTACTGROUPSELECTIONDIALOG_H
|
||||
#define CONTACTGROUPSELECTIONDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QVector>
|
||||
#include <QSet>
|
||||
|
||||
#include "../properties/elementdata.h"
|
||||
|
||||
class QTableWidget;
|
||||
class QTableWidgetItem;
|
||||
class QPushButton;
|
||||
|
||||
/**
|
||||
@brief The ContactGroupSelectionDialog class
|
||||
A dialog that displays all slave contact groups defined by a master element
|
||||
in a table format. The user can select one group to assign to a slave element.
|
||||
Columns: #, Type, Subtype, Contacts, Terminals, T1, T2, ..., TN
|
||||
*/
|
||||
class ContactGroupSelectionDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ContactGroupSelectionDialog(
|
||||
const QVector<ElementData::SlaveContactGroup> &groups,
|
||||
const QSet<int> &usedGroupIndices,
|
||||
const ElementData &slaveData,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
int selectedIndex() const;
|
||||
|
||||
private:
|
||||
QTableWidget *m_table = nullptr;
|
||||
QPushButton *m_ok_button = nullptr;
|
||||
int m_selected_index = -1;
|
||||
QSet<int> m_used_indices;
|
||||
QSet<int> m_disabled_rows;
|
||||
|
||||
static QString typeToString(ElementData::SlaveState type);
|
||||
static QString subtypeToString(ElementData::SlaveType subtype);
|
||||
};
|
||||
|
||||
#endif // CONTACTGROUPSELECTIONDIALOG_H
|
||||
@@ -130,7 +130,7 @@ QList<QStandardItem *> DynamicElementTextModel::itemsForText(
|
||||
{
|
||||
QList <QStandardItem *> qsi_list;
|
||||
|
||||
if(m_texts_list.keys().contains(deti))
|
||||
if(m_texts_list.contains(deti))
|
||||
return qsi_list;
|
||||
|
||||
QStandardItem *qsi = new QStandardItem(deti->toPlainText());
|
||||
@@ -718,7 +718,7 @@ QUndoCommand *DynamicElementTextModel::undoForEditedGroup(
|
||||
*/
|
||||
void DynamicElementTextModel::addGroup(ElementTextItemGroup *group)
|
||||
{
|
||||
if(m_groups_list.keys().contains(group))
|
||||
if(m_groups_list.contains(group))
|
||||
return;
|
||||
|
||||
//Group
|
||||
@@ -869,7 +869,7 @@ void DynamicElementTextModel::addGroup(ElementTextItemGroup *group)
|
||||
*/
|
||||
void DynamicElementTextModel::removeGroup(ElementTextItemGroup *group)
|
||||
{
|
||||
if(m_groups_list.keys().contains(group))
|
||||
if(m_groups_list.contains(group))
|
||||
{
|
||||
QModelIndex group_index = m_groups_list.value(group)->index();
|
||||
this->removeRow(group_index.row(), group_index.parent());
|
||||
@@ -896,7 +896,7 @@ void DynamicElementTextModel::removeTextFromGroup(DynamicElementTextItem *deti,
|
||||
{
|
||||
Q_UNUSED(group)
|
||||
|
||||
if(m_texts_list.keys().contains(deti))
|
||||
if(m_texts_list.contains(deti))
|
||||
{
|
||||
QStandardItem *text_item = m_texts_list.value(deti);
|
||||
QModelIndex text_index = indexFromItem(text_item);
|
||||
@@ -961,7 +961,7 @@ ElementTextItemGroup *DynamicElementTextModel::groupFromItem(
|
||||
QModelIndex DynamicElementTextModel::indexFromGroup(
|
||||
ElementTextItemGroup *group) const
|
||||
{
|
||||
if(m_groups_list.keys().contains(group))
|
||||
if(m_groups_list.contains(group))
|
||||
return m_groups_list.value(group)->index();
|
||||
else
|
||||
return QModelIndex();
|
||||
@@ -1371,7 +1371,7 @@ void DynamicElementTextModel::setConnection(DynamicElementTextItem *deti, bool s
|
||||
{
|
||||
if(set)
|
||||
{
|
||||
if(m_hash_text_connect.keys().contains(deti))
|
||||
if(m_hash_text_connect.contains(deti))
|
||||
return;
|
||||
|
||||
QList<QMetaObject::Connection> connection_list;
|
||||
@@ -1390,7 +1390,7 @@ void DynamicElementTextModel::setConnection(DynamicElementTextItem *deti, bool s
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!m_hash_text_connect.keys().contains(deti))
|
||||
if(!m_hash_text_connect.contains(deti))
|
||||
return;
|
||||
|
||||
for (const QMetaObject::Connection& con : m_hash_text_connect.value(deti))
|
||||
@@ -1412,7 +1412,7 @@ void DynamicElementTextModel::setConnection(ElementTextItemGroup *group, bool se
|
||||
{
|
||||
if(set)
|
||||
{
|
||||
if(m_hash_group_connect.keys().contains(group))
|
||||
if(m_hash_group_connect.contains(group))
|
||||
return;
|
||||
|
||||
QList<QMetaObject::Connection> connection_list;
|
||||
@@ -1429,7 +1429,7 @@ void DynamicElementTextModel::setConnection(ElementTextItemGroup *group, bool se
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!m_hash_group_connect.keys().contains(group))
|
||||
if(!m_hash_group_connect.contains(group))
|
||||
return;
|
||||
|
||||
for (const QMetaObject::Connection& con : m_hash_group_connect.value(group))
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "elementinfowidget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include "../diagram.h"
|
||||
#include "../qetapp.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
@@ -161,6 +161,13 @@ void ElementInfoWidget::enableLiveEdit()
|
||||
for (ElementInfoPartWidget *eipw : m_eipw_list)
|
||||
connect(eipw, &ElementInfoPartWidget::textChanged, this, &ElementInfoWidget::apply);
|
||||
connect(ui->m_auto_num_locked_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
||||
|
||||
if (m_potential_isolating_cb) {
|
||||
connect(m_potential_isolating_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
||||
}
|
||||
if (m_exclude_from_bom_cb) {
|
||||
connect(m_exclude_from_bom_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,6 +179,13 @@ void ElementInfoWidget::disableLiveEdit()
|
||||
for (ElementInfoPartWidget *eipw : m_eipw_list)
|
||||
disconnect(eipw, &ElementInfoPartWidget::textChanged, this, &ElementInfoWidget::apply);
|
||||
disconnect(ui->m_auto_num_locked_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
||||
|
||||
if (m_potential_isolating_cb) {
|
||||
disconnect(m_potential_isolating_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
||||
}
|
||||
if (m_exclude_from_bom_cb) {
|
||||
disconnect(m_exclude_from_bom_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,16 +207,34 @@ void ElementInfoWidget::buildInterface()
|
||||
ui->scroll_vlayout->addWidget(eipw);
|
||||
m_eipw_list << eipw;
|
||||
}
|
||||
|
||||
ui->scroll_vlayout->addStretch();
|
||||
|
||||
// Existing potential isolating checkbox
|
||||
m_potential_isolating_cb = new QCheckBox(tr("Séparation de potentiel"), this);
|
||||
m_potential_isolating_cb->setStyleSheet(QStringLiteral("margin: 5px; font-weight: bold;"));
|
||||
|
||||
// English: Initialize and style the BOM exclusion checkbox
|
||||
m_exclude_from_bom_cb = new QCheckBox(tr("Exclure de la nomenclature"), this);
|
||||
m_exclude_from_bom_cb->setStyleSheet(QStringLiteral("margin: 5px; font-weight: bold;"));
|
||||
|
||||
if (QVBoxLayout *mainLayout = qobject_cast<QVBoxLayout*>(this->layout())) {
|
||||
mainLayout->insertWidget(1, m_potential_isolating_cb);
|
||||
// English: Insert the new checkbox into the main vertical layout
|
||||
mainLayout->insertWidget(2, m_exclude_from_bom_cb);
|
||||
}
|
||||
|
||||
// English: BOM exclusion applies to all elements, so it's always visible
|
||||
m_exclude_from_bom_cb->setVisible(true);
|
||||
|
||||
// Show checkbox only if the element is a terminal
|
||||
if (m_element.data()->elementData().m_type == ElementData::Terminal) {
|
||||
ui->m_auto_num_locked_cb->setVisible(true);
|
||||
m_potential_isolating_cb->setVisible(true);
|
||||
} else {
|
||||
ui->m_auto_num_locked_cb->setVisible(false);
|
||||
m_potential_isolating_cb->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementInfoWidget::infoPartWidgetForKey
|
||||
@param key
|
||||
@@ -211,7 +243,7 @@ void ElementInfoWidget::buildInterface()
|
||||
*/
|
||||
ElementInfoPartWidget *ElementInfoWidget::infoPartWidgetForKey(const QString &key) const
|
||||
{
|
||||
for (const auto &eipw : qAsConst(m_eipw_list))
|
||||
for (const auto &eipw : std::as_const(m_eipw_list))
|
||||
{
|
||||
if (eipw->key() == key)
|
||||
return eipw;
|
||||
@@ -243,6 +275,17 @@ void ElementInfoWidget::updateUi()
|
||||
if (m_element->elementData().m_type == ElementData::Terminal) {
|
||||
QString lock_value = element_info.value(QStringLiteral("auto_num_locked")).toString();
|
||||
ui->m_auto_num_locked_cb->setChecked(lock_value == QLatin1String("true"));
|
||||
|
||||
// English: Load the potential isolating status from the element information mapping
|
||||
if (m_potential_isolating_cb) {
|
||||
QString isolating_value = element_info.value(QStringLiteral("potential_isolating")).toString();
|
||||
m_potential_isolating_cb->setChecked(isolating_value == QLatin1String("true"));
|
||||
}
|
||||
}
|
||||
// English: Load the BOM exclusion status from the element information mapping
|
||||
if (m_exclude_from_bom_cb) {
|
||||
QString exclude_bom_value = element_info.value(QStringLiteral("exclude_from_bom")).toString();
|
||||
m_exclude_from_bom_cb->setChecked(exclude_bom_value == QLatin1String("true"));
|
||||
}
|
||||
|
||||
if (m_live_edit) {
|
||||
@@ -258,14 +301,13 @@ DiagramContext ElementInfoWidget::currentInfo() const
|
||||
{
|
||||
DiagramContext info_;
|
||||
|
||||
for (const auto &eipw : qAsConst(m_eipw_list))
|
||||
for (const auto &eipw : std::as_const(m_eipw_list))
|
||||
{
|
||||
|
||||
//add value only if they're something to store
|
||||
//add value only if they're something to store
|
||||
if (!eipw->text().isEmpty())
|
||||
{
|
||||
QString txt{eipw->text()};
|
||||
//remove line feed and carriage return
|
||||
//remove line feed and carriage return
|
||||
txt.remove(QStringLiteral("\r"));
|
||||
txt.remove(QStringLiteral("\n"));
|
||||
info_.addValue(eipw->key(), txt);
|
||||
@@ -275,10 +317,17 @@ DiagramContext ElementInfoWidget::currentInfo() const
|
||||
// Save the auto numbering lock status
|
||||
if (m_element->elementData().m_type == ElementData::Terminal) {
|
||||
info_.addValue(QStringLiteral("auto_num_locked"), ui->m_auto_num_locked_cb->isChecked() ? QStringLiteral("true") : QStringLiteral("false"));
|
||||
|
||||
if (m_potential_isolating_cb) {
|
||||
info_.addValue(QStringLiteral("potential_isolating"), m_potential_isolating_cb->isChecked() ? QStringLiteral("true") : QStringLiteral("false"));
|
||||
}
|
||||
}
|
||||
|
||||
if (m_exclude_from_bom_cb) {
|
||||
info_.addValue(QStringLiteral("exclude_from_bom"), m_exclude_from_bom_cb->isChecked() ? QStringLiteral("true") : QStringLiteral("false"));
|
||||
}
|
||||
return info_;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementInfoWidget::firstActivated
|
||||
Slot activated when this widget is show.
|
||||
|
||||
@@ -27,6 +27,7 @@ class Element;
|
||||
class QUndoCommand;
|
||||
class ElementInfoPartWidget;
|
||||
class ChangeElementInformationCommand;
|
||||
class QCheckBox;
|
||||
|
||||
namespace Ui {
|
||||
class ElementInfoWidget;
|
||||
@@ -71,6 +72,8 @@ class ElementInfoWidget : public AbstractElementPropertiesEditorWidget
|
||||
private:
|
||||
Ui::ElementInfoWidget *ui;
|
||||
QList <ElementInfoPartWidget *> m_eipw_list;
|
||||
QCheckBox *m_potential_isolating_cb = nullptr;
|
||||
QCheckBox *m_exclude_from_bom_cb = nullptr;
|
||||
bool m_first_activation;
|
||||
bool m_ui_builded = false;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "linksingleelementwidget.h"
|
||||
#include "contactgroupselectiondialog.h"
|
||||
#include "../qetgraphicsitem/masterelement.h"
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
#include "../diagram.h"
|
||||
@@ -24,10 +25,12 @@
|
||||
#include "../elementprovider.h"
|
||||
#include "../undocommand/linkelementcommand.h"
|
||||
#include "../qetinformation.h"
|
||||
|
||||
#include "../qetproject.h"
|
||||
#include "../ui_linksingleelementwidget.h"
|
||||
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QInputDialog>
|
||||
|
||||
|
||||
/**
|
||||
@brief LinkSingleElementWidget::LinkSingleElementWidget
|
||||
@@ -175,6 +178,7 @@ void LinkSingleElementWidget::apply()
|
||||
m_unlink = false;
|
||||
m_element_to_link = nullptr;
|
||||
m_pending_qtwi = nullptr;
|
||||
m_pending_group_index = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,8 +192,12 @@ QUndoCommand *LinkSingleElementWidget::associatedUndo() const
|
||||
|
||||
if (m_element_to_link || m_unlink)
|
||||
{
|
||||
if (m_element_to_link)
|
||||
if (m_element_to_link) {
|
||||
undo->setLink(m_element_to_link);
|
||||
if (m_pending_group_index >= 0) {
|
||||
undo->setGroupIndex(m_pending_group_index);
|
||||
}
|
||||
}
|
||||
else if (m_unlink)
|
||||
undo->unlinkAll();
|
||||
|
||||
@@ -386,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);
|
||||
}
|
||||
@@ -529,9 +544,86 @@ 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;
|
||||
|
||||
//If linking a slave to a master with contact groups, show group selection dialog
|
||||
if (m_element->linkType() == Element::Slave
|
||||
&& m_element_to_link
|
||||
&& m_element_to_link->linkType() == Element::Master)
|
||||
{
|
||||
// Check if this is a PLC master
|
||||
if (m_element_to_link->elementData().m_master_type == ElementData::PLC)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(m_live_edit)
|
||||
{
|
||||
apply();
|
||||
@@ -552,7 +644,7 @@ void LinkSingleElementWidget::linkTriggered()
|
||||
Qt::NoBrush));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (int i=0 ; i<6 ; i++)
|
||||
{
|
||||
m_qtwi_at_context_menu->setBackground(i,
|
||||
@@ -561,7 +653,7 @@ void LinkSingleElementWidget::linkTriggered()
|
||||
}
|
||||
m_pending_qtwi = m_qtwi_at_context_menu;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "abstractelementpropertieseditorwidget.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QDialog>
|
||||
#include <QComboBox>
|
||||
|
||||
class QTreeWidgetItem;
|
||||
class Element;
|
||||
@@ -101,6 +103,8 @@ class LinkSingleElementWidget : public AbstractElementPropertiesEditorWidget
|
||||
Element *m_showed_element = nullptr,
|
||||
*m_element_to_link = nullptr;
|
||||
|
||||
int m_pending_group_index = -1;
|
||||
|
||||
QMenu *m_context_menu{nullptr};
|
||||
QAction *m_link_action{nullptr},
|
||||
*m_show_qtwi{nullptr},
|
||||
|
||||
@@ -16,16 +16,30 @@
|
||||
* along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "masterpropertieswidget.h"
|
||||
|
||||
#include "contactgroupselectiondialog.h"
|
||||
#include "../qetproject.h"
|
||||
#include "../diagram.h"
|
||||
#include "../diagramposition.h"
|
||||
#include "../elementprovider.h"
|
||||
#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>
|
||||
|
||||
|
||||
/**
|
||||
* @brief MasterPropertiesWidget::MasterPropertiesWidget
|
||||
@@ -143,7 +157,7 @@ void MasterPropertiesWidget::setElement(Element *element)
|
||||
disconnect(m_project, SIGNAL(diagramRemoved(QETProject*,Diagram*)),
|
||||
this, SLOT(diagramWasdeletedFromProject()));
|
||||
|
||||
if(Q_LIKELY(element->diagram() && element->diagram()->project()))
|
||||
if(Q_LIKELY(element->diagram() && element->diagram()->project()))
|
||||
{
|
||||
m_project = element->diagram()->project();
|
||||
connect(m_project, SIGNAL(diagramRemoved(QETProject*,Diagram*)),
|
||||
@@ -157,7 +171,7 @@ void MasterPropertiesWidget::setElement(Element *element)
|
||||
disconnect(m_element.data(), &Element::linkedElementChanged,
|
||||
this, &MasterPropertiesWidget::updateUi);
|
||||
|
||||
m_element = element;
|
||||
m_element = element;
|
||||
connect(m_element.data(), &Element::linkedElementChanged,
|
||||
this, &MasterPropertiesWidget::updateUi);
|
||||
|
||||
@@ -176,6 +190,8 @@ void MasterPropertiesWidget::apply()
|
||||
{
|
||||
if (QUndoCommand *undo = associatedUndo())
|
||||
m_element -> diagram() -> undoStack().push(undo);
|
||||
|
||||
m_pending_group_indices.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,8 +240,23 @@ QUndoCommand* MasterPropertiesWidget::associatedUndo() const
|
||||
if (to_link.isEmpty())
|
||||
undo->unlinkAll();
|
||||
else
|
||||
{
|
||||
undo->setLink(to_link);
|
||||
|
||||
//Pass group indices for newly linked slaves
|
||||
if (!m_pending_group_indices.isEmpty())
|
||||
{
|
||||
QMap<Element*, int> indices;
|
||||
for (Element *slave : to_link)
|
||||
{
|
||||
if (m_pending_group_indices.contains(slave))
|
||||
indices[slave] = m_pending_group_indices.value(slave);
|
||||
}
|
||||
if (!indices.isEmpty())
|
||||
undo->setGroupIndices(indices);
|
||||
}
|
||||
}
|
||||
|
||||
return undo;
|
||||
}
|
||||
|
||||
@@ -241,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
|
||||
@@ -375,6 +315,38 @@ void MasterPropertiesWidget::on_link_button_clicked()
|
||||
QTreeWidgetItem *qtwi = ui->m_free_tree_widget->currentItem();
|
||||
if (qtwi)
|
||||
{
|
||||
Element *slave_elmt = m_qtwi_hash.value(qtwi);
|
||||
|
||||
//If master has contact groups, show group selection dialog
|
||||
const auto &groups = m_element->elementData().m_slave_contact_groups;
|
||||
if (!groups.isEmpty() && slave_elmt)
|
||||
{
|
||||
// Collect already-used group indices from the master
|
||||
QSet<int> used_indices;
|
||||
for (Element *linked : m_element->linkedElements()) {
|
||||
int idx = m_element->groupIndexForElement(linked);
|
||||
if (idx >= 0) {
|
||||
used_indices.insert(idx);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't mark the current slave as used (it might be relinked)
|
||||
if (slave_elmt->linkedElements().contains(m_element)) {
|
||||
int current_idx = m_element->groupIndexForElement(slave_elmt);
|
||||
if (current_idx >= 0) {
|
||||
used_indices.remove(current_idx);
|
||||
}
|
||||
}
|
||||
|
||||
ContactGroupSelectionDialog dlg(groups, used_indices,
|
||||
slave_elmt->elementData(), this);
|
||||
if (dlg.exec() == QDialog::Accepted && dlg.selectedIndex() >= 0) {
|
||||
m_pending_group_indices[slave_elmt] = dlg.selectedIndex();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ui->m_free_tree_widget->takeTopLevelItem(
|
||||
ui->m_free_tree_widget->indexOfTopLevelItem(qtwi));
|
||||
ui->m_link_tree_widget->insertTopLevelItem(0, qtwi);
|
||||
@@ -497,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));
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include <QWidget>
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
|
||||
#include "abstractelementpropertieseditorwidget.h"
|
||||
|
||||
@@ -30,6 +31,10 @@ class Diagram;
|
||||
class QTreeWidgetItem;
|
||||
class QMenu;
|
||||
class QAction;
|
||||
class QTableWidget;
|
||||
class QSpinBox;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
|
||||
namespace Ui {
|
||||
class MasterPropertiesWidget;
|
||||
@@ -74,9 +79,21 @@ 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;
|
||||
QMap <Element *, int> m_pending_group_indices;
|
||||
QTreeWidgetItem *m_qtwi_at_context_menu = nullptr;
|
||||
QPointer <Element> m_showed_element;
|
||||
QETProject *m_project;
|
||||
@@ -86,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
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
#include "multipastedialog.h"
|
||||
|
||||
#include "../qetproject.h"
|
||||
#include "../conductorautonumerotation.h"
|
||||
#include "../diagram.h"
|
||||
#include "../diagramcommands.h"
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
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 "kautosavefile.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QLockFile>
|
||||
#include <QRandomGenerator>
|
||||
#include <QStandardPaths>
|
||||
#include <QTextStream>
|
||||
|
||||
namespace {
|
||||
|
||||
const auto autosaveSuffix = QStringLiteral(".qetautosave");
|
||||
|
||||
QString staleFilesDir()
|
||||
{
|
||||
auto data_dir = QStandardPaths::writableLocation(
|
||||
QStandardPaths::AppDataLocation);
|
||||
while (data_dir.endsWith(QLatin1Char('/'))) {
|
||||
data_dir.chop(1);
|
||||
}
|
||||
if (data_dir.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return data_dir + QDir::separator() + QStringLiteral("autosave");
|
||||
}
|
||||
|
||||
QString metadataFileName(const QString &autosave_file_name)
|
||||
{
|
||||
return autosave_file_name + QStringLiteral(".path");
|
||||
}
|
||||
|
||||
QString lockFileName(const QString &autosave_file_name)
|
||||
{
|
||||
return autosave_file_name + QStringLiteral(".lock");
|
||||
}
|
||||
|
||||
QUrl normalizeManagedFile(const QUrl &url)
|
||||
{
|
||||
if (url.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (url.isLocalFile() || url.scheme().isEmpty()) {
|
||||
const auto path = url.isLocalFile() ? url.toLocalFile() : url.path();
|
||||
QUrl normalized;
|
||||
normalized.setPath(QDir::cleanPath(QFileInfo(path).absoluteFilePath()));
|
||||
return normalized;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
QString storedManagedFile(const QUrl &url)
|
||||
{
|
||||
if (url.isLocalFile() || url.scheme().isEmpty()) {
|
||||
return url.path();
|
||||
}
|
||||
|
||||
return url.toString(QUrl::FullyEncoded);
|
||||
}
|
||||
|
||||
QUrl managedFileFromStorage(const QString &stored_path)
|
||||
{
|
||||
if (!stored_path.startsWith(QLatin1Char('/'))) {
|
||||
return QUrl(stored_path);
|
||||
}
|
||||
|
||||
QUrl url;
|
||||
url.setPath(QDir::cleanPath(stored_path));
|
||||
return url;
|
||||
}
|
||||
|
||||
bool writeManagedFileMetadata(const QString &autosave_file_name, const QUrl &managed_file)
|
||||
{
|
||||
QFile metadata_file(metadataFileName(autosave_file_name));
|
||||
if (!metadata_file.open(QIODevice::WriteOnly
|
||||
| QIODevice::Truncate
|
||||
| QIODevice::Text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream stream(&metadata_file);
|
||||
stream << storedManagedFile(managed_file) << '\n';
|
||||
stream.flush();
|
||||
return metadata_file.error() == QFile::NoError;
|
||||
}
|
||||
|
||||
QUrl readManagedFileMetadata(const QString &autosave_file_name)
|
||||
{
|
||||
QFile metadata_file(metadataFileName(autosave_file_name));
|
||||
if (!metadata_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QTextStream stream(&metadata_file);
|
||||
const auto stored_path = stream.readLine().trimmed();
|
||||
if (stored_path.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return normalizeManagedFile(managedFileFromStorage(stored_path));
|
||||
}
|
||||
|
||||
QString autosaveFileName(const QUrl &managed_file)
|
||||
{
|
||||
const auto stored_path = storedManagedFile(managed_file);
|
||||
const auto digest = QCryptographicHash::hash(
|
||||
stored_path.toUtf8(),
|
||||
QCryptographicHash::Sha256).toHex().left(16);
|
||||
|
||||
auto basename = QFileInfo(managed_file.path()).fileName();
|
||||
if (basename.isEmpty()) {
|
||||
basename = QStringLiteral("autosave");
|
||||
}
|
||||
|
||||
auto encoded_basename = QString::fromLatin1(
|
||||
QUrl::toPercentEncoding(basename));
|
||||
if (encoded_basename.size() > 80) {
|
||||
encoded_basename.truncate(80);
|
||||
}
|
||||
|
||||
const auto random = QString::number(
|
||||
QRandomGenerator::global()->generate64(), 16);
|
||||
|
||||
return QStringLiteral("%1_%2_%3%4").arg(
|
||||
encoded_basename,
|
||||
QString::fromLatin1(digest),
|
||||
random,
|
||||
autosaveSuffix);
|
||||
}
|
||||
|
||||
QStringList findAllStaleFiles(const QString &application_name)
|
||||
{
|
||||
Q_UNUSED(application_name)
|
||||
|
||||
const auto dir_path = staleFilesDir();
|
||||
if (dir_path.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QDir dir(dir_path);
|
||||
const auto entries = dir.entryList(
|
||||
{QStringLiteral("*") + autosaveSuffix},
|
||||
QDir::Files);
|
||||
|
||||
QStringList files;
|
||||
for (const auto &entry : entries) {
|
||||
files << dir.absoluteFilePath(entry);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
bool autosaveFileIsRecoverable(const QString &autosave_file_name)
|
||||
{
|
||||
QLockFile lock(lockFileName(autosave_file_name));
|
||||
lock.setStaleLockTime(60 * 1000);
|
||||
if (!lock.tryLock()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
KAutoSaveFile::KAutoSaveFile(const QUrl &filename, QObject *parent) :
|
||||
QFile{parent}
|
||||
{
|
||||
setManagedFile(filename);
|
||||
}
|
||||
|
||||
KAutoSaveFile::KAutoSaveFile(QObject *parent) :
|
||||
QFile{parent}
|
||||
{
|
||||
}
|
||||
|
||||
KAutoSaveFile::~KAutoSaveFile()
|
||||
{
|
||||
releaseLock();
|
||||
}
|
||||
|
||||
QUrl KAutoSaveFile::managedFile() const
|
||||
{
|
||||
return m_managed_file;
|
||||
}
|
||||
|
||||
void KAutoSaveFile::setManagedFile(const QUrl &filename)
|
||||
{
|
||||
releaseLock();
|
||||
|
||||
m_managed_file = normalizeManagedFile(filename);
|
||||
m_managed_file_name_changed = true;
|
||||
setFileName({});
|
||||
}
|
||||
|
||||
void KAutoSaveFile::releaseLock()
|
||||
{
|
||||
if (m_lock && m_lock->isLocked()) {
|
||||
const auto autosave_file_name = fileName();
|
||||
m_lock.reset();
|
||||
|
||||
if (!autosave_file_name.isEmpty()) {
|
||||
QFile::remove(metadataFileName(autosave_file_name));
|
||||
remove();
|
||||
}
|
||||
} else {
|
||||
m_lock.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool KAutoSaveFile::open(OpenMode openmode)
|
||||
{
|
||||
if (m_managed_file.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_managed_file_name_changed) {
|
||||
const auto stale_dir = staleFilesDir();
|
||||
if (stale_dir.isEmpty() || !QDir().mkpath(stale_dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setFileName(QDir(stale_dir).absoluteFilePath(
|
||||
autosaveFileName(m_managed_file)));
|
||||
if (!writeManagedFileMetadata(fileName(), m_managed_file)) {
|
||||
setFileName({});
|
||||
return false;
|
||||
}
|
||||
|
||||
m_managed_file_name_changed = false;
|
||||
}
|
||||
|
||||
if (!QFile::open(openmode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_lock) {
|
||||
m_lock = std::make_unique<QLockFile>(lockFileName(fileName()));
|
||||
m_lock->setStaleLockTime(60 * 1000);
|
||||
}
|
||||
|
||||
if (m_lock->isLocked() || m_lock->tryLock()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<KAutoSaveFile *> KAutoSaveFile::staleFiles(
|
||||
const QUrl &url,
|
||||
const QString &applicationName)
|
||||
{
|
||||
const auto managed_file_filter = normalizeManagedFile(url);
|
||||
QList<KAutoSaveFile *> stale_files;
|
||||
|
||||
for (const auto &file : findAllStaleFiles(applicationName)) {
|
||||
const auto managed_file = readManagedFileMetadata(file);
|
||||
if (managed_file.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!managed_file_filter.isEmpty()
|
||||
&& managed_file != managed_file_filter) {
|
||||
continue;
|
||||
}
|
||||
if (!autosaveFileIsRecoverable(file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto *stale_file = new KAutoSaveFile(managed_file);
|
||||
stale_file->setFileName(file);
|
||||
stale_file->m_managed_file_name_changed = false;
|
||||
stale_files << stale_file;
|
||||
}
|
||||
|
||||
return stale_files;
|
||||
}
|
||||
|
||||
QList<KAutoSaveFile *> KAutoSaveFile::allStaleFiles(const QString &applicationName)
|
||||
{
|
||||
return staleFiles({}, applicationName);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
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 QET_KAUTOSAVEFILE_H
|
||||
#define QET_KAUTOSAVEFILE_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QList>
|
||||
#include <QUrl>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QLockFile;
|
||||
|
||||
/**
|
||||
Small Qt-only replacement for the KAutoSaveFile API used by QET.
|
||||
|
||||
It stores crash-recovery files below the application data autosave folder
|
||||
and protects each recovery file with QLockFile. The original managed file
|
||||
path is kept in a sidecar file so allStaleFiles() can present recoverable
|
||||
projects on the next startup.
|
||||
*/
|
||||
class KAutoSaveFile : public QFile
|
||||
{
|
||||
public:
|
||||
explicit KAutoSaveFile(const QUrl &filename, QObject *parent = nullptr);
|
||||
explicit KAutoSaveFile(QObject *parent = nullptr);
|
||||
~KAutoSaveFile() override;
|
||||
|
||||
QUrl managedFile() const;
|
||||
void setManagedFile(const QUrl &filename);
|
||||
virtual void releaseLock();
|
||||
bool open(OpenMode openmode) override;
|
||||
|
||||
static QList<KAutoSaveFile *> staleFiles(
|
||||
const QUrl &url,
|
||||
const QString &applicationName = QString());
|
||||
static QList<KAutoSaveFile *> allStaleFiles(
|
||||
const QString &applicationName = QString());
|
||||
|
||||
private:
|
||||
QUrl m_managed_file;
|
||||
std::unique_ptr<QLockFile> m_lock;
|
||||
bool m_managed_file_name_changed = false;
|
||||
};
|
||||
|
||||
#endif // QET_KAUTOSAVEFILE_H
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
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 "kcolorbutton.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QPalette>
|
||||
|
||||
namespace {
|
||||
QColor fallbackColor()
|
||||
{
|
||||
return QPalette{}.color(QPalette::Button);
|
||||
}
|
||||
}
|
||||
|
||||
KColorButton::KColorButton(QWidget *parent) :
|
||||
QPushButton{parent},
|
||||
m_color{fallbackColor()}
|
||||
{
|
||||
connect(this, &QPushButton::clicked, this, &KColorButton::chooseColor);
|
||||
updateButton();
|
||||
}
|
||||
|
||||
QColor KColorButton::color() const
|
||||
{
|
||||
return m_color;
|
||||
}
|
||||
|
||||
void KColorButton::setColor(const QColor &color)
|
||||
{
|
||||
m_color = color.isValid() ? color : fallbackColor();
|
||||
updateButton();
|
||||
}
|
||||
|
||||
void KColorButton::chooseColor()
|
||||
{
|
||||
const auto selected = QColorDialog::getColor(m_color, this);
|
||||
if (!selected.isValid() || selected == m_color) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_color = selected;
|
||||
updateButton();
|
||||
emit changed(m_color);
|
||||
}
|
||||
|
||||
void KColorButton::updateButton()
|
||||
{
|
||||
setText(m_color.name());
|
||||
|
||||
auto pal = palette();
|
||||
pal.setColor(QPalette::Button, m_color);
|
||||
setAutoFillBackground(true);
|
||||
setPalette(pal);
|
||||
update();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 QET_KCOLORBUTTON_H
|
||||
#define QET_KCOLORBUTTON_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QPushButton>
|
||||
|
||||
class KColorButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit KColorButton(QWidget *parent = nullptr);
|
||||
|
||||
QColor color() const;
|
||||
|
||||
public slots:
|
||||
void setColor(const QColor &color);
|
||||
|
||||
signals:
|
||||
void changed(const QColor &color);
|
||||
|
||||
private slots:
|
||||
void chooseColor();
|
||||
|
||||
private:
|
||||
void updateButton();
|
||||
|
||||
QColor m_color;
|
||||
};
|
||||
|
||||
#endif // QET_KCOLORBUTTON_H
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 "kcolorcombo.h"
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
KColorCombo::KColorCombo(QWidget *parent) :
|
||||
QComboBox{parent}
|
||||
{
|
||||
connect(
|
||||
this,
|
||||
QOverload<int>::of(&QComboBox::activated),
|
||||
this,
|
||||
[this](int index) {
|
||||
emit activated(itemData(index).value<QColor>());
|
||||
});
|
||||
}
|
||||
|
||||
void KColorCombo::setColors(const QList<QColor> &colors)
|
||||
{
|
||||
clear();
|
||||
for (const auto &color : colors) {
|
||||
addItem(color.name(), color);
|
||||
}
|
||||
}
|
||||
|
||||
QColor KColorCombo::color(int index) const
|
||||
{
|
||||
if (index < 0 || index >= count()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return itemData(index).value<QColor>();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
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 QET_KCOLORCOMBO_H
|
||||
#define QET_KCOLORCOMBO_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QComboBox>
|
||||
|
||||
class KColorCombo : public QComboBox
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit KColorCombo(QWidget *parent = nullptr);
|
||||
|
||||
void setColors(const QList<QColor> &colors);
|
||||
QColor color(int index) const;
|
||||
|
||||
signals:
|
||||
void activated(const QColor &color);
|
||||
};
|
||||
|
||||
#endif // QET_KCOLORCOMBO_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
|
||||
@@ -188,8 +188,11 @@ class LinkReportPotentialSelector : public AbstractPotentialSelector
|
||||
//### END PRIVATE CLASS ###//
|
||||
|
||||
|
||||
ConductorProperties PotentialSelectorDialog::chosenProperties(QList<ConductorProperties> list, QWidget *widget)
|
||||
ConductorProperties PotentialSelectorDialog::chosenProperties(QList<ConductorProperties> list, QWidget *widget, bool *cancelled)
|
||||
{
|
||||
if (cancelled)
|
||||
*cancelled = false;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return ConductorProperties() ;
|
||||
} else if (list.size() == 1) {
|
||||
@@ -222,11 +225,25 @@ ConductorProperties PotentialSelectorDialog::chosenProperties(QList<ConductorPro
|
||||
layout.addWidget(b);
|
||||
H.insert(b, cp);
|
||||
}
|
||||
QDialogButtonBox *button_box = new QDialogButtonBox(QDialogButtonBox::Ok, &dialog);
|
||||
|
||||
// Pre-select the first entry: without this, accepting the dialog without
|
||||
// ever touching a radio button silently returned blank properties too,
|
||||
// the same failure mode as the missing Cancel button below.
|
||||
if (!H.isEmpty())
|
||||
H.constBegin().key()->setChecked(true);
|
||||
|
||||
QDialogButtonBox *button_box = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
|
||||
layout.addWidget(button_box);
|
||||
connect(button_box, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
|
||||
connect(button_box, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
|
||||
|
||||
if (dialog.exec() != QDialog::Accepted) {
|
||||
if (cancelled)
|
||||
*cancelled = true;
|
||||
return ConductorProperties();
|
||||
}
|
||||
|
||||
dialog.exec();
|
||||
for (QRadioButton *b : H.keys()) {
|
||||
if(b->isChecked()) {
|
||||
return H.value(b);
|
||||
|
||||
@@ -64,7 +64,11 @@ namespace Ui {
|
||||
|
||||
the static function chosenProperties,
|
||||
open a dialog who ask user to make a choice between the given
|
||||
properties
|
||||
properties. If the dialog is cancelled (Cancel button, Escape, or the
|
||||
window's close button) and @a cancelled is non-null, *cancelled is set
|
||||
to true and an empty ConductorProperties() is returned; callers that
|
||||
care about a real cancellation (as opposed to "no properties to choose
|
||||
from") should check it rather than relying on the returned value alone.
|
||||
*/
|
||||
class PotentialSelectorDialog : public QDialog
|
||||
{
|
||||
@@ -73,7 +77,8 @@ class PotentialSelectorDialog : public QDialog
|
||||
public:
|
||||
static ConductorProperties chosenProperties(
|
||||
QList<ConductorProperties> list,
|
||||
QWidget *parent = nullptr);
|
||||
QWidget *parent = nullptr,
|
||||
bool *cancelled = nullptr);
|
||||
|
||||
public:
|
||||
explicit PotentialSelectorDialog(
|
||||
|
||||
@@ -55,7 +55,7 @@ Veuillez choisir les propriétées à appliquer au nouveau potentiel.</string>
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -63,7 +63,13 @@ ProjectPropertiesDialog::~ProjectPropertiesDialog ()
|
||||
*/
|
||||
void ProjectPropertiesDialog::exec()
|
||||
{
|
||||
m_properties_dialog->setWindowModality(Qt::WindowModal);
|
||||
// ApplicationModal (not WindowModal) so no other window — including other
|
||||
// MDI subwindows — can dispatch events while the dialog holds raw
|
||||
// QETProject* pointers in its config pages. WindowModal only blocks the
|
||||
// parent ProjectView; the rest of the MDI area stays live, which allowed
|
||||
// new_project / close_project to replace the project under the dialog
|
||||
// and cause a SIGSEGV. See issue #527.
|
||||
m_properties_dialog->setWindowModality(Qt::ApplicationModal);
|
||||
m_properties_dialog -> exec();
|
||||
}
|
||||
|
||||
|
||||
@@ -504,7 +504,7 @@ void ShapeGraphicsItemPropertiesWidget::setUpEditConnection()
|
||||
|
||||
void ShapeGraphicsItemPropertiesWidget::clearEditConnection()
|
||||
{
|
||||
for (const auto &c : qAsConst(m_edit_connection)) {
|
||||
for (const auto &c : std::as_const(m_edit_connection)) {
|
||||
disconnect(c);
|
||||
}
|
||||
m_edit_connection.clear();
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "ui_titleblockpropertieswidget.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QSet>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
@@ -162,7 +163,11 @@ void TitleBlockPropertiesWidget::setProperties(
|
||||
}
|
||||
ui -> m_tbt_cb -> setCurrentIndex(index);
|
||||
|
||||
m_dcw -> setContext(properties.context);
|
||||
// Show the saved custom values, plus any of the template's custom variables
|
||||
// that aren't defined yet, so the user only fills in the missing ones (#271).
|
||||
DiagramContext context = properties.context;
|
||||
addTemplateVariables(context, index);
|
||||
m_dcw -> setContext(context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -435,12 +440,15 @@ void TitleBlockPropertiesWidget::updateTemplateList()
|
||||
}
|
||||
|
||||
/**
|
||||
@brief TitleBlockPropertiesWidget::changeCurrentTitleBlockTemplate
|
||||
Load the additional field of title block "text"
|
||||
@brief TitleBlockPropertiesWidget::templateForIndex
|
||||
@param index : index in the collection-type map (= the template combo index)
|
||||
@return the TitleBlockTemplate currently selected for that collection, or
|
||||
nullptr.
|
||||
*/
|
||||
void TitleBlockPropertiesWidget::changeCurrentTitleBlockTemplate(int index)
|
||||
TitleBlockTemplate *TitleBlockPropertiesWidget::templateForIndex(int index) const
|
||||
{
|
||||
m_dcw -> clear();
|
||||
if (index < 0 || index >= m_map_index_to_collection_type.count())
|
||||
return nullptr;
|
||||
|
||||
QET::QetCollection qc = m_map_index_to_collection_type.at(index);
|
||||
TitleBlockTemplatesCollection *collection = nullptr;
|
||||
@@ -448,21 +456,55 @@ void TitleBlockPropertiesWidget::changeCurrentTitleBlockTemplate(int index)
|
||||
if (c -> collection() == qc)
|
||||
collection = c;
|
||||
|
||||
if (!collection) return;
|
||||
if (!collection) return nullptr;
|
||||
return collection -> getTemplate(ui -> m_tbt_cb -> currentText());
|
||||
}
|
||||
|
||||
// get template
|
||||
TitleBlockTemplate *tpl = collection -> getTemplate(ui -> m_tbt_cb -> currentText());
|
||||
if(tpl != nullptr) {
|
||||
// get all template fields
|
||||
QStringList fields = tpl -> listOfVariables();
|
||||
// set fields to additional_fields_ widget
|
||||
DiagramContext templateContext;
|
||||
for(int i =0; i<fields.count(); i++)
|
||||
templateContext.addValue(fields.at(i), "");
|
||||
m_dcw -> setContext(templateContext);
|
||||
/**
|
||||
@brief TitleBlockPropertiesWidget::addTemplateVariables
|
||||
Add to @p context every CUSTOM variable used by the currently selected
|
||||
template that is not already present, with an empty value — so the user
|
||||
only has to fill in the values instead of declaring the variables (#271).
|
||||
The standard fields (title, author, date, …) are handled by their own
|
||||
widgets and are skipped. Existing values in @p context are preserved.
|
||||
*/
|
||||
void TitleBlockPropertiesWidget::addTemplateVariables(
|
||||
DiagramContext &context, int index) const
|
||||
{
|
||||
TitleBlockTemplate *tpl = templateForIndex(index);
|
||||
if (!tpl) return;
|
||||
|
||||
// Variables rendered from the dedicated standard-field widgets; they must
|
||||
// not appear in the "Custom" tab.
|
||||
static const QSet<QString> reserved {
|
||||
QStringLiteral("author"), QStringLiteral("date"),
|
||||
QStringLiteral("title"), QStringLiteral("filename"),
|
||||
QStringLiteral("plant"), QStringLiteral("locmach"),
|
||||
QStringLiteral("indexrev"), QStringLiteral("version"),
|
||||
QStringLiteral("folio"), QStringLiteral("folio-id"),
|
||||
QStringLiteral("folio-total"), QStringLiteral("auto_page_num"),
|
||||
QStringLiteral("previous-folio-num"), QStringLiteral("next-folio-num")
|
||||
};
|
||||
|
||||
const QStringList variables = tpl -> listOfVariables();
|
||||
for (const QString &name : variables) {
|
||||
if (name.isEmpty() || reserved.contains(name)) continue;
|
||||
if (!context.contains(name)) context.addValue(name, "");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief TitleBlockPropertiesWidget::changeCurrentTitleBlockTemplate
|
||||
When the user picks a template, append its missing custom variables to the
|
||||
"Custom" tab while keeping the values already entered (#271).
|
||||
*/
|
||||
void TitleBlockPropertiesWidget::changeCurrentTitleBlockTemplate(int index)
|
||||
{
|
||||
DiagramContext context = m_dcw -> context();
|
||||
addTemplateVariables(context, index);
|
||||
m_dcw -> setContext(context);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief TitleBlockPropertiesWidget::on_m_date_now_pb_clicked
|
||||
Set the date to current date
|
||||
|
||||
@@ -30,6 +30,7 @@ class NumerotationContext;
|
||||
class QETProject;
|
||||
class QMenu;
|
||||
class TitleBlockTemplatesCollection;
|
||||
class TitleBlockTemplate;
|
||||
|
||||
namespace Ui {
|
||||
class TitleBlockPropertiesWidget;
|
||||
@@ -77,6 +78,8 @@ class TitleBlockPropertiesWidget : public QWidget
|
||||
void initDialog(const bool ¤t_date, QETProject *project);
|
||||
int getIndexFor (const QString &tbt_name,
|
||||
const QET::QetCollection collection) const;
|
||||
TitleBlockTemplate *templateForIndex (int index) const;
|
||||
void addTemplateVariables (DiagramContext &context, int index) const;
|
||||
|
||||
private slots:
|
||||
void editCurrentTitleBlockTemplate();
|
||||
|
||||
@@ -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