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;