diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake index d3178901e..60fddcf55 100644 --- a/cmake/qet_compilation_vars.cmake +++ b/cmake/qet_compilation_vars.cmake @@ -716,6 +716,8 @@ set(QET_SRC_FILES ${QET_DIR}/sources/ui/borderpropertieswidget.h ${QET_DIR}/sources/ui/compositetexteditdialog.cpp ${QET_DIR}/sources/ui/compositetexteditdialog.h + ${QET_DIR}/sources/ui/conductorpropertieseditorwidget.cpp + ${QET_DIR}/sources/ui/conductorpropertieseditorwidget.h ${QET_DIR}/sources/ui/contactgroupselectiondialog.cpp ${QET_DIR}/sources/ui/contactgroupselectiondialog.h ${QET_DIR}/sources/ui/conductorpropertiesdialog.cpp diff --git a/lang/qet_en.ts b/lang/qet_en.ts index 7a899eabf..6f4dae9f4 100644 --- a/lang/qet_en.ts +++ b/lang/qet_en.ts @@ -912,6 +912,31 @@ Note: these options DO NOT allow or block auto numberings, only their update pol Edit the properties of several conductors + + ConductorPropertiesEditorWidget + + + Appliquer les propriétés à l'ensemble des conducteurs de ce potentiel + Apply properties to all conductors of this potential + + + + Modifier les propriétés d'un conducteur + undo caption + Edit conductor properties + + + + Modifier les propriétés de plusieurs conducteurs + undo caption + Edit the properties of several conductors + + + + Conducteur + Conductor + + ConductorPropertiesWidget diff --git a/sources/factory/propertieseditorfactory.cpp b/sources/factory/propertieseditorfactory.cpp index 6a18db924..48a39738b 100644 --- a/sources/factory/propertieseditorfactory.cpp +++ b/sources/factory/propertieseditorfactory.cpp @@ -22,6 +22,8 @@ #include "../qetgraphicsitem/ViewItem/qetgraphicstableitem.h" #include "../qetgraphicsitem/ViewItem/ui/graphicstablepropertieseditor.h" #include "../qetgraphicsitem/ViewItem/ui/projectdbmodelpropertieswidget.h" +#include "../qetgraphicsitem/conductor.h" +#include "../qetgraphicsitem/conductortextitem.h" #include "../qetgraphicsitem/diagramimageitem.h" #include "../qetgraphicsitem/dynamicelementtextitem.h" #include "../qetgraphicsitem/element.h" @@ -30,11 +32,13 @@ #include "../qetgraphicsitem/qetshapeitem.h" #include "../ui/dynamicelementtextitemeditor.h" #include "../ui/elementpropertieswidget.h" +#include "../ui/conductorpropertieseditorwidget.h" #include "../ui/imagepropertieswidget.h" #include "../ui/inditextpropertieswidget.h" #include "../ui/shapegraphicsitempropertieswidget.h" #include +#include /** @brief PropertiesEditorFactory::propertiesEditor @@ -83,6 +87,17 @@ PropertiesEditorWidget *PropertiesEditorFactory::propertiesEditor( return nullptr; } QGraphicsItem *item = items.first(); + + //Selecting a conductor's text label edits its parent conductor (#500), + //mirroring how double-clicking the label opens the conductor dialog. + if (count_ == 1) { + if (auto *cti = qgraphicsitem_cast(item)) { + if (Conductor *parent_cond = cti->parentConductor()) { + items = {parent_cond}; + item = parent_cond; + } + } + } const int type_ = item->type(); //The editor widget can only edit one item @@ -100,6 +115,28 @@ PropertiesEditorWidget *PropertiesEditorFactory::propertiesEditor( switch (type_) { + case Conductor::Type: //1001 + { + //Off unless enabled in the preferences (General page): selecting + //a conductor then brings up nothing in the dock, as before. + if (!QSettings().value( + QStringLiteral("diagrameditor/conductor_properties_panel"), + false).toBool()) { + return nullptr; + } + //Prototype (#500): single-conductor editing in the dock. + if (count_ > 1) { + return nullptr; + } + auto conductor = static_cast(item); + + if (class_name == ConductorPropertiesEditorWidget::staticMetaObject.className()) + { + static_cast(editor)->setConductor(conductor); + return editor; + } + return new ConductorPropertiesEditorWidget(conductor, parent); + } case Element::Type: //1000 { if (count_ > 1) { diff --git a/sources/ui/conductorpropertieseditorwidget.cpp b/sources/ui/conductorpropertieseditorwidget.cpp new file mode 100644 index 000000000..384e43f11 --- /dev/null +++ b/sources/ui/conductorpropertieseditorwidget.cpp @@ -0,0 +1,287 @@ +/* + 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 "conductorpropertieseditorwidget.h" + +#include "../QPropertyUndoCommand/qpropertyundocommand.h" +#include "../diagram.h" +#include "../qetgraphicsitem/conductor.h" +#include "conductorpropertieswidget.h" +#include "../qtextorientationspinboxwidget.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + @brief ConductorPropertiesEditorWidget::ConductorPropertiesEditorWidget + @param conductor : conductor to edit + @param parent : parent widget +*/ +ConductorPropertiesEditorWidget::ConductorPropertiesEditorWidget( + Conductor *conductor, QWidget *parent) : + PropertiesEditorWidget(parent), + m_cpw(new ConductorPropertiesWidget(this)) +{ + // The conductor widget is dialog-sized (min width ~600px), far too wide for + // a dock. Host it in a scroll area with a small minimum width so the dock + // can be dragged to any width (a scrollbar appears when narrower than the + // content). QET already persists dock geometry across restarts via + // QETDiagramEditor save/restoreState, so the chosen width is remembered. + // "Apply to all conductors of the potential": same semantics as the modal + // dialog's checkbox (ConductorPropertiesDialog::applyAll), pinned at the top + // of the panel so it stays visible above the scrolling tabs (#500). Unlike + // the dialog the choice is persisted, so a user who always wants it on (or + // off) sets it once. It is a child of this editor, not of m_cpw, so it is + // deliberately outside the live-edit signal wiring in connectChangeSignals() + // (toggling it must not push an edit, only change how the next edit applies). + // Reuse the modal dialog's exact wording for consistency (and so the + // existing translation applies). + m_apply_all_cb = new QCheckBox( + tr("Appliquer les propriétés à l'ensemble des conducteurs de ce potentiel"), + this); + m_apply_all_cb->setChecked(QSettings().value( + QStringLiteral("diagrameditor/conductor_apply_all"), true).toBool()); + connect(m_apply_all_cb, &QCheckBox::toggled, this, [](bool on) { + QSettings().setValue( + QStringLiteral("diagrameditor/conductor_apply_all"), on); + }); + + auto *scroll = new QScrollArea(this); + scroll->setWidgetResizable(true); + scroll->setFrameShape(QFrame::NoFrame); + scroll->setWidget(m_cpw); + auto *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_apply_all_cb); + layout->addWidget(scroll); + setMinimumWidth(120); + // Expand vertically to fill the dock like the other editors do (otherwise + // the panel sits at its small size hint with empty space below it, #500), + // while keeping a minimum height so it stays usable when the dock is short. + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + setMinimumHeight(200); + setDisabled(true); + setConductor(conductor); +} + +ConductorPropertiesEditorWidget::~ConductorPropertiesEditorWidget() +{} + +/** + @brief ConductorPropertiesEditorWidget::setConductor + Set (or change) the conductor whose properties are edited. + @param conductor +*/ +void ConductorPropertiesEditorWidget::setConductor(Conductor *conductor) +{ + if (!conductor) return; + if (m_conductor && m_conductor != conductor) + disconnect(m_conductor, &Conductor::propertiesChange, + this, &ConductorPropertiesEditorWidget::updateUi); + m_conductor = conductor; + //Keep the dock in sync when the conductor is edited elsewhere (e.g. the + //modal "Edit conductor" dialog); otherwise a stale snapshot would be + //written back on the next apply() and overwrite that change (issue #500). + connect(m_conductor, &Conductor::propertiesChange, + this, &ConductorPropertiesEditorWidget::updateUi, Qt::UniqueConnection); + setEnabled(true); + updateUi(); +} + +/** + @brief ConductorPropertiesEditorWidget::apply + Push the edit onto the diagram's undo stack. +*/ +void ConductorPropertiesEditorWidget::apply() +{ + // Ignore the field-change signals emitted while the widget is being loaded + // programmatically (updateUi/reset): mid-load the widget holds a partial + // state that must not be committed onto the conductor. + if (m_updating) return; + if (!m_conductor || !m_conductor->diagram()) return; + if (QUndoCommand *undo = associatedUndo()) + m_conductor->diagram()->undoStack().push(undo); + m_initial = m_conductor->properties(); +} + +/** + @brief ConductorPropertiesEditorWidget::setLiveEdit + In live-edit mode (how the dock uses every editor), each field change is + applied immediately instead of via an explicit apply() call. Without this + override the base class is a no-op and edits in the dock were never applied + (issue #500). + @param live_edit true to enable live edit + @return always true +*/ +bool ConductorPropertiesEditorWidget::setLiveEdit(bool live_edit) +{ + if (m_live_edit == live_edit) return true; + m_live_edit = live_edit; + + if (m_live_edit) connectChangeSignals(); + else disconnectChangeSignals(); + + return true; +} + +/** + @brief ConductorPropertiesEditorWidget::connectChangeSignals + Wire every editable control of the hosted ConductorPropertiesWidget to + apply(). Commit-style signals (editingFinished / activated / toggled / + sliderReleased) are used rather than per-keystroke ones so each edit yields + a single, clean undo step. Loading the widget programmatically (updateUi) + also fires some of these, but apply() is a no-op then because + associatedUndo() returns nullptr when the properties are unchanged. +*/ +void ConductorPropertiesEditorWidget::connectChangeSignals() +{ + if (!m_cpw) return; + + const auto add = [this](QMetaObject::Connection c) { + m_live_connections << c; + }; + + for (auto *w : m_cpw->findChildren()) + add(connect(w, &QLineEdit::editingFinished, + this, &ConductorPropertiesEditorWidget::apply)); + for (auto *w : m_cpw->findChildren()) + add(connect(w, &QAbstractSpinBox::editingFinished, + this, &ConductorPropertiesEditorWidget::apply)); + for (auto *w : m_cpw->findChildren()) + add(connect(w, QOverload::of(&QComboBox::activated), + this, &ConductorPropertiesEditorWidget::apply)); + for (auto *w : m_cpw->findChildren()) + add(connect(w, &QSlider::sliderReleased, + this, &ConductorPropertiesEditorWidget::apply)); + for (auto *w : m_cpw->findChildren()) + add(connect(w, &KColorButton::changed, + this, &ConductorPropertiesEditorWidget::apply)); + for (auto *w : m_cpw->findChildren()) + add(connect(w, QOverload<>::of(&QTextOrientationSpinBoxWidget::editingFinished), + this, &ConductorPropertiesEditorWidget::apply)); + // Checkboxes and the checkable group boxes (single/multi wire, bicolor…). + for (auto *w : m_cpw->findChildren()) + if (w->isCheckable()) + add(connect(w, &QAbstractButton::toggled, + this, &ConductorPropertiesEditorWidget::apply)); + for (auto *w : m_cpw->findChildren()) + if (w->isCheckable()) + add(connect(w, &QGroupBox::toggled, + this, &ConductorPropertiesEditorWidget::apply)); +} + +/** + @brief ConductorPropertiesEditorWidget::disconnectChangeSignals + Tear down the live-edit connections made by connectChangeSignals(). +*/ +void ConductorPropertiesEditorWidget::disconnectChangeSignals() +{ + for (const QMetaObject::Connection &c : m_live_connections) + disconnect(c); + m_live_connections.clear(); +} + +/** + @brief ConductorPropertiesEditorWidget::reset + Discard the in-progress edit, restoring the conductor's current properties. +*/ +void ConductorPropertiesEditorWidget::reset() +{ + if (!m_conductor) return; + m_updating = true; + m_cpw->setProperties(m_initial); + m_updating = false; +} + +/** + @brief ConductorPropertiesEditorWidget::updateUi + Reload the widget from the conductor (e.g. when the selection changes). +*/ +void ConductorPropertiesEditorWidget::updateUi() +{ + if (!m_conductor) return; + m_updating = true; + m_initial = m_conductor->properties(); + m_cpw->setProperties(m_initial); + m_updating = false; +} + +/** + @brief ConductorPropertiesEditorWidget::associatedUndo + @return the edit as a QPropertyUndoCommand, or nullptr if unchanged. + + When "apply to all" is ticked, every conductor on the same potential is + updated in the same undo step (one undo reverts them all), exactly as the + modal dialog does (ConductorPropertiesDialog::PropertiesDialog). Otherwise + only the selected conductor is changed. +*/ +QUndoCommand *ConductorPropertiesEditorWidget::associatedUndo() const +{ + if (!m_conductor) return nullptr; + + const ConductorProperties new_properties = m_cpw->properties(); + if (new_properties == m_conductor->properties()) return nullptr; + + QVariant old_value, new_value; + old_value.setValue(m_conductor->properties()); + new_value.setValue(new_properties); + + auto *undo = new QPropertyUndoCommand( + m_conductor, "properties", old_value, new_value); + undo->setText(tr("Modifier les propriétés d'un conducteur", "undo caption")); + + // Propagate to every conductor on the same potential, as the modal dialog + // does: each related conductor becomes a child command of the same undo + // step, set to the same target properties. + if (m_apply_all_cb && m_apply_all_cb->isChecked()) + { + const auto potential = m_conductor->relatedPotentialConductors(); + if (!potential.isEmpty()) + { + undo->setText(tr("Modifier les propriétés de plusieurs conducteurs", + "undo caption")); + for (Conductor *potential_conductor : potential) + { + QVariant old_v; + old_v.setValue(potential_conductor->properties()); + new QPropertyUndoCommand( + potential_conductor, "properties", old_v, new_value, undo); + } + } + } + return undo; +} + +/** + @brief ConductorPropertiesEditorWidget::title + @return the panel title. +*/ +QString ConductorPropertiesEditorWidget::title() const +{ + return tr("Conducteur"); +} diff --git a/sources/ui/conductorpropertieseditorwidget.h b/sources/ui/conductorpropertieseditorwidget.h new file mode 100644 index 000000000..0479a6bee --- /dev/null +++ b/sources/ui/conductorpropertieseditorwidget.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 . +*/ +#ifndef CONDUCTORPROPERTIESEDITORWIDGET_H +#define CONDUCTORPROPERTIESEDITORWIDGET_H + +#include "../PropertiesEditor/propertieseditorwidget.h" +#include "../conductorproperties.h" + +#include +#include + +class Conductor; +class ConductorPropertiesWidget; +class QCheckBox; + +/** + @brief The ConductorPropertiesEditorWidget class + Hosts the existing ConductorPropertiesWidget in the dockable selection- + properties panel, so a selected conductor can be edited in place like the + other item types, instead of only through the modal dialog (issue #500). + A pinned "apply to all conductors of the potential" checkbox (persisted) + mirrors the modal dialog's option to propagate edits to the whole potential. +*/ +class ConductorPropertiesEditorWidget : public PropertiesEditorWidget +{ + Q_OBJECT + + public: + explicit ConductorPropertiesEditorWidget( + Conductor *conductor = nullptr, QWidget *parent = nullptr); + ~ConductorPropertiesEditorWidget() override; + + void setConductor(Conductor *conductor); + + void apply() override; + void reset() override; + void updateUi() override; + QUndoCommand *associatedUndo() const override; + QString title() const override; + bool setLiveEdit(bool live_edit) override; + + private: + void connectChangeSignals(); + void disconnectChangeSignals(); + + private: + ConductorPropertiesWidget *m_cpw = nullptr; + QCheckBox *m_apply_all_cb = nullptr; + Conductor *m_conductor = nullptr; + ConductorProperties m_initial; + QList m_live_connections; + bool m_updating = false; +}; + +#endif // CONDUCTORPROPERTIESEDITORWIDGET_H diff --git a/sources/ui/configpage/generalconfigurationpage.cpp b/sources/ui/configpage/generalconfigurationpage.cpp index 1340b9b66..41fa915bd 100644 --- a/sources/ui/configpage/generalconfigurationpage.cpp +++ b/sources/ui/configpage/generalconfigurationpage.cpp @@ -102,6 +102,7 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) : else ui->m_use_windows_mode_rb->setChecked(true); ui->m_zoom_out_beyond_folio->setChecked(settings.value("diagrameditor/zoom-out-beyond-of-folio", false).toBool()); + ui->m_conductor_properties_panel->setChecked(settings.value("diagrameditor/conductor_properties_panel", false).toBool()); ui->m_use_gesture_trackpad->setChecked(settings.value("diagramview/gestures", false).toBool()); ui->m_save_label_paste->setChecked(settings.value("diagramcommands/erase-label-on-copy", true).toBool()); ui->m_enable_scripting->setChecked(QetSettings::scriptingEnabled()); @@ -295,6 +296,7 @@ void GeneralConfigurationPage::applyConf() settings.setValue("diagrameditor/viewmode", view_mode) ; 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/conductor_properties_panel", ui->m_conductor_properties_panel->isChecked()); settings.setValue("diagrameditor/autosave-interval", ui->m_autosave_sb->value()); settings.setValue("diagrameditor/grid_display_startup", ui->grid_startup_cb->isChecked()); diff --git a/sources/ui/configpage/generalconfigurationpage.ui b/sources/ui/configpage/generalconfigurationpage.ui index 177314d93..cfd57dd5c 100644 --- a/sources/ui/configpage/generalconfigurationpage.ui +++ b/sources/ui/configpage/generalconfigurationpage.ui @@ -63,6 +63,13 @@ + + + + Afficher les propriétés d'un conducteur sélectionné dans le panneau Propriétés de la sélection + + + @@ -1205,6 +1212,7 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments m_use_system_color_cb m_use_gesture_trackpad m_zoom_out_beyond_folio + m_conductor_properties_panel m_use_windows_mode_rb m_use_tab_mode_rb m_save_label_paste