Merge pull request #545 from Kellermorph/terminal-name

Implement slave contact groups — label transfer, terminal assignment and UI fixes
This commit is contained in:
Laurent Trinques
2026-07-18 08:00:23 +02:00
committed by GitHub
29 changed files with 1305 additions and 31 deletions
+2
View File
@@ -623,6 +623,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/ui/borderpropertieswidget.h ${QET_DIR}/sources/ui/borderpropertieswidget.h
${QET_DIR}/sources/ui/compositetexteditdialog.cpp ${QET_DIR}/sources/ui/compositetexteditdialog.cpp
${QET_DIR}/sources/ui/compositetexteditdialog.h ${QET_DIR}/sources/ui/compositetexteditdialog.h
${QET_DIR}/sources/ui/contactgroupselectiondialog.cpp
${QET_DIR}/sources/ui/contactgroupselectiondialog.h
${QET_DIR}/sources/ui/conductorpropertiesdialog.cpp ${QET_DIR}/sources/ui/conductorpropertiesdialog.cpp
${QET_DIR}/sources/ui/conductorpropertiesdialog.h ${QET_DIR}/sources/ui/conductorpropertiesdialog.h
${QET_DIR}/sources/ui/conductorpropertieswidget.cpp ${QET_DIR}/sources/ui/conductorpropertieswidget.cpp
+3
View File
@@ -85,11 +85,14 @@ ElementData ElementScene::elementData() {
void ElementScene::setElementData(ElementData data) void ElementScene::setElementData(ElementData data)
{ {
bool emit_ = m_element_data.m_informations != data.m_informations; bool emit_ = m_element_data.m_informations != data.m_informations;
bool type_changed = m_element_data.m_type != data.m_type;
m_element_data = data; m_element_data = data;
if (emit_) if (emit_)
emit elementInfoChanged(); emit elementInfoChanged();
if (type_changed)
emit elementTypeChanged();
} }
/** /**
+2
View File
@@ -177,6 +177,8 @@ class ElementScene : public QGraphicsScene
/// Signal emitted when need zoomFit /// Signal emitted when need zoomFit
void needZoomFit(); void needZoomFit();
void elementInfoChanged(); void elementInfoChanged();
/// Signal emitted when the element type changes
void elementTypeChanged();
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(ElementScene::ItemOptions) Q_DECLARE_OPERATORS_FOR_FLAGS(ElementScene::ItemOptions)
@@ -460,6 +460,22 @@ void PartTerminal::setLabelColor(QColor color)
emit labelColorChanged(); emit labelColorChanged();
} }
void PartTerminal::setUseMasterLabel(bool use)
{
if (d->m_use_master_label == use) return;
d->m_use_master_label = use;
update();
emit useMasterLabelChanged();
}
void PartTerminal::setMasterLabelIndex(int index)
{
if (d->m_master_label_index == index) return;
d->m_master_label_index = index;
update();
emit masterLabelIndexChanged();
}
/** /**
Updates the position of the second point according to the position Updates the position of the second point according to the position
and orientation of the terminal. and orientation of the terminal.
@@ -42,6 +42,8 @@ class PartTerminal : public CustomElementGraphicPart
Q_PROPERTY(Qt::Alignment label_valignment READ labelVAlignment WRITE setLabelVAlignment) Q_PROPERTY(Qt::Alignment label_valignment READ labelVAlignment WRITE setLabelVAlignment)
Q_PROPERTY(bool label_frame READ labelFrame WRITE setLabelFrame) Q_PROPERTY(bool label_frame READ labelFrame WRITE setLabelFrame)
Q_PROPERTY(QColor label_color READ labelColor WRITE setLabelColor) Q_PROPERTY(QColor label_color READ labelColor WRITE setLabelColor)
Q_PROPERTY(bool use_master_label READ useMasterLabel WRITE setUseMasterLabel)
Q_PROPERTY(int master_label_index READ masterLabelIndex WRITE setMasterLabelIndex)
public: public:
// constructors, destructor // constructors, destructor
@@ -62,6 +64,8 @@ class PartTerminal : public CustomElementGraphicPart
void labelVAlignmentChanged(); void labelVAlignmentChanged();
void labelFrameChanged(); void labelFrameChanged();
void labelColorChanged(); void labelColorChanged();
void useMasterLabelChanged();
void masterLabelIndexChanged();
// methods // methods
public: public:
@@ -130,6 +134,12 @@ class PartTerminal : public CustomElementGraphicPart
QColor labelColor() const { return d->m_label_color; } QColor labelColor() const { return d->m_label_color; }
void setLabelColor(QColor color); void setLabelColor(QColor color);
bool useMasterLabel() const { return d->m_use_master_label; }
void setUseMasterLabel(bool use);
int masterLabelIndex() const { return d->m_master_label_index; }
void setMasterLabelIndex(int index);
void setNewUuid(); void setNewUuid();
QRectF labelRect() const; QRectF labelRect() const;
@@ -23,6 +23,11 @@
#include "../../qetinformation.h" #include "../../qetinformation.h"
#include <QItemDelegate> #include <QItemDelegate>
#include <QComboBox>
#include <QSpinBox>
#include <QSignalBlocker>
#include <QTableWidgetItem>
#include <QHeaderView>
/** /**
@brief The EditorDelegate class @brief The EditorDelegate class
@@ -108,6 +113,13 @@ void ElementPropertiesEditorWidget::upDateInterface()
ui->max_slaves_spinbox->setEnabled(true); ui->max_slaves_spinbox->setEnabled(true);
ui->max_slaves_spinbox->setValue(m_data.m_max_slaves); ui->max_slaves_spinbox->setValue(m_data.m_max_slaves);
} }
// Slave contact groups checkbox
ui->m_slave_groups_checkbox->setChecked(m_data.m_slave_contact_groups_enabled);
ui->m_slave_groups_table->setEnabled(m_data.m_slave_contact_groups_enabled);
if (m_data.m_slave_contact_groups_enabled) {
populateSlaveGroupsTable();
}
} else if (m_data.m_type == ElementData::Terminal) { } else if (m_data.m_type == ElementData::Terminal) {
ui->m_terminal_type_cb->setCurrentIndex( ui->m_terminal_type_cb->setCurrentIndex(
ui->m_terminal_type_cb->findData( ui->m_terminal_type_cb->findData(
@@ -168,6 +180,11 @@ void ElementPropertiesEditorWidget::setUpInterface()
// NEU: Checkbox mit der Zahlenbox verbinden (Aktivieren/Deaktivieren) // NEU: Checkbox mit der Zahlenbox verbinden (Aktivieren/Deaktivieren)
connect(ui->max_slaves_checkbox, SIGNAL(toggled(bool)), ui->max_slaves_spinbox, SLOT(setEnabled(bool))); connect(ui->max_slaves_checkbox, SIGNAL(toggled(bool)), ui->max_slaves_spinbox, SLOT(setEnabled(bool)));
connect(ui->max_slaves_spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this](int) {
if (ui->m_slave_groups_checkbox->isChecked()) {
populateSlaveGroupsTable();
}
});
populateTree(); populateTree();
} }
@@ -241,7 +258,7 @@ void ElementPropertiesEditorWidget::on_m_buttonBox_accepted()
m_data.m_slave_type = ui->m_type_cb->currentData().value<ElementData::SlaveType>(); m_data.m_slave_type = ui->m_type_cb->currentData().value<ElementData::SlaveType>();
m_data.m_contact_count = ui->m_number_ctc->value(); m_data.m_contact_count = ui->m_number_ctc->value();
} }
else if (m_data.m_type == ElementData::Master) { else if (m_data.m_type == ElementData::Master) {
m_data.m_master_type = ui->m_master_type_cb->currentData().value<ElementData::MasterType>(); m_data.m_master_type = ui->m_master_type_cb->currentData().value<ElementData::MasterType>();
//If the checkbox is checked, save the number; otherwise, -1 (infinity) //If the checkbox is checked, save the number; otherwise, -1 (infinity)
@@ -250,6 +267,8 @@ void ElementPropertiesEditorWidget::on_m_buttonBox_accepted()
} else { } else {
m_data.m_max_slaves = -1; m_data.m_max_slaves = -1;
} }
readSlaveGroupsFromTable();
} }
else if (m_data.m_type == ElementData::Terminal) else if (m_data.m_type == ElementData::Terminal)
{ {
@@ -299,3 +318,217 @@ void ElementPropertiesEditorWidget::on_m_base_type_cb_currentIndexChanged(int in
updateTree(); updateTree();
} }
/**
* @brief ElementPropertiesEditorWidget::on_max_slaves_checkbox_toggled
* When max_slaves checkbox is unchecked, also uncheck slave groups checkbox
*/
void ElementPropertiesEditorWidget::on_max_slaves_checkbox_toggled(bool checked)
{
if (!checked && ui->m_slave_groups_checkbox->isChecked()) {
ui->m_slave_groups_checkbox->setChecked(false);
}
}
/**
* @brief ElementPropertiesEditorWidget::on_m_slave_groups_checkbox_toggled
* When slave groups checkbox is toggled, enable/disable the table
* Also ensure max_slaves checkbox is checked when enabling groups
*/
void ElementPropertiesEditorWidget::on_m_slave_groups_checkbox_toggled(bool checked)
{
ui->m_slave_groups_table->setEnabled(checked);
if (checked && !ui->max_slaves_checkbox->isChecked()) {
ui->max_slaves_checkbox->setChecked(true);
}
if (checked) {
populateSlaveGroupsTable();
}
}
/**
* @brief ElementPropertiesEditorWidget::populateSlaveGroupsTable
* Fill the slave contact groups table from m_data
*/
void ElementPropertiesEditorWidget::populateSlaveGroupsTable()
{
QSignalBlocker blocker_table(ui->m_slave_groups_table);
QSignalBlocker blocker_spinbox(ui->max_slaves_spinbox);
ui->m_slave_groups_table->setRowCount(0);
int row_count = ui->max_slaves_checkbox->isChecked()
? ui->max_slaves_spinbox->value() : 0;
// If we have existing groups, use their count (up to max_slaves)
int existing_groups = m_data.m_slave_contact_groups.size();
// Adjust the groups list to match the spinbox value
while (m_data.m_slave_contact_groups.size() < row_count) {
ElementData::SlaveContactGroup group;
group.type = ElementData::NO;
group.subtype = ElementData::SSimple;
group.contactCount = 1;
group.terminalCount = 2;
m_data.m_slave_contact_groups.append(group);
}
while (m_data.m_slave_contact_groups.size() > row_count) {
m_data.m_slave_contact_groups.removeLast();
}
// Find max terminal count across all groups to determine T columns
int max_tc = 0;
for (const auto &g : m_data.m_slave_contact_groups) {
max_tc = qMax(max_tc, g.terminalCount);
}
max_tc = qMax(max_tc, 2); // at least T1, T2
// Set up 4 fixed columns + max_tc label columns
int total_cols = 4 + max_tc;
ui->m_slave_groups_table->setColumnCount(total_cols);
// Set T column headers
for (int t = 0; t < max_tc; ++t) {
ui->m_slave_groups_table->setHorizontalHeaderItem(
4 + t, new QTableWidgetItem(tr("T%1").arg(t + 1)));
}
// Set column widths for readability
ui->m_slave_groups_table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
ui->m_slave_groups_table->horizontalHeader()->setMinimumSectionSize(60);
ui->m_slave_groups_table->setColumnWidth(0, 180); // Type
ui->m_slave_groups_table->setColumnWidth(1, 180); // Subtype
ui->m_slave_groups_table->setColumnWidth(2, 80); // Contacts
ui->m_slave_groups_table->setColumnWidth(3, 80); // Bornes
ui->m_slave_groups_table->setRowCount(m_data.m_slave_contact_groups.size());
for (int i = 0; i < m_data.m_slave_contact_groups.size(); ++i) {
auto &group = m_data.m_slave_contact_groups[i];
// Type column
auto *type_cb = new QComboBox(ui->m_slave_groups_table);
type_cb->addItem(tr("Normalement ouvert"), ElementData::NO);
type_cb->addItem(tr("Normalement fermé"), ElementData::NC);
type_cb->addItem(tr("Inverseur"), ElementData::SW);
type_cb->addItem(tr("Autre"), ElementData::Other);
type_cb->setCurrentIndex(type_cb->findData(group.type));
ui->m_slave_groups_table->setCellWidget(i, 0, type_cb);
// Subtype column
auto *subtype_cb = new QComboBox(ui->m_slave_groups_table);
subtype_cb->addItem(tr("Simple"), ElementData::SSimple);
subtype_cb->addItem(tr("Puissance"), ElementData::Power);
subtype_cb->addItem(tr("Temporisé travail"), ElementData::DelayOn);
subtype_cb->addItem(tr("Temporisé repos"), ElementData::DelayOff);
subtype_cb->addItem(tr("Temporisé travail & repos"), ElementData::delayOnOff);
subtype_cb->setCurrentIndex(subtype_cb->findData(group.subtype));
ui->m_slave_groups_table->setCellWidget(i, 1, subtype_cb);
// Contact count
auto *contact_ct = new QSpinBox(ui->m_slave_groups_table);
contact_ct->setMinimum(1);
contact_ct->setMaximum(20);
contact_ct->setValue(group.contactCount);
ui->m_slave_groups_table->setCellWidget(i, 2, contact_ct);
// Terminal count
auto *terminal_ct = new QSpinBox(ui->m_slave_groups_table);
terminal_ct->setMinimum(1);
terminal_ct->setMaximum(20);
terminal_ct->setValue(group.terminalCount);
ui->m_slave_groups_table->setCellWidget(i, 3, terminal_ct);
// When terminal count changes, rebuild the table to update T columns
connect(terminal_ct, QOverload<int>::of(&QSpinBox::valueChanged),
this, [this, terminal_ct, i](int val) {
if (i < m_data.m_slave_contact_groups.size()) {
m_data.m_slave_contact_groups[i].terminalCount = val;
readSlaveGroupsFromTable();
populateSlaveGroupsTable();
}
});
// Auto-generate labels if empty
QStringList labels = group.labels;
while (labels.size() < group.terminalCount) {
labels << tr("T%1").arg(labels.size() + 1);
}
// Fill T1..TN columns
for (int t = 0; t < max_tc; ++t) {
auto *item = new QTableWidgetItem(
t < labels.size() ? labels.at(t) : QString());
if (t >= group.terminalCount) {
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
item->setBackground(QBrush(QColor(240, 240, 240)));
}
ui->m_slave_groups_table->setItem(i, 4 + t, item);
}
// Store updated labels back
group.labels = labels;
}
}
/**
* @brief ElementPropertiesEditorWidget::readSlaveGroupsFromTable
* Read the slave contact groups from the table back into m_data
*/
void ElementPropertiesEditorWidget::readSlaveGroupsFromTable()
{
m_data.m_slave_contact_groups.clear();
if (!ui->m_slave_groups_checkbox->isChecked()) {
m_data.m_slave_contact_groups_enabled = false;
return;
}
m_data.m_slave_contact_groups_enabled = true;
for (int i = 0; i < ui->m_slave_groups_table->rowCount(); ++i) {
ElementData::SlaveContactGroup group;
auto *type_cb = qobject_cast<QComboBox *>(
ui->m_slave_groups_table->cellWidget(i, 0));
if (type_cb) {
group.type = type_cb->currentData().value<ElementData::SlaveState>();
}
auto *subtype_cb = qobject_cast<QComboBox *>(
ui->m_slave_groups_table->cellWidget(i, 1));
if (subtype_cb) {
group.subtype = subtype_cb->currentData().value<ElementData::SlaveType>();
}
auto *contact_ct = qobject_cast<QSpinBox *>(
ui->m_slave_groups_table->cellWidget(i, 2));
if (contact_ct) {
group.contactCount = contact_ct->value();
}
auto *terminal_ct = qobject_cast<QSpinBox *>(
ui->m_slave_groups_table->cellWidget(i, 3));
if (terminal_ct) {
group.terminalCount = terminal_ct->value();
}
// Read labels from T1..TN columns
for (int t = 0; t < group.terminalCount; ++t) {
int col = 4 + t;
if (col < ui->m_slave_groups_table->columnCount()) {
auto *item = ui->m_slave_groups_table->item(i, col);
if (item && !item->text().isEmpty()) {
group.labels.append(item->text());
} else {
group.labels << tr("T%1").arg(t + 1);
}
} else {
group.labels << tr("T%1").arg(t + 1);
}
}
m_data.m_slave_contact_groups.append(group);
}
}
@@ -49,11 +49,15 @@ class ElementPropertiesEditorWidget : public QDialog
void setUpInterface(); void setUpInterface();
void updateTree(); void updateTree();
void populateTree(); void populateTree();
void populateSlaveGroupsTable();
void readSlaveGroupsFromTable();
//SLOTS //SLOTS
private slots: private slots:
void on_m_buttonBox_accepted(); void on_m_buttonBox_accepted();
void on_m_base_type_cb_currentIndexChanged(int index); void on_m_base_type_cb_currentIndexChanged(int index);
void on_m_slave_groups_checkbox_toggled(bool checked);
void on_max_slaves_checkbox_toggled(bool checked);
//ATTRIBUTES //ATTRIBUTES
private: private:
@@ -121,6 +121,55 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0">
<widget class="QCheckBox" name="m_slave_groups_checkbox">
<property name="text">
<string>Définir les éléments esclave</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QTableWidget" name="m_slave_groups_table">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>150</height>
</size>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="columnCount">
<number>4</number>
</property>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
<column>
<property name="text">
<string>Contact</string>
</property>
</column>
<column>
<property name="text">
<string>Nb. contacts</string>
</property>
</column>
<column>
<property name="text">
<string>Nb. bornes</string>
</property>
</column>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
+6
View File
@@ -1192,6 +1192,12 @@ void QETElementEditor::initGui()
updateInformations(); updateInformations();
fillPartsList(); fillPartsList();
// When the element type changes, update the terminal editor master label visibility
connect(m_elmt_scene, &ElementScene::elementTypeChanged, this, [this]() {
auto *te = static_cast<TerminalEditor *>(m_editors["terminal"]);
if (te) te->refreshMasterLabelVisibility();
});
statusBar()->showMessage(tr("Éditeur d'éléments", "status bar message")); statusBar()->showMessage(tr("Éditeur d'éléments", "status bar message"));
} }
+128
View File
@@ -25,6 +25,8 @@
#include <QColorDialog> #include <QColorDialog>
#include <QFontDialog> #include <QFontDialog>
#include "../elementscene.h"
#include "qetelementeditor.h"
/** /**
* @brief TerminalEditor::TerminalEditor * @brief TerminalEditor::TerminalEditor
@@ -101,6 +103,26 @@ void TerminalEditor::updateForm()
ui->m_text_props_gb->setEnabled(m_part->showName()); ui->m_text_props_gb->setEnabled(m_part->showName());
// Update master label fields
bool is_slave = updateMasterLabelVisibility();
if (is_slave) {
PartTerminal *pt = m_part;
if (pt) {
ui->m_use_master_label_cb->setChecked(pt->useMasterLabel());
ui->m_master_label_cb->setEnabled(pt->useMasterLabel());
ui->m_name_le->setEnabled(!pt->useMasterLabel());
int idx = ui->m_master_label_cb->findData(pt->masterLabelIndex());
if (idx >= 0) {
ui->m_master_label_cb->setCurrentIndex(idx);
}
// Show T-label in name field when master label is active
if (pt->useMasterLabel()) {
int label_idx = pt->masterLabelIndex();
ui->m_name_le->setText(tr("T%1").arg(label_idx + 1));
}
}
}
activeConnections(true); activeConnections(true);
} }
@@ -165,6 +187,14 @@ void TerminalEditor::init()
ui->m_type_cb->addItem(tr("Commun (contact SW)"), TerminalData::Common); ui->m_type_cb->addItem(tr("Commun (contact SW)"), TerminalData::Common);
ui->m_text_props_gb->setEnabled(false); ui->m_text_props_gb->setEnabled(false);
// Populate master label dropdown (T1-T20)
for (int i = 1; i <= 20; ++i) {
ui->m_master_label_cb->addItem(tr("T%1").arg(i), i - 1);
}
// Check if parent element is a Slave to show/hide master label group
updateMasterLabelVisibility();
} }
/** /**
@@ -427,6 +457,10 @@ void TerminalEditor::activeConnections(bool active)
this, &TerminalEditor::labelAlignClicked); this, &TerminalEditor::labelAlignClicked);
m_editor_connections << connect(ui->m_label_frame_cb, &QCheckBox::toggled, m_editor_connections << connect(ui->m_label_frame_cb, &QCheckBox::toggled,
this, &TerminalEditor::labelFrameEdited); this, &TerminalEditor::labelFrameEdited);
m_editor_connections << connect(ui->m_use_master_label_cb, &QCheckBox::toggled,
this, &TerminalEditor::useMasterLabelEdited);
m_editor_connections << connect(ui->m_master_label_cb, QOverload<int>::of(&QComboBox::activated),
this, &TerminalEditor::masterLabelIndexEdited);
} else { } else {
for (auto const & con : std::as_const(m_editor_connections)) { for (auto const & con : std::as_const(m_editor_connections)) {
QObject::disconnect(con); QObject::disconnect(con);
@@ -452,6 +486,8 @@ void TerminalEditor::activeChangeConnections(bool active)
m_change_connections << connect(m_part, &PartTerminal::labelVAlignmentChanged, this, &TerminalEditor::updateForm); m_change_connections << connect(m_part, &PartTerminal::labelVAlignmentChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelFrameChanged, this, &TerminalEditor::updateForm); m_change_connections << connect(m_part, &PartTerminal::labelFrameChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelColorChanged, this, &TerminalEditor::updateForm); m_change_connections << connect(m_part, &PartTerminal::labelColorChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::useMasterLabelChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::masterLabelIndexChanged, this, &TerminalEditor::updateForm);
} else { } else {
for (auto &con : m_change_connections) { for (auto &con : m_change_connections) {
QObject::disconnect(con); QObject::disconnect(con);
@@ -459,3 +495,95 @@ void TerminalEditor::activeChangeConnections(bool active)
m_change_connections.clear(); m_change_connections.clear();
} }
} }
void TerminalEditor::useMasterLabelEdited()
{
if (m_locked) return;
m_locked = true;
bool use = ui->m_use_master_label_cb->isChecked();
ui->m_master_label_cb->setEnabled(use);
QSignalBlocker name_blocker(ui->m_name_le);
if (m_part->useMasterLabel() != use) {
auto undo = new QPropertyUndoCommand(m_part, "use_master_label",
m_part->useMasterLabel(), use);
undo->setText(tr("Modifier l'étiquette du maître"));
undoStack().push(undo);
}
if (use) {
int idx = ui->m_master_label_cb->currentData().toInt();
QString t_label = tr("T%1").arg(idx + 1);
ui->m_name_le->setText(t_label);
ui->m_name_le->setEnabled(false);
if (m_part->terminalName() != t_label) {
auto undo = new QPropertyUndoCommand(m_part, "terminal_name",
m_part->terminalName(), t_label);
undo->setText(tr("Modifier le nom de la borne"));
undoStack().push(undo);
}
} else {
ui->m_name_le->setEnabled(true);
if (!m_part->terminalName().isEmpty()) {
auto undo = new QPropertyUndoCommand(m_part, "terminal_name",
m_part->terminalName(), QString());
undo->setText(tr("Modifier le nom de la borne"));
undoStack().push(undo);
}
ui->m_name_le->clear();
}
m_locked = false;
}
void TerminalEditor::masterLabelIndexEdited()
{
if (m_locked) return;
m_locked = true;
int idx = ui->m_master_label_cb->currentData().toInt();
if (m_part->masterLabelIndex() != idx) {
auto undo = new QPropertyUndoCommand(m_part, "master_label_index",
m_part->masterLabelIndex(), idx);
undo->setText(tr("Modifier l'index de l'étiquette du maître"));
undoStack().push(undo);
}
if (ui->m_use_master_label_cb->isChecked()) {
QString t_label = tr("T%1").arg(idx + 1);
ui->m_name_le->setText(t_label);
if (m_part->terminalName() != t_label) {
auto undo = new QPropertyUndoCommand(m_part, "terminal_name",
m_part->terminalName(), t_label);
undo->setText(tr("Modifier le nom de la borne"));
undoStack().push(undo);
}
}
m_locked = false;
}
bool TerminalEditor::updateMasterLabelVisibility()
{
QETElementEditor *editor = elementEditor();
if (!editor || !editor->elementScene()) {
ui->m_master_label_gb->setVisible(false);
return false;
}
ElementData data = editor->elementScene()->elementData();
bool is_slave = (data.m_type == ElementData::Slave);
ui->m_master_label_gb->setVisible(is_slave);
return is_slave;
}
void TerminalEditor::refreshMasterLabelVisibility()
{
updateMasterLabelVisibility();
if (m_part) {
updateForm();
}
}
+9 -4
View File
@@ -49,6 +49,8 @@ class TerminalEditor : public ElementItemEditor
bool setPart(CustomElementPart *new_part) override; bool setPart(CustomElementPart *new_part) override;
CustomElementPart *currentPart() const override; CustomElementPart *currentPart() const override;
QList<CustomElementPart *> currentParts() const override {return QList<CustomElementPart *>();} QList<CustomElementPart *> currentParts() const override {return QList<CustomElementPart *>();}
public slots:
void refreshMasterLabelVisibility();
private: private:
void init(); void init();
@@ -62,10 +64,13 @@ class TerminalEditor : public ElementItemEditor
void labelSizeEdited(); void labelSizeEdited();
void labelRotationEdited(); void labelRotationEdited();
void labelAlignClicked(); void labelAlignClicked();
void labelFrameEdited(); void labelFrameEdited();
void labelColorClicked(); void labelColorClicked();
void activeConnections(bool active); void activeConnections(bool active);
void activeChangeConnections(bool active); void activeChangeConnections(bool active);
void useMasterLabelEdited();
void masterLabelIndexEdited();
bool updateMasterLabelVisibility();
private: private:
Ui::TerminalEditor *ui; Ui::TerminalEditor *ui;
+26
View File
@@ -214,6 +214,32 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="6" column="0" colspan="2">
<widget class="QGroupBox" name="m_master_label_gb">
<property name="title">
<string>Étiquette du maître</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QCheckBox" name="m_use_master_label_cb">
<property name="text">
<string>Reprendre du maître</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="m_master_label_cb">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>
+99 -13
View File
@@ -49,18 +49,7 @@ bool ElementData::fromXml(const QDomElement &xml_element)
return false; return false;
} }
// --- OUR DEBUG BLOCK STARTS HERE --- m_type = typeFromString(xml_element.attribute(QStringLiteral("link_type"), QStringLiteral("simple")));
// We retrieve the string from the XML and store it temporarily
QString raw_type_string = xml_element.attribute(QStringLiteral("link_type"), QStringLiteral("simple"));
qDebug() << "\n=== NEW COMPONENT IS LOADING ===";
qDebug() << "[XML Parser] Raw “link_type” string from the .elmt file:" << raw_type_string;
// Now well pass it on to your translation function
m_type = typeFromString(raw_type_string);
qDebug() << "[XML Parser] Translated ElementData type:" << typeToString(m_type);
// --- THIS IS WHERE OUR DEBUG BLOCK ENDS ---
kindInfoFromXml(xml_element); kindInfoFromXml(xml_element);
m_informations.fromXml(xml_element.firstChildElement(QStringLiteral("elementInformations")), m_informations.fromXml(xml_element.firstChildElement(QStringLiteral("elementInformations")),
@@ -98,6 +87,31 @@ QDomElement ElementData::kindInfoToXml(QDomDocument &document)
returned_elmt.appendChild(xml_max_slaves); returned_elmt.appendChild(xml_max_slaves);
} }
// Save slave contact groups if enabled
if (m_slave_contact_groups_enabled) {
auto xml_groups = document.createElement(QStringLiteral("slaveContactGroups"));
for (const auto &group : m_slave_contact_groups) {
auto xml_group = document.createElement(QStringLiteral("group"));
xml_group.setAttribute(QStringLiteral("type"),
slaveStateToString(group.type));
xml_group.setAttribute(QStringLiteral("subtype"),
slaveTypeToString(group.subtype));
xml_group.setAttribute(QStringLiteral("contactCount"),
group.contactCount);
xml_group.setAttribute(QStringLiteral("terminalCount"),
group.terminalCount);
for (const auto &label : group.labels) {
auto xml_label = document.createElement(QStringLiteral("label"));
auto label_txt = document.createTextNode(label);
xml_label.appendChild(label_txt);
xml_group.appendChild(xml_label);
}
xml_groups.appendChild(xml_group);
}
returned_elmt.appendChild(xml_groups);
}
} }
else if (m_type == ElementData::Slave) else if (m_type == ElementData::Slave)
{ {
@@ -247,6 +261,15 @@ bool ElementData::operator==(const ElementData &data) const
if(data.m_master_type != m_master_type) { if(data.m_master_type != m_master_type) {
return false; return false;
} }
if (data.m_max_slaves != m_max_slaves) {
return false;
}
if (data.m_slave_contact_groups_enabled != m_slave_contact_groups_enabled) {
return false;
}
if (data.m_slave_contact_groups != m_slave_contact_groups) {
return false;
}
} }
else if (data.m_type == ElementData::Slave) { else if (data.m_type == ElementData::Slave) {
if (data.m_slave_state != m_slave_state || if (data.m_slave_state != m_slave_state ||
@@ -591,7 +614,7 @@ void ElementData::kindInfoFromXml(const QDomElement &xml_element)
m_max_slaves = dom_elmt.text().toInt(); m_max_slaves = dom_elmt.text().toInt();
} }
} }
else if (m_type == ElementData::Slave ) { else if (m_type == ElementData::Slave) {
if (name == QLatin1String("type")) { if (name == QLatin1String("type")) {
m_slave_type = slaveTypeFromString(dom_elmt.text()); m_slave_type = slaveTypeFromString(dom_elmt.text());
} else if (name == QLatin1String("state")) { } else if (name == QLatin1String("state")) {
@@ -606,8 +629,71 @@ void ElementData::kindInfoFromXml(const QDomElement &xml_element)
} else if (name == QLatin1String("function")) { } else if (name == QLatin1String("function")) {
m_terminal_function = terminalFunctionFromString(dom_elmt.text()); m_terminal_function = terminalFunctionFromString(dom_elmt.text());
} }
}
}
}
// Parse slave contact groups for Master elements
if (m_type == ElementData::Master) {
auto xml_kind = xml_element.firstChildElement(QStringLiteral("kindInformations"));
auto xml_groups = xml_kind.firstChildElement(QStringLiteral("slaveContactGroups"));
if (!xml_groups.isNull()) {
m_slave_contact_groups_enabled = true;
auto group_list = QETXML::findInDomElement(xml_groups, QStringLiteral("group"));
for (const auto &xml_group : group_list) {
SlaveContactGroup group;
group.type = slaveStateFromString(
xml_group.attribute(QStringLiteral("type"), QStringLiteral("NO")));
group.subtype = slaveTypeFromString(
xml_group.attribute(QStringLiteral("subtype"), QStringLiteral("simple")));
group.contactCount = xml_group.attribute(
QStringLiteral("contactCount"), QStringLiteral("1")).toInt();
group.terminalCount = xml_group.attribute(
QStringLiteral("terminalCount"), QStringLiteral("1")).toInt();
auto label_list = QETXML::findInDomElement(xml_group, QStringLiteral("label"));
for (const auto &xml_label : label_list) {
group.labels.append(xml_label.text());
}
m_slave_contact_groups.append(group);
} }
} }
} }
} }
/**
* @brief ElementData::slaveContactGroupTypeToString
* Convert a SlaveState to string for XML storage in slave contact groups.
* Maps: NO -> "NO", NC -> "NC", SW -> "SW", Other -> "Other"
*/
QString ElementData::slaveContactGroupTypeToString(ElementData::SlaveState type)
{
return slaveStateToString(type);
}
/**
* @brief ElementData::slaveContactGroupTypeFromString
* Convert a string from XML to SlaveState for slave contact groups.
*/
ElementData::SlaveState ElementData::slaveContactGroupTypeFromString(const QString &string)
{
return slaveStateFromString(string);
}
/**
* @brief ElementData::slaveContactGroupSubtypeToString
* Convert a SlaveType to string for XML storage in slave contact groups.
*/
QString ElementData::slaveContactGroupSubtypeToString(ElementData::SlaveType type)
{
return slaveTypeToString(type);
}
/**
* @brief ElementData::slaveContactGroupSubtypeFromString
* Convert a string from XML to SlaveType for slave contact groups.
*/
ElementData::SlaveType ElementData::slaveContactGroupSubtypeFromString(const QString &string)
{
return slaveTypeFromString(string);
}
+35
View File
@@ -22,6 +22,9 @@
#include "../diagramcontext.h" #include "../diagramcontext.h"
#include "../NameList/nameslist.h" #include "../NameList/nameslist.h"
#include <QStringList>
#include <QVector>
/** /**
* @brief The ElementData class * @brief The ElementData class
* WARNING * WARNING
@@ -86,6 +89,31 @@ class ElementData : public PropertiesInterface
}; };
Q_ENUM(TerminalFunction) Q_ENUM(TerminalFunction)
/**
* @brief The SlaveContactGroup struct
* Defines a contact group that a master element provides for slave elements.
* Each group specifies the type, subtype, number of displayed contacts,
* number of terminals, and the labels for each terminal (T1-T20).
*/
struct SlaveContactGroup {
SlaveState type = NO; ///< Contact type (NO/NC/SW/Other)
SlaveType subtype = SSimple; ///< Contact subtype (Simple/Power/Delay*)
int contactCount = 1; ///< Number of displayed contacts
int terminalCount = 1; ///< Number of terminals (Anschlüsse)
QStringList labels; ///< Terminal labels (T1-T20), max 20 entries
bool operator==(const SlaveContactGroup &other) const {
return type == other.type
&& subtype == other.subtype
&& contactCount == other.contactCount
&& terminalCount == other.terminalCount
&& labels == other.labels;
}
bool operator!=(const SlaveContactGroup &other) const {
return !(*this == other);
}
};
ElementData() {} ElementData() {}
~ElementData() override {} ~ElementData() override {}
@@ -130,12 +158,19 @@ class ElementData : public PropertiesInterface
static ElementData::TerminalFunction terminalFunctionFromString(const QString &string); static ElementData::TerminalFunction terminalFunctionFromString(const QString &string);
static QString translatedTerminalFunction(ElementData::TerminalFunction function); static QString translatedTerminalFunction(ElementData::TerminalFunction function);
static QString slaveContactGroupTypeToString(ElementData::SlaveState type);
static ElementData::SlaveState slaveContactGroupTypeFromString(const QString &string);
static QString slaveContactGroupSubtypeToString(ElementData::SlaveType type);
static ElementData::SlaveType slaveContactGroupSubtypeFromString(const QString &string);
// must be public, because this class is a private member // must be public, because this class is a private member
// of Element/ element editor and they must access this data // of Element/ element editor and they must access this data
ElementData::Type m_type = ElementData::Simple; ElementData::Type m_type = ElementData::Simple;
ElementData::MasterType m_master_type = ElementData::Coil; ElementData::MasterType m_master_type = ElementData::Coil;
int m_max_slaves{-1}; int m_max_slaves{-1};
bool m_slave_contact_groups_enabled{false}; ///< Whether slave contact groups table is active
QVector<SlaveContactGroup> m_slave_contact_groups; ///< Contact groups defined by master
ElementData::SlaveType m_slave_type = ElementData::SSimple; ElementData::SlaveType m_slave_type = ElementData::SSimple;
ElementData::SlaveState m_slave_state = ElementData::NO; ElementData::SlaveState m_slave_state = ElementData::NO;
+10
View File
@@ -125,6 +125,12 @@ QDomElement TerminalData::toXml(QDomDocument &xml_document) const
xml_element.setAttribute("label_color", m_label_color.name()); xml_element.setAttribute("label_color", m_label_color.name());
} }
// Save master label override settings
if (m_use_master_label) {
xml_element.setAttribute("use_master_label", "true");
xml_element.setAttribute("master_label_index", m_master_label_index);
}
return(xml_element); return(xml_element);
} }
@@ -197,6 +203,10 @@ bool TerminalData::fromXml (const QDomElement &xml_element)
m_label_color = QColor(color_str); m_label_color = QColor(color_str);
} }
// Read master label override settings
m_use_master_label = (xml_element.attribute("use_master_label") == QLatin1String("true"));
m_master_label_index = xml_element.attribute("master_label_index", "0").toInt();
return true; return true;
} }
+5
View File
@@ -134,6 +134,11 @@ class TerminalData : public PropertiesInterface
/// Color of the text label /// Color of the text label
QColor m_label_color = Qt::black; QColor m_label_color = Qt::black;
/// Whether this terminal uses a label from the master's contact group
bool m_use_master_label = false;
/// Index into the master's contact group labels (T1=0, T2=1, ..., T20=19)
int m_master_label_index = 0;
private: private:
QGraphicsObject* q{nullptr}; QGraphicsObject* q{nullptr};
}; };
+53 -3
View File
@@ -741,7 +741,9 @@ bool Element::fromXml(QDomElement &e,
QStringLiteral("links_uuids"), QStringLiteral("links_uuids"),
QStringLiteral("link_uuid")); QStringLiteral("link_uuid"));
foreach (QDomElement qdo, uuid_list) { foreach (QDomElement qdo, uuid_list) {
tmp_uuids_link << QUuid(qdo.attribute(QStringLiteral("uuid"))); QUuid uuid(qdo.attribute(QStringLiteral("uuid")));
int group_index = qdo.attribute(QStringLiteral("group_index"), QStringLiteral("-1")).toInt();
tmp_uuids_link << LinkInfo(uuid, group_index);
} }
//uuid of this element //uuid of this element
@@ -932,6 +934,13 @@ QDomElement Element::toXml(
QDomElement link_uuid = QDomElement link_uuid =
document.createElement(QStringLiteral("link_uuid")); document.createElement(QStringLiteral("link_uuid"));
link_uuid.setAttribute(QStringLiteral("uuid"), elmt->uuid().toString()); link_uuid.setAttribute(QStringLiteral("uuid"), elmt->uuid().toString());
// Save group index if assigned (for slave->master links)
int gi = m_group_index_map.value(elmt, -1);
if (gi >= 0) {
link_uuid.setAttribute(QStringLiteral("group_index"), gi);
}
links_uuids.appendChild(link_uuid); links_uuids.appendChild(link_uuid);
} }
element.appendChild(links_uuids); element.appendChild(links_uuids);
@@ -1267,8 +1276,21 @@ void Element::initLink(QETProject *prj)
if (tmp_uuids_link.isEmpty()) return; if (tmp_uuids_link.isEmpty()) return;
ElementProvider ep(prj); ElementProvider ep(prj);
foreach (Element *elmt, ep.fromUuids(tmp_uuids_link)) { QList<QUuid> uuids;
elmt->linkToElement(this); for (const auto &linkInfo : tmp_uuids_link) {
uuids.append(linkInfo.uuid);
}
QList<Element *> elements = ep.fromUuids(uuids);
for (int i = 0; i < tmp_uuids_link.size(); ++i) {
for (Element *elmt : elements) {
if (elmt->uuid() == tmp_uuids_link[i].uuid) {
elmt->linkToElement(this);
if (tmp_uuids_link[i].group_index >= 0) {
m_group_index_map[elmt] = tmp_uuids_link[i].group_index;
}
break;
}
}
} }
tmp_uuids_link.clear(); tmp_uuids_link.clear();
} }
@@ -1304,6 +1326,34 @@ QString Element::linkTypeToString() const
} }
} }
/**
* @brief Element::groupIndexForElement
* Returns the group index assigned to the given linked element.
* For slave elements, this indicates which contact group of the master
* this slave is assigned to.
* @param elmt the linked element to query
* @return group index, or -1 if not assigned
*/
int Element::groupIndexForElement(Element *elmt) const
{
return m_group_index_map.value(elmt, -1);
}
/**
* @brief Element::setGroupIndexForElement
* Sets the group index for a linked element.
* @param elmt the linked element
* @param index the group index to assign
*/
void Element::setGroupIndexForElement(Element *elmt, int index)
{
if (index >= 0) {
m_group_index_map[elmt] = index;
} else {
m_group_index_map.remove(elmt);
}
}
/** /**
@brief Element::setElementInformations @brief Element::setElementInformations
Set new information for this element. Set new information for this element.
+19 -1
View File
@@ -62,6 +62,20 @@ class Element : public QetGraphicsItem
Thumbnail = 64, Thumbnail = 64,
ConductorDefinition = 128}; ConductorDefinition = 128};
/**
* @brief The LinkInfo struct
* Stores link data for element connections.
* For slave elements, group_index indicates which contact group
* of the master this slave is assigned to (-1 = no group assigned).
*/
struct LinkInfo {
QUuid uuid;
int group_index = -1; ///< Index into master's slaveContactGroups, -1 = not assigned
LinkInfo() = default;
LinkInfo(const QUuid &u, int gi = -1) : uuid(u), group_index(gi) {}
};
Element(const ElementsLocation &location, Element(const ElementsLocation &location,
QGraphicsItem * = nullptr, QGraphicsItem * = nullptr,
int *state = nullptr, int *state = nullptr,
@@ -181,6 +195,9 @@ class Element : public QetGraphicsItem
virtual void initLink(QETProject *); virtual void initLink(QETProject *);
QList<Element *> linkedElements (); QList<Element *> linkedElements ();
int groupIndexForElement(Element *elmt) const;
void setGroupIndexForElement(Element *elmt, int index);
/** /**
* @brief linkType * @brief linkType
* use elementData function instead * use elementData function instead
@@ -228,7 +245,8 @@ class Element : public QetGraphicsItem
protected: protected:
//ATTRIBUTES related to linked element //ATTRIBUTES related to linked element
QList <Element *> connected_elements; QList <Element *> connected_elements;
QList <QUuid> tmp_uuids_link; QList <LinkInfo> tmp_uuids_link;
QHash <Element *, int> m_group_index_map; ///< Maps linked elements to their group index (for slave->master links)
QUuid m_uuid; QUuid m_uuid;
kind m_link_type = Element::Simple; kind m_link_type = Element::Simple;
+49 -6
View File
@@ -274,7 +274,8 @@ void Terminal::paint(
} }
// Draw label if show_name is enabled // Draw label if show_name is enabled
if (d->m_show_name && !d->m_name.isEmpty()) { const QString display_name = name();
if (d->m_show_name && !display_name.isEmpty()) {
painter->setRenderHint(QPainter::Antialiasing, true); painter->setRenderHint(QPainter::Antialiasing, true);
painter->setRenderHint(QPainter::TextAntialiasing, true); painter->setRenderHint(QPainter::TextAntialiasing, true);
painter->setFont(d->m_label_font); painter->setFont(d->m_label_font);
@@ -282,7 +283,7 @@ void Terminal::paint(
QPointF label_pos = d->m_pos + d->m_label_pos; QPointF label_pos = d->m_pos + d->m_label_pos;
QFontMetrics fm(d->m_label_font); QFontMetrics fm(d->m_label_font);
QSizeF text_size = fm.size(Qt::TextSingleLine, d->m_name); QSizeF text_size = fm.size(Qt::TextSingleLine, display_name);
if (!qFuzzyIsNull(d->m_label_rotation)) { if (!qFuzzyIsNull(d->m_label_rotation)) {
painter->save(); painter->save();
@@ -290,7 +291,7 @@ void Terminal::paint(
painter->rotate(d->m_label_rotation); painter->rotate(d->m_label_rotation);
QRectF text_rect(-text_size.width()/2.0, -text_size.height()/2.0, QRectF text_rect(-text_size.width()/2.0, -text_size.height()/2.0,
text_size.width(), text_size.height()); text_size.width(), text_size.height());
painter->drawText(text_rect, static_cast<int>(d->m_label_halignment | d->m_label_valignment), d->m_name); painter->drawText(text_rect, static_cast<int>(d->m_label_halignment | d->m_label_valignment), display_name);
painter->restore(); painter->restore();
} else { } else {
qreal dx = 0, dy = 0; qreal dx = 0, dy = 0;
@@ -306,7 +307,7 @@ void Terminal::paint(
if (d->m_label_frame) { if (d->m_label_frame) {
painter->drawRect(text_rect.adjusted(-1, -1, 1, 1)); painter->drawRect(text_rect.adjusted(-1, -1, 1, 1));
} }
painter->drawText(text_rect, static_cast<int>(Qt::AlignLeft | Qt::AlignTop), d->m_name); painter->drawText(text_rect, static_cast<int>(Qt::AlignLeft | Qt::AlignTop), display_name);
} }
} }
@@ -378,12 +379,13 @@ QLineF Terminal::HelpLine() const
@return Le rectangle (en precision flottante) delimitant la borne et ses alentours. @return Le rectangle (en precision flottante) delimitant la borne et ses alentours.
*/ */
QRectF Terminal::boundingRect() const { QRectF Terminal::boundingRect() const {
if (!d->m_show_name || d->m_name.isEmpty()) { const QString display_name = name();
if (!d->m_show_name || display_name.isEmpty()) {
return m_br; return m_br;
} }
QFontMetrics fm(d->m_label_font); QFontMetrics fm(d->m_label_font);
QSizeF text_size = fm.size(Qt::TextSingleLine, d->m_name); QSizeF text_size = fm.size(Qt::TextSingleLine, display_name);
QPointF label_pos = d->m_pos + d->m_label_pos; QPointF label_pos = d->m_pos + d->m_label_pos;
qreal dx = 0, dy = 0; qreal dx = 0, dy = 0;
@@ -808,6 +810,25 @@ QUuid Terminal::uuid() const
QString Terminal::name() const QString Terminal::name() const
{ {
if (d->m_use_master_label && parent_element_) {
// Find the master element in the slave's linked elements
for (Element *elmt : parent_element_->linkedElements()) {
if (elmt->linkType() == Element::Master) {
int group_idx = elmt->groupIndexForElement(parent_element_);
if (group_idx >= 0) {
const auto &groups = elmt->elementData().m_slave_contact_groups;
if (group_idx < groups.size()) {
int label_idx = d->m_master_label_index;
const QStringList &labels = groups.at(group_idx).labels;
if (label_idx >= 0 && label_idx < labels.size()) {
return labels.at(label_idx);
}
}
}
break;
}
}
}
return d->m_name; return d->m_name;
} }
@@ -820,6 +841,28 @@ TerminalData::Type Terminal::terminalType() const
return d->m_type; return d->m_type;
} }
/**
@brief Terminal::setUseMasterLabel
Set whether this terminal uses a label from the master's contact group
@param use true to use master label
*/
void Terminal::setUseMasterLabel(bool use)
{
d->m_use_master_label = use;
update();
}
/**
@brief Terminal::setMasterLabelIndex
Set the index into the master's contact group labels
@param index the label index (0-based)
*/
void Terminal::setMasterLabelIndex(int index)
{
d->m_master_label_index = index;
update();
}
/** /**
@brief Conductor::relatedPotentialTerminal @brief Conductor::relatedPotentialTerminal
Return terminal at the same potential from the same Return terminal at the same potential from the same
+4
View File
@@ -77,6 +77,10 @@ class Terminal : public QGraphicsObject
QUuid uuid () const; QUuid uuid () const;
QString name () const; QString name () const;
TerminalData::Type terminalType() const; TerminalData::Type terminalType() const;
bool useMasterLabel() const { return d->m_use_master_label; }
void setUseMasterLabel(bool use);
int masterLabelIndex() const { return d->m_master_label_index; }
void setMasterLabelIndex(int index);
QList<Conductor *> conductors() const; QList<Conductor *> conductors() const;
Qet::Orientation orientation() const; Qet::Orientation orientation() const;
+16
View File
@@ -1179,6 +1179,22 @@ ElementsLocation QETProject::importElement(ElementsLocation &location)
} }
//Erase the existing element, and use the newer instead //Erase the existing element, and use the newer instead
else if (action == QET::Erase) { else if (action == QET::Erase) {
// Warn if the new element introduces slave contact groups
QDomElement new_kind = location.xml().firstChildElement("kindInformations");
if (!new_kind.firstChildElement("slaveContactGroups").isNull()) {
QMessageBox::StandardButton answer = QMessageBox::warning(nullptr,
tr("Système de contacts modifié"),
tr("Le nouvel élément définit des groupes de contacts esclaves.\n"
"Les éléments esclaves existants ne seront pas automatiquement "
"assignés. Vous devrez relier manuellement les esclaves "
"et assigner les groupes de contacts.\n\n"
"Voulez-vous continuer ?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
if (answer == QMessageBox::No) {
return ElementsLocation();
}
}
ElementsLocation parent_loc = existing_location.parent(); ElementsLocation parent_loc = existing_location.parent();
return m_elements_collection->copy(location, parent_loc); return m_elements_collection->copy(location, parent_loc);
} }
+238
View File
@@ -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");
}
}
+61
View File
@@ -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
+37 -1
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>. along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "linksingleelementwidget.h" #include "linksingleelementwidget.h"
#include "contactgroupselectiondialog.h"
#include "../qetgraphicsitem/masterelement.h" #include "../qetgraphicsitem/masterelement.h"
#include "../qetgraphicsitem/conductor.h" #include "../qetgraphicsitem/conductor.h"
#include "../diagram.h" #include "../diagram.h"
@@ -29,6 +30,7 @@
#include <QTreeWidgetItem> #include <QTreeWidgetItem>
/** /**
@brief LinkSingleElementWidget::LinkSingleElementWidget @brief LinkSingleElementWidget::LinkSingleElementWidget
Default constructor Default constructor
@@ -175,6 +177,7 @@ void LinkSingleElementWidget::apply()
m_unlink = false; m_unlink = false;
m_element_to_link = nullptr; m_element_to_link = nullptr;
m_pending_qtwi = nullptr; m_pending_qtwi = nullptr;
m_pending_group_index = -1;
} }
/** /**
@@ -188,8 +191,12 @@ QUndoCommand *LinkSingleElementWidget::associatedUndo() const
if (m_element_to_link || m_unlink) if (m_element_to_link || m_unlink)
{ {
if (m_element_to_link) if (m_element_to_link) {
undo->setLink(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) else if (m_unlink)
undo->unlinkAll(); undo->unlinkAll();
@@ -531,6 +538,35 @@ void LinkSingleElementWidget::linkTriggered()
return; return;
m_element_to_link = m_qtwi_elmt_hash.value(m_qtwi_at_context_menu); 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)
{
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) if(m_live_edit)
{ {
+4
View File
@@ -22,6 +22,8 @@
#include "abstractelementpropertieseditorwidget.h" #include "abstractelementpropertieseditorwidget.h"
#include <QHash> #include <QHash>
#include <QDialog>
#include <QComboBox>
class QTreeWidgetItem; class QTreeWidgetItem;
class Element; class Element;
@@ -101,6 +103,8 @@ class LinkSingleElementWidget : public AbstractElementPropertiesEditorWidget
Element *m_showed_element = nullptr, Element *m_showed_element = nullptr,
*m_element_to_link = nullptr; *m_element_to_link = nullptr;
int m_pending_group_index = -1;
QMenu *m_context_menu{nullptr}; QMenu *m_context_menu{nullptr};
QAction *m_link_action{nullptr}, QAction *m_link_action{nullptr},
*m_show_qtwi{nullptr}, *m_show_qtwi{nullptr},
+51
View File
@@ -16,6 +16,7 @@
* along with QElectroTech. If not, see <http://www.gnu.org/licenses/>. * along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "masterpropertieswidget.h" #include "masterpropertieswidget.h"
#include "contactgroupselectiondialog.h"
#include "../qetproject.h" #include "../qetproject.h"
#include "../diagram.h" #include "../diagram.h"
#include "../diagramposition.h" #include "../diagramposition.h"
@@ -27,6 +28,7 @@
#include <QListWidgetItem> #include <QListWidgetItem>
#include <QMessageBox> #include <QMessageBox>
/** /**
* @brief MasterPropertiesWidget::MasterPropertiesWidget * @brief MasterPropertiesWidget::MasterPropertiesWidget
* Default constructor * Default constructor
@@ -176,6 +178,8 @@ void MasterPropertiesWidget::apply()
{ {
if (QUndoCommand *undo = associatedUndo()) if (QUndoCommand *undo = associatedUndo())
m_element -> diagram() -> undoStack().push(undo); m_element -> diagram() -> undoStack().push(undo);
m_pending_group_indices.clear();
} }
/** /**
@@ -224,8 +228,23 @@ QUndoCommand* MasterPropertiesWidget::associatedUndo() const
if (to_link.isEmpty()) if (to_link.isEmpty())
undo->unlinkAll(); undo->unlinkAll();
else else
{
undo->setLink(to_link); 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; return undo;
} }
@@ -375,6 +394,38 @@ void MasterPropertiesWidget::on_link_button_clicked()
QTreeWidgetItem *qtwi = ui->m_free_tree_widget->currentItem(); QTreeWidgetItem *qtwi = ui->m_free_tree_widget->currentItem();
if (qtwi) 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->takeTopLevelItem(
ui->m_free_tree_widget->indexOfTopLevelItem(qtwi)); ui->m_free_tree_widget->indexOfTopLevelItem(qtwi));
ui->m_link_tree_widget->insertTopLevelItem(0, qtwi); ui->m_link_tree_widget->insertTopLevelItem(0, qtwi);
+2
View File
@@ -20,6 +20,7 @@
#include <QWidget> #include <QWidget>
#include <QHash> #include <QHash>
#include <QMap>
#include "abstractelementpropertieseditorwidget.h" #include "abstractelementpropertieseditorwidget.h"
@@ -77,6 +78,7 @@ class MasterPropertiesWidget : public AbstractElementPropertiesEditorWidget
private: private:
Ui::MasterPropertiesWidget *ui; Ui::MasterPropertiesWidget *ui;
QHash <QTreeWidgetItem *, Element *> m_qtwi_hash; QHash <QTreeWidgetItem *, Element *> m_qtwi_hash;
QMap <Element *, int> m_pending_group_indices;
QTreeWidgetItem *m_qtwi_at_context_menu = nullptr; QTreeWidgetItem *m_qtwi_at_context_menu = nullptr;
QPointer <Element> m_showed_element; QPointer <Element> m_showed_element;
QETProject *m_project; QETProject *m_project;
+126
View File
@@ -21,6 +21,7 @@
#include "../diagram.h" #include "../diagram.h"
#include "../qetgraphicsitem/conductor.h" #include "../qetgraphicsitem/conductor.h"
#include "../qetgraphicsitem/element.h" #include "../qetgraphicsitem/element.h"
#include "../qetgraphicsitem/terminal.h"
#include "../ui/potentialselectordialog.h" #include "../ui/potentialselectordialog.h"
/** /**
@@ -176,6 +177,54 @@ void LinkElementCommand::unlinkAll()
void LinkElementCommand::undo() void LinkElementCommand::undo()
{ {
if(m_element->diagram()) m_element->diagram()->showMe(); if(m_element->diagram()) m_element->diagram()->showMe();
//Clear group index for slave elements when undoing
if (m_element->linkType() == Element::Slave)
{
int group_idx = m_group_index;
if (m_group_indices.contains(m_element))
group_idx = m_group_indices.value(m_element);
if (group_idx >= 0)
{
// Reset master labels on slave terminals
QList<Terminal *> slave_terms = m_element->terminals();
for (Terminal *t : slave_terms)
{
t->setUseMasterLabel(false);
t->setMasterLabelIndex(0);
}
foreach(Element *elmt, m_element->linkedElements())
{
if (elmt->linkType() == Element::Master)
{
elmt->setGroupIndexForElement(m_element, -1);
break;
}
}
}
}
else if (m_element->linkType() == Element::Master)
{
for (auto it = m_group_indices.constBegin(); it != m_group_indices.constEnd(); ++it)
{
Element *slave = it.key();
if (m_element->linkedElements().contains(slave))
{
// Reset master labels on slave terminals
QList<Terminal *> slave_terms = slave->terminals();
for (Terminal *t : slave_terms)
{
t->setUseMasterLabel(false);
t->setMasterLabelIndex(0);
}
m_element->setGroupIndexForElement(slave, -1);
}
}
}
makeLink(m_linked_before); makeLink(m_linked_before);
QUndoCommand::undo(); QUndoCommand::undo();
} }
@@ -275,6 +324,83 @@ void LinkElementCommand::makeLink(const QList<Element *> &element_list)
foreach(Element *elmt, element_list) foreach(Element *elmt, element_list)
m_element->linkToElement(elmt); m_element->linkToElement(elmt);
//Set group index for slave-master links
if (m_element->linkType() == Element::Slave)
{
int group_idx = m_group_index;
//Check if we have a per-slave group index
if (m_group_indices.contains(m_element))
group_idx = m_group_indices.value(m_element);
if (group_idx >= 0)
{
foreach(Element *elmt, element_list)
{
if (elmt->linkType() == Element::Master)
{
elmt->setGroupIndexForElement(m_element, group_idx);
// Set master labels on slave terminals
const auto &groups = elmt->elementData().m_slave_contact_groups;
if (group_idx < groups.size())
{
const QStringList &labels = groups.at(group_idx).labels;
QList<Terminal *> slave_terms = m_element->terminals();
// Sort terminals by name (T1, T2, T3...) to match label order
std::sort(slave_terms.begin(), slave_terms.end(),
[](Terminal *a, Terminal *b) {
return a->name() < b->name();
});
for (int i = 0; i < slave_terms.size(); ++i)
{
if (i < labels.size())
{
slave_terms.at(i)->setUseMasterLabel(true);
slave_terms.at(i)->setMasterLabelIndex(i);
}
}
}
break;
}
}
}
}
else if (m_element->linkType() == Element::Master)
{
//For master linking to slaves, set group indices for each slave
for (auto it = m_group_indices.constBegin(); it != m_group_indices.constEnd(); ++it)
{
Element *slave = it.key();
int group_idx = it.value();
if (group_idx >= 0 && element_list.contains(slave))
{
m_element->setGroupIndexForElement(slave, group_idx);
// Set master labels on slave terminals
const auto &groups = m_element->elementData().m_slave_contact_groups;
if (group_idx < groups.size())
{
const QStringList &labels = groups.at(group_idx).labels;
QList<Terminal *> slave_terms = slave->terminals();
// Sort terminals by name (T1, T2, T3...) to match label order
std::sort(slave_terms.begin(), slave_terms.end(),
[](Terminal *a, Terminal *b) {
return a->name() < b->name();
});
for (int i = 0; i < slave_terms.size(); ++i)
{
if (i < labels.size())
{
slave_terms.at(i)->setUseMasterLabel(true);
slave_terms.at(i)->setMasterLabelIndex(i);
}
}
}
}
}
}
/* At this point there may be unwanted linked elements to m_element. /* At this point there may be unwanted linked elements to m_element.
* We must unlink it. * We must unlink it.
* Elements from element_list are wanted so we compare element_list * Elements from element_list are wanted so we compare element_list
+5
View File
@@ -19,6 +19,7 @@
#define LINKELEMENTCOMMAND_H #define LINKELEMENTCOMMAND_H
#include <QUndoCommand> #include <QUndoCommand>
#include <QMap>
class Element; class Element;
@@ -42,6 +43,8 @@ class LinkElementCommand : public QUndoCommand
void setLink (Element *element_); void setLink (Element *element_);
void unlink (QList<Element *> element_list); void unlink (QList<Element *> element_list);
void unlinkAll (); void unlinkAll ();
void setGroupIndex(int index) { m_group_index = index; }
void setGroupIndices(const QMap<Element*, int> &indices) { m_group_indices = indices; }
void undo() override; void undo() override;
void redo() override; void redo() override;
@@ -53,6 +56,8 @@ class LinkElementCommand : public QUndoCommand
private: private:
Element *m_element; Element *m_element;
bool m_first_redo; bool m_first_redo;
int m_group_index = -1;
QMap<Element*, int> m_group_indices;
QList<Element *> m_linked_before; //<Linked elements before this command, or when we call "undo" QList<Element *> m_linked_before; //<Linked elements before this command, or when we call "undo"
QList<Element *> m_linked_after; //<Linked elements after this command, or when we recall "redo" QList<Element *> m_linked_after; //<Linked elements after this command, or when we recall "redo"
}; };