diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake index 57a012916..56ed72ebc 100644 --- a/cmake/qet_compilation_vars.cmake +++ b/cmake/qet_compilation_vars.cmake @@ -683,6 +683,8 @@ set(QET_SRC_FILES ${QET_DIR}/sources/ui/dynamicelementtextitemeditor.h ${QET_DIR}/sources/ui/dynamicelementtextmodel.cpp ${QET_DIR}/sources/ui/dynamicelementtextmodel.h + ${QET_DIR}/sources/ui/customelementinfopartwidget.cpp + ${QET_DIR}/sources/ui/customelementinfopartwidget.h ${QET_DIR}/sources/ui/elementinfopartwidget.cpp ${QET_DIR}/sources/ui/elementinfopartwidget.h ${QET_DIR}/sources/ui/elementinfowidget.cpp diff --git a/sources/ui/customelementinfopartwidget.cpp b/sources/ui/customelementinfopartwidget.cpp new file mode 100644 index 000000000..a1130f807 --- /dev/null +++ b/sources/ui/customelementinfopartwidget.cpp @@ -0,0 +1,113 @@ +/* + 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 . +*/ +#include "customelementinfopartwidget.h" + +#include "../diagramcontext.h" +#include "../qeticons.h" + +#include +#include +#include + +/** + @brief CustomElementInfoPartWidget::CustomElementInfoPartWidget + Constructor + @param key initial key name (empty for a freshly added row) + @param value initial value + @param parent parent widget +*/ +CustomElementInfoPartWidget::CustomElementInfoPartWidget( + const QString &key, + const QString &value, + QWidget *parent) : + QWidget(parent), + m_key_edit(new QLineEdit(key, this)), + m_value_edit(new QLineEdit(value, this)), + m_remove_button(new QToolButton(this)) +{ + m_key_edit->setPlaceholderText(tr("nom_de_la_propriete")); + m_key_edit->setToolTip(tr("Lettres minuscules, chiffres, tiret et underscore uniquement")); + m_value_edit->setClearButtonEnabled(true); + + m_remove_button->setIcon(QET::Icons::Remove); + m_remove_button->setToolTip(tr("Supprimer cette propriété")); + m_remove_button->setAutoRaise(true); + + auto *layout = new QGridLayout(this); + layout->setContentsMargins(0, 2, 0, 2); + layout->setVerticalSpacing(2); + layout->setHorizontalSpacing(0); + layout->addWidget(m_key_edit, 0, 0); + layout->addWidget(m_value_edit, 1, 0); + layout->addWidget(m_remove_button, 0, 1, 2, 1); + + connect(m_key_edit, &QLineEdit::textChanged, this, &CustomElementInfoPartWidget::validateKey); + connect(m_key_edit, &QLineEdit::textChanged, this, &CustomElementInfoPartWidget::changed); + connect(m_value_edit, &QLineEdit::textChanged, this, &CustomElementInfoPartWidget::changed); + connect(m_remove_button, &QToolButton::clicked, this, [this]() { + emit removeRequested(this); + }); + + setFocusProxy(m_key_edit); + validateKey(); +} + +CustomElementInfoPartWidget::~CustomElementInfoPartWidget() +{ +} + +/** + @return the key name currently typed in this row +*/ +QString CustomElementInfoPartWidget::key() const +{ + return m_key_edit->text().trimmed(); +} + +/** + @return the value currently typed in this row +*/ +QString CustomElementInfoPartWidget::value() const +{ + return m_value_edit->text(); +} + +/** + @return true if the typed key is non-empty and matches + DiagramContext::isKeyAcceptable() +*/ +bool CustomElementInfoPartWidget::hasValidKey() const +{ + const QString k = key(); + return !k.isEmpty() && DiagramContext::isKeyAcceptable(k); +} + +/** + @brief CustomElementInfoPartWidget::validateKey + Flag the key field when it doesn't match the accepted format, + instead of silently dropping it later. +*/ +void CustomElementInfoPartWidget::validateKey() +{ + const QString k = key(); + if (k.isEmpty() || DiagramContext::isKeyAcceptable(k)) { + m_key_edit->setStyleSheet(QString()); + } else { + m_key_edit->setStyleSheet(QStringLiteral("border: 1px solid red;")); + } +} diff --git a/sources/ui/customelementinfopartwidget.h b/sources/ui/customelementinfopartwidget.h new file mode 100644 index 000000000..c5bd3d1e9 --- /dev/null +++ b/sources/ui/customelementinfopartwidget.h @@ -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 . +*/ +#ifndef CUSTOMELEMENTINFOPARTWIDGET_H +#define CUSTOMELEMENTINFOPARTWIDGET_H + +#include + +class QLineEdit; +class QToolButton; + +/** + @brief The CustomElementInfoPartWidget class + A single row letting the user define their own element information + key/value pair, unlike ElementInfoPartWidget which is bound to one + predefined key. The key is validated against + DiagramContext::isKeyAcceptable() as the user types. +*/ +class CustomElementInfoPartWidget : public QWidget +{ + Q_OBJECT + + public: + explicit CustomElementInfoPartWidget( + const QString &key = QString(), + const QString &value = QString(), + QWidget *parent = nullptr); + ~CustomElementInfoPartWidget() override; + + QString key() const; + QString value() const; + bool hasValidKey() const; + + signals: + void changed(); + void removeRequested(CustomElementInfoPartWidget *self); + + private slots: + void validateKey(); + + private: + QLineEdit *m_key_edit; + QLineEdit *m_value_edit; + QToolButton *m_remove_button; +}; + +#endif // CUSTOMELEMENTINFOPARTWIDGET_H diff --git a/sources/ui/elementinfowidget.cpp b/sources/ui/elementinfowidget.cpp index 0b240809b..a566643c5 100644 --- a/sources/ui/elementinfowidget.cpp +++ b/sources/ui/elementinfowidget.cpp @@ -17,12 +17,14 @@ */ #include "elementinfowidget.h" #include +#include #include "../diagram.h" #include "../qetapp.h" #include "../qetgraphicsitem/element.h" #include "../qetinformation.h" #include "../ui_elementinfowidget.h" #include "../undocommand/changeelementinformationcommand.h" +#include "customelementinfopartwidget.h" #include "elementinfopartwidget.h" /** @@ -47,6 +49,7 @@ ElementInfoWidget::ElementInfoWidget(Element *elmt, QWidget *parent) : ElementInfoWidget::~ElementInfoWidget() { qDeleteAll(m_eipw_list); + qDeleteAll(m_custom_eipw_list); delete ui; } @@ -207,6 +210,11 @@ void ElementInfoWidget::buildInterface() ui->scroll_vlayout->addWidget(eipw); m_eipw_list << eipw; } + + m_add_custom_property_btn = new QPushButton(tr("Ajouter une propriété personnalisée"), this); + connect(m_add_custom_property_btn, &QPushButton::clicked, this, [this]() { addCustomProperty(); }); + ui->scroll_vlayout->addWidget(m_add_custom_property_btn); + ui->scroll_vlayout->addStretch(); // Existing potential isolating checkbox @@ -235,6 +243,67 @@ void ElementInfoWidget::buildInterface() m_potential_isolating_cb->setVisible(false); } } +/** + @brief ElementInfoWidget::predefinedKeys + @return every key this widget already exposes a dedicated row for, + whether through ElementInfoPartWidget (the ~40 ELMT_* keys) or one + of the standalone checkboxes. Anything present in the element's + informations but absent from this list is a user-defined custom + property. +*/ +QStringList ElementInfoWidget::predefinedKeys() const +{ + QStringList keys = (m_element.data()->elementData().m_type == ElementData::Terminal) + ? QETInformation::terminalElementInfoKeys() + : QETInformation::elementInfoKeys(); + + keys << QStringLiteral("auto_num_locked") + << QStringLiteral("potential_isolating") + << QStringLiteral("exclude_from_bom"); + + return keys; +} + +/** + @brief ElementInfoWidget::addCustomProperty + Append a new user-defined key/value row to the widget. + @param key initial key, left empty for a freshly added row + @param value initial value +*/ +void ElementInfoWidget::addCustomProperty(const QString &key, const QString &value) +{ + auto *widget = new CustomElementInfoPartWidget(key, value, this); + + const int insert_index = ui->scroll_vlayout->indexOf(m_add_custom_property_btn); + ui->scroll_vlayout->insertWidget(insert_index >= 0 ? insert_index : ui->scroll_vlayout->count(), widget); + m_custom_eipw_list << widget; + + connect(widget, &CustomElementInfoPartWidget::removeRequested, this, &ElementInfoWidget::removeCustomProperty); + connect(widget, &CustomElementInfoPartWidget::changed, this, [this]() { + if (m_live_edit) apply(); + }); + + if (key.isEmpty()) { + widget->setFocus(); + } +} + +/** + @brief ElementInfoWidget::removeCustomProperty + Remove a user-defined key/value row. + @param widget the row to remove +*/ +void ElementInfoWidget::removeCustomProperty(CustomElementInfoPartWidget *widget) +{ + if (!m_custom_eipw_list.removeOne(widget)) + return; + + ui->scroll_vlayout->removeWidget(widget); + widget->deleteLater(); + + if (m_live_edit) apply(); +} + /** @brief ElementInfoWidget::infoPartWidgetForKey @param key @@ -271,6 +340,21 @@ void ElementInfoWidget::updateUi() for (ElementInfoPartWidget *eipw : m_eipw_list) { eipw -> setText (element_info[eipw->key()].toString()); } + + // Rebuild the custom-property rows to match whatever + // user-defined keys this element currently carries. + while (!m_custom_eipw_list.isEmpty()) { + CustomElementInfoPartWidget *w = m_custom_eipw_list.takeLast(); + ui->scroll_vlayout->removeWidget(w); + delete w; + } + const auto known_keys = predefinedKeys(); + for (const QString &key : element_info.keys()) { + if (!known_keys.contains(key)) { + addCustomProperty(key, element_info[key].toString()); + } + } + // Load the lock status for auto numbering if (m_element->elementData().m_type == ElementData::Terminal) { QString lock_value = element_info.value(QStringLiteral("auto_num_locked")).toString(); @@ -314,6 +398,17 @@ DiagramContext ElementInfoWidget::currentInfo() const } } + for (const auto &custom : std::as_const(m_custom_eipw_list)) + { + if (custom->hasValidKey() && !custom->value().isEmpty()) + { + QString txt{custom->value()}; + txt.remove(QStringLiteral("\r")); + txt.remove(QStringLiteral("\n")); + info_.addValue(custom->key(), txt); + } + } + // 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")); diff --git a/sources/ui/elementinfowidget.h b/sources/ui/elementinfowidget.h index d47d455b6..263a276ae 100644 --- a/sources/ui/elementinfowidget.h +++ b/sources/ui/elementinfowidget.h @@ -26,8 +26,10 @@ class Element; class QUndoCommand; class ElementInfoPartWidget; +class CustomElementInfoPartWidget; class ChangeElementInformationCommand; class QCheckBox; +class QPushButton; namespace Ui { class ElementInfoWidget; @@ -63,15 +65,20 @@ class ElementInfoWidget : public AbstractElementPropertiesEditorWidget private: void buildInterface(); ElementInfoPartWidget *infoPartWidgetForKey(const QString &key) const; + QStringList predefinedKeys() const; private slots: void firstActivated(); void elementInfoChange(); + void addCustomProperty(const QString &key = QString(), const QString &value = QString()); + void removeCustomProperty(CustomElementInfoPartWidget *widget); //ATTRIBUTES private: Ui::ElementInfoWidget *ui; QList m_eipw_list; + QList m_custom_eipw_list; + QPushButton *m_add_custom_property_btn = nullptr; QCheckBox *m_potential_isolating_cb = nullptr; QCheckBox *m_exclude_from_bom_cb = nullptr; bool m_first_activation;