From a556204ba1f1b77a3e1ad3926eec1120c497edc7 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Thu, 1 Oct 2026 23:15:32 +1300 Subject: [PATCH] Edit several wires at once in the Selection properties panel Selecting more than one wire left the Selection properties panel empty, and so did a box selection over a single wire, because it also picks up the wire's number label. Now the panel opens for any number of selected wires. A selected wire label counts as its wire, once, so a box drawn over wires works. The fields show the first wire's values, a line above them says how many wires are selected, and an edit is applied to each wire field by field: setting the function on ten wires leaves each with its own number, cable and section. With "apply to all conductors of this potential" ticked, the potentials of all the selected wires follow the same rule. Each edit is one undo step. Fields whose values differ between the selected wires are not marked as such; that can follow if wanted. Co-Authored-By: Claude Opus 5.5 --- sources/factory/propertieseditorfactory.cpp | 39 +++--- .../ui/conductorpropertieseditorwidget.cpp | 118 ++++++++++++------ sources/ui/conductorpropertieseditorwidget.h | 15 ++- 3 files changed, 116 insertions(+), 56 deletions(-) diff --git a/sources/factory/propertieseditorfactory.cpp b/sources/factory/propertieseditorfactory.cpp index 48a39738b..83b554f79 100644 --- a/sources/factory/propertieseditorfactory.cpp +++ b/sources/factory/propertieseditorfactory.cpp @@ -82,22 +82,28 @@ PropertiesEditorWidget *PropertiesEditorFactory::propertiesEditor( PropertiesEditorWidget *editor, QWidget *parent) { + //Selecting a conductor's text label edits its parent conductor (#500), + //mirroring how double-clicking the label opens the conductor dialog. + //A rubber band over wires selects their labels too: each label + //counts as its conductor, once. + QList mapped; + for (QGraphicsItem *qgi : std::as_const(items)) { + if (auto *cti = qgraphicsitem_cast(qgi)) { + if (Conductor *parent_cond = cti->parentConductor()) { + qgi = parent_cond; + } + } + if (!mapped.contains(qgi)) { + mapped << qgi; + } + } + items = mapped; + const int count_ = items.size(); if (count_ == 0) { 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 @@ -124,18 +130,17 @@ PropertiesEditorWidget *PropertiesEditorFactory::propertiesEditor( false).toBool()) { return nullptr; } - //Prototype (#500): single-conductor editing in the dock. - if (count_ > 1) { - return nullptr; + QList conductors; + for (QGraphicsItem *qgi : std::as_const(items)) { + conductors << static_cast(qgi); } - auto conductor = static_cast(item); if (class_name == ConductorPropertiesEditorWidget::staticMetaObject.className()) { - static_cast(editor)->setConductor(conductor); + static_cast(editor)->setConductors(conductors); return editor; } - return new ConductorPropertiesEditorWidget(conductor, parent); + return new ConductorPropertiesEditorWidget(conductors, parent); } case Element::Type: //1000 { diff --git a/sources/ui/conductorpropertieseditorwidget.cpp b/sources/ui/conductorpropertieseditorwidget.cpp index 8b50fa02d..af3e0be33 100644 --- a/sources/ui/conductorpropertieseditorwidget.cpp +++ b/sources/ui/conductorpropertieseditorwidget.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -44,11 +45,11 @@ /** @brief ConductorPropertiesEditorWidget::ConductorPropertiesEditorWidget - @param conductor : conductor to edit + @param conductors : conductors to edit @param parent : parent widget */ ConductorPropertiesEditorWidget::ConductorPropertiesEditorWidget( - Conductor *conductor, QWidget *parent) : + const QList &conductors, QWidget *parent) : PropertiesEditorWidget(parent), m_cpw(new ConductorPropertiesWidget(this)) { @@ -76,12 +77,19 @@ ConductorPropertiesEditorWidget::ConductorPropertiesEditorWidget( QStringLiteral("diagrameditor/conductor_apply_all"), on); }); + //Shown only when several conductors are selected: the fields hold + //the first one's values, and only the edited ones reach the others. + m_count_label = new QLabel(this); + m_count_label->setWordWrap(true); + m_count_label->hide(); + 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_count_label); layout->addWidget(m_apply_all_cb); layout->addWidget(scroll); setMinimumWidth(120); @@ -100,7 +108,7 @@ ConductorPropertiesEditorWidget::ConductorPropertiesEditorWidget( gb->installEventFilter(this); setDisabled(true); - setConductor(conductor); + setConductors(conductors); } /** @@ -123,26 +131,56 @@ ConductorPropertiesEditorWidget::~ConductorPropertiesEditorWidget() {} /** - @brief ConductorPropertiesEditorWidget::setConductor - Set (or change) the conductor whose properties are edited. - @param conductor + @brief ConductorPropertiesEditorWidget::setConductors + Set (or change) the conductors whose properties are edited. + @param conductors */ -void ConductorPropertiesEditorWidget::setConductor(Conductor *conductor) +void ConductorPropertiesEditorWidget::setConductors( + const QList &conductors) { - 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); + if (conductors.isEmpty()) return; + + for (const QPointer &c : std::as_const(m_conductors)) + if (c) + disconnect(c, &Conductor::propertiesChange, + this, &ConductorPropertiesEditorWidget::updateUi); + m_conductors.clear(); + + for (Conductor *c : conductors) + { + m_conductors << c; + //Keep the dock in sync when a 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(c, &Conductor::propertiesChange, + this, &ConductorPropertiesEditorWidget::updateUi, + Qt::UniqueConnection); + } + + const int count = conductors.size(); + m_count_label->setText( + tr("%n conducteurs sélectionnés : seuls les champs modifiés leur sont appliqués.", + "selection properties panel", count)); + m_count_label->setVisible(count > 1); + setEnabled(true); updateUi(); } +/** + @brief ConductorPropertiesEditorWidget::firstConductor + @return the first edited conductor still alive, its values are the ones + shown, or nullptr +*/ +Conductor *ConductorPropertiesEditorWidget::firstConductor() const +{ + for (const QPointer &c : m_conductors) + if (c) + return c; + return nullptr; +} + /** @brief ConductorPropertiesEditorWidget::apply Push the edit onto the diagram's undo stack. @@ -153,9 +191,10 @@ void ConductorPropertiesEditorWidget::apply() // 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; + Conductor *first = firstConductor(); + if (!first || !first->diagram()) return; if (QUndoCommand *undo = associatedUndo()) - m_conductor->diagram()->undoStack().push(undo); + first->diagram()->undoStack().push(undo); m_shown = m_cpw->properties(); } @@ -242,7 +281,7 @@ void ConductorPropertiesEditorWidget::disconnectChangeSignals() */ void ConductorPropertiesEditorWidget::reset() { - if (!m_conductor) return; + if (!firstConductor()) return; m_updating = true; m_cpw->setProperties(m_shown); m_updating = false; @@ -250,13 +289,15 @@ void ConductorPropertiesEditorWidget::reset() /** @brief ConductorPropertiesEditorWidget::updateUi - Reload the widget from the conductor (e.g. when the selection changes). + Reload the widget from the first conductor (e.g. when the selection + changes). */ void ConductorPropertiesEditorWidget::updateUi() { - if (!m_conductor) return; + Conductor *first = firstConductor(); + if (!first) return; m_updating = true; - m_cpw->setProperties(m_conductor->properties()); + m_cpw->setProperties(first->properties()); //Read back rather than keep the conductor's own values: a value the //widget cannot show exactly must not count as an edit. m_shown = m_cpw->properties(); @@ -267,25 +308,32 @@ void ConductorPropertiesEditorWidget::updateUi() @brief ConductorPropertiesEditorWidget::associatedUndo @return the edit as one undo step, or nullptr if nothing changes. - Only the fields the user changed are written: the conductor keeps every - other value, including one the widget cannot show exactly. + Only the fields the user changed are applied, to each edited conductor: + with several conductors selected, each keeps its own text, function, + cable... unless that is the field being edited. - When "apply to all" is ticked, every conductor on the same potential is - updated too, in the same undo step (one undo reverts them all), as the - modal dialog does (ConductorPropertiesDialog::PropertiesDialog). + When "apply to all" is ticked, every conductor on the same potential as + an edited one is updated too, in the same undo step (one undo reverts + them all), as the modal dialog does + (ConductorPropertiesDialog::PropertiesDialog). */ QUndoCommand *ConductorPropertiesEditorWidget::associatedUndo() const { - if (!m_conductor) return nullptr; - const ConductorProperties new_properties = m_cpw->properties(); if (new_properties == m_shown) return nullptr; - QList targets {m_conductor}; - if (m_apply_all_cb && m_apply_all_cb->isChecked()) - for (Conductor *potential_conductor : m_conductor->relatedPotentialConductors()) - if (!targets.contains(potential_conductor)) - targets << potential_conductor; + const bool apply_all = m_apply_all_cb && m_apply_all_cb->isChecked(); + QList targets; + for (const QPointer &c : m_conductors) + { + if (!c) continue; + if (!targets.contains(c.data())) + targets << c.data(); + if (apply_all) + for (Conductor *potential_conductor : c->relatedPotentialConductors()) + if (!targets.contains(potential_conductor)) + targets << potential_conductor; + } auto *undo = new QUndoCommand(); int changed = 0; diff --git a/sources/ui/conductorpropertieseditorwidget.h b/sources/ui/conductorpropertieseditorwidget.h index a5f517738..f0b0d0d0c 100644 --- a/sources/ui/conductorpropertieseditorwidget.h +++ b/sources/ui/conductorpropertieseditorwidget.h @@ -23,18 +23,22 @@ #include #include +#include class Conductor; class ConductorPropertiesWidget; class QCheckBox; +class QLabel; /** @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 + properties panel, so selected conductors 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. + When several conductors are selected, the panel shows the first one and + applies to each only the fields the user changed. */ class ConductorPropertiesEditorWidget : public PropertiesEditorWidget { @@ -42,10 +46,11 @@ class ConductorPropertiesEditorWidget : public PropertiesEditorWidget public: explicit ConductorPropertiesEditorWidget( - Conductor *conductor = nullptr, QWidget *parent = nullptr); + const QList &conductors = {}, + QWidget *parent = nullptr); ~ConductorPropertiesEditorWidget() override; - void setConductor(Conductor *conductor); + void setConductors(const QList &conductors); void apply() override; void reset() override; @@ -60,11 +65,13 @@ class ConductorPropertiesEditorWidget : public PropertiesEditorWidget private: void connectChangeSignals(); void disconnectChangeSignals(); + Conductor *firstConductor() const; private: ConductorPropertiesWidget *m_cpw = nullptr; QCheckBox *m_apply_all_cb = nullptr; - Conductor *m_conductor = nullptr; + QLabel *m_count_label = nullptr; + QList> m_conductors; //What the widget showed before the edit: the fields that //differ from it are the ones the user changed. ConductorProperties m_shown;