mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-28 21:34:12 +02:00
6ed41bbfd0
Clicking an item of a group selects the whole group (#1070). Clicking again on one of its items is meant to select just that item, to edit it on its own -- what discussion #1070 proposed -- but the second click selected the whole group again: Qt left only the clicked item selected on release, and the group completion pulled the others back in. A press on a member of a group that is selected whole now notes that member (ItemGroups::memberToPick(), which also finds the member when the click lands on a symbol's own text). If the click ends without a drag and Qt has left only that member selected, the selection stays so. A drag still moves the whole group; Ctrl+click keeps its meaning; a group of one is not picked from. In the GUI, on two grouped texts: one click then Delete removes both (master and this); click, click again, Delete removes only the clicked text here, both on master; dragging after one click moves both texts by the same amount on both. tst_itemgroups: 5 new checks; removing the whole-group or the group-of-one condition fails one each. ctest 24/24. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
166 lines
4.5 KiB
C++
166 lines
4.5 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "itemgroups.h"
|
|
|
|
#include <QDomElement>
|
|
#include <QGraphicsItem>
|
|
#include <QGraphicsScene>
|
|
#include <QSet>
|
|
|
|
/**
|
|
@return the uuid of the group @a item belongs to, or a null uuid.
|
|
*/
|
|
QUuid ItemGroups::groupOf(const QGraphicsItem *item)
|
|
{
|
|
return item ? item->data(data_key).toUuid() : QUuid();
|
|
}
|
|
|
|
/**
|
|
Put @a item in @a group, or take it out of any group if @a group is null.
|
|
*/
|
|
void ItemGroups::setGroup(QGraphicsItem *item, const QUuid &group)
|
|
{
|
|
if (!item) {
|
|
return;
|
|
}
|
|
item->setData(data_key, group.isNull() ? QVariant() : QVariant(group));
|
|
}
|
|
|
|
/**
|
|
Write @a item's group, if it has one, on its XML element @a xml.
|
|
Nothing is written otherwise, so a project without groups saves exactly
|
|
as before.
|
|
*/
|
|
void ItemGroups::write(QDomElement &xml, const QGraphicsItem *item)
|
|
{
|
|
const QUuid group = groupOf(item);
|
|
if (!group.isNull()) {
|
|
xml.setAttribute(QString::fromLatin1(xml_attribute), group.toString());
|
|
}
|
|
}
|
|
|
|
/**
|
|
@return the group written on @a xml, or a null uuid.
|
|
*/
|
|
QUuid ItemGroups::read(const QDomElement &xml)
|
|
{
|
|
return QUuid(xml.attribute(QString::fromLatin1(xml_attribute)));
|
|
}
|
|
|
|
/**
|
|
@return @a item, or its nearest ancestor, that belongs to a group -- a
|
|
click on a symbol's own text hits the text, but the symbol is the member
|
|
-- or nullptr if none does.
|
|
*/
|
|
QGraphicsItem *ItemGroups::groupedItem(QGraphicsItem *item)
|
|
{
|
|
for (; item; item = item->parentItem()) {
|
|
if (!groupOf(item).isNull()) {
|
|
return item;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
@return the member a click on @a hit may pick out on its own: the grouped
|
|
item hit, when every member of its group is selected already. A first
|
|
click selects the whole group; a second click, on a member of the group
|
|
it selected, is how the user asks for that member alone (discussion
|
|
#1070). nullptr when the click is not that.
|
|
*/
|
|
QGraphicsItem *ItemGroups::memberToPick(QGraphicsItem *hit)
|
|
{
|
|
QGraphicsItem *member = groupedItem(hit);
|
|
if (!member || !member->isSelected() || !member->scene()) {
|
|
return nullptr;
|
|
}
|
|
const QUuid group = groupOf(member);
|
|
int members = 0;
|
|
for (QGraphicsItem *item : member->scene()->items()) {
|
|
if (groupOf(item) == group) {
|
|
if (!item->isSelected()) {
|
|
return nullptr;
|
|
}
|
|
++members;
|
|
}
|
|
}
|
|
return members > 1 ? member : nullptr;
|
|
}
|
|
|
|
/**
|
|
Make the selection of @a scene whole groups again after it changed.
|
|
A group with a selected member is selected entirely, except when the
|
|
change took members of an entirely selected group out of the selection
|
|
while @a toggling (Ctrl+click on a member): then the whole group leaves
|
|
the selection, as the user meant.
|
|
@param previous : the selection before the change
|
|
@param toggling : true when Ctrl is held
|
|
@return true if the selection was changed
|
|
*/
|
|
bool ItemGroups::completeSelection(QGraphicsScene *scene,
|
|
const QList<QGraphicsItem *> &previous,
|
|
bool toggling)
|
|
{
|
|
if (!scene) {
|
|
return false;
|
|
}
|
|
|
|
QSet<QUuid> touched;
|
|
const QList<QGraphicsItem *> selected = scene->selectedItems();
|
|
for (QGraphicsItem *item : selected) {
|
|
const QUuid group = groupOf(item);
|
|
if (!group.isNull()) {
|
|
touched << group;
|
|
}
|
|
}
|
|
|
|
//Groups that just lost a member from the selection
|
|
QSet<QUuid> shrunk;
|
|
for (QGraphicsItem *item : previous) {
|
|
if (item->scene() == scene && !item->isSelected()) {
|
|
const QUuid group = groupOf(item);
|
|
if (!group.isNull()) {
|
|
shrunk << group;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (touched.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
//One pass over the folio for every touched group, not one per group:
|
|
//select all on a folio of 2000 items in 200 groups took 44 ms the
|
|
//other way.
|
|
bool changed = false;
|
|
for (QGraphicsItem *item : scene->items())
|
|
{
|
|
const QUuid group = groupOf(item);
|
|
if (group.isNull() || !touched.contains(group)) {
|
|
continue;
|
|
}
|
|
const bool select = !(toggling && shrunk.contains(group));
|
|
if (item->isSelected() != select) {
|
|
item->setSelected(select);
|
|
changed = true;
|
|
}
|
|
}
|
|
return changed;
|
|
}
|