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 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-01 23:15:32 +13:00
parent a8699f6f62
commit a556204ba1
3 changed files with 116 additions and 56 deletions
+22 -17
View File
@@ -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<QGraphicsItem *> mapped;
for (QGraphicsItem *qgi : std::as_const(items)) {
if (auto *cti = qgraphicsitem_cast<ConductorTextItem *>(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<ConductorTextItem *>(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<Conductor *> conductors;
for (QGraphicsItem *qgi : std::as_const(items)) {
conductors << static_cast<Conductor *>(qgi);
}
auto conductor = static_cast<Conductor*>(item);
if (class_name == ConductorPropertiesEditorWidget::staticMetaObject.className())
{
static_cast<ConductorPropertiesEditorWidget*>(editor)->setConductor(conductor);
static_cast<ConductorPropertiesEditorWidget*>(editor)->setConductors(conductors);
return editor;
}
return new ConductorPropertiesEditorWidget(conductor, parent);
return new ConductorPropertiesEditorWidget(conductors, parent);
}
case Element::Type: //1000
{
+83 -35
View File
@@ -35,6 +35,7 @@
#include <QComboBox>
#include <QGroupBox>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QScrollArea>
#include <QSettings>
@@ -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<Conductor *> &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<Conductor *> &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<Conductor> &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<Conductor> &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<Conductor *> 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<Conductor *> targets;
for (const QPointer<Conductor> &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;
+11 -4
View File
@@ -23,18 +23,22 @@
#include <QList>
#include <QMetaObject>
#include <QPointer>
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<Conductor *> &conductors = {},
QWidget *parent = nullptr);
~ConductorPropertiesEditorWidget() override;
void setConductor(Conductor *conductor);
void setConductors(const QList<Conductor *> &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<QPointer<Conductor>> m_conductors;
//What the widget showed before the edit: the fields that
//differ from it are the ones the user changed.
ConductorProperties m_shown;