mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-27 20:44:13 +02:00
Add grouping of folio items: Group and Ungroup (#1070)
Symbols, free texts, shapes and pictures can be grouped from the Edit menu, the selection's context menu or the command search. Clicking one member selects the group, so moving, copying and deleting act on all of it; Ctrl+click on a member deselects the group; a rubber band touching part of a group selects all of it when released. No default shortcut: Ctrl+G is "jump to element". A group is not an object in the scene. Each member keeps its place and carries the group's uuid (QGraphicsItem::data()), saved as a "group" attribute written only when set: a project without groups saves exactly as before, and older versions open a grouped one and ignore the groups. Re-parenting under a QGraphicsItemGroup would have made every member's position group-relative; ElementTextItemGroup already needs nine special cases for that. - Selection is completed on clicks and at the end of a rubber band, not on every selectionChanged(): export, search and Tab select items themselves and must not have groups pulled back in. - Project database: group_uuid on element, shape, independent_text and image, kept in step by projectDataBase::itemGroupChanged(). - Paste and folio duplication give each source group one new uuid. - Undo of Ungroup restores each item's exact group. Builds on #1065 (uuids and database rows for texts, shapes and images). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
This commit is contained in:
@@ -232,6 +232,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/exportpropertieswidget.h
|
||||
${QET_DIR}/sources/genericpanel.cpp
|
||||
${QET_DIR}/sources/genericpanel.h
|
||||
${QET_DIR}/sources/itemgroups.cpp
|
||||
${QET_DIR}/sources/itemgroups.h
|
||||
${QET_DIR}/sources/lastusedstyle.cpp
|
||||
${QET_DIR}/sources/lastusedstyle.h
|
||||
${QET_DIR}/sources/machine_info.cpp
|
||||
@@ -836,6 +838,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/undocommand/removediagramcommand.h
|
||||
${QET_DIR}/sources/undocommand/setautonumcontextcommand.cpp
|
||||
${QET_DIR}/sources/undocommand/setautonumcontextcommand.h
|
||||
${QET_DIR}/sources/undocommand/groupitemscommand.cpp
|
||||
${QET_DIR}/sources/undocommand/groupitemscommand.h
|
||||
${QET_DIR}/sources/undocommand/rotateselectioncommand.cpp
|
||||
${QET_DIR}/sources/undocommand/rotateselectioncommand.h
|
||||
${QET_DIR}/sources/undocommand/promoteshapecommand.cpp
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "../diagram.h"
|
||||
#include "../diagramposition.h"
|
||||
#include "../elementprovider.h"
|
||||
#include "../itemgroups.h"
|
||||
#include "../qetapp.h"
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
#include "../qetgraphicsitem/diagramimageitem.h"
|
||||
@@ -669,6 +670,13 @@ QUuid drawingItemUuid(QObject *object)
|
||||
return QUuid();
|
||||
}
|
||||
|
||||
/// The group_uuid column of @p item's row: its group, or NULL.
|
||||
QVariant groupValue(const QGraphicsItem *item)
|
||||
{
|
||||
const QUuid group = ItemGroups::groupOf(item);
|
||||
return group.isNull() ? QVariant() : QVariant(group.toString());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
@@ -751,6 +759,33 @@ void projectDataBase::removeDrawingItem(QGraphicsItem *item)
|
||||
forgetDrawingItem(object);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::itemGroupChanged
|
||||
@a item joined or left a group (discussion #1070). An element's row is
|
||||
updated at once; a drawing item's is queued like any other change to it.
|
||||
@param item
|
||||
*/
|
||||
void projectDataBase::itemGroupChanged(QGraphicsItem *item)
|
||||
{
|
||||
if (auto element = qgraphicsitem_cast<Element *>(item))
|
||||
{
|
||||
QSqlQuery update(m_data_base);
|
||||
update.prepare(QStringLiteral("UPDATE element SET group_uuid = :group_uuid WHERE uuid = :uuid"));
|
||||
update.bindValue(QStringLiteral(":group_uuid"), groupValue(element));
|
||||
update.bindValue(QStringLiteral(":uuid"), element->uuid().toString());
|
||||
if (!update.exec()) {
|
||||
qDebug() << "projectDataBase::itemGroupChanged update error : " << update.lastError();
|
||||
}
|
||||
m_content_changed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
QGraphicsObject *object = item ? item->toGraphicsObject() : nullptr;
|
||||
if (object && !drawingItemTable(object).isEmpty()) {
|
||||
m_dirty_drawing_items.insert(object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::drawingItemChanged
|
||||
Queue the sender's row to be rewritten.
|
||||
@@ -884,6 +919,7 @@ bool projectDataBase::writeDrawingItem(QObject *object)
|
||||
query->bindValue(QStringLiteral(":y"), rect.y());
|
||||
query->bindValue(QStringLiteral(":width"), rect.width());
|
||||
query->bindValue(QStringLiteral(":height"), rect.height());
|
||||
query->bindValue(QStringLiteral(":group_uuid"), groupValue(item));
|
||||
if (!query->exec()) {
|
||||
qDebug() << "projectDataBase::writeDrawingItem error : " << query->lastError();
|
||||
return true;
|
||||
@@ -985,6 +1021,7 @@ bool projectDataBase::createDataBase()
|
||||
"pos VARCHAR(6) NOT NULL,"
|
||||
"type VARCHAR(50),"
|
||||
"sub_type VARCHAR(50),"
|
||||
"group_uuid VARCHAR(50),"
|
||||
"FOREIGN KEY (diagram_uuid) REFERENCES diagram (uuid)"
|
||||
")");
|
||||
if (!query_.exec(element_table)) {
|
||||
@@ -1085,7 +1122,8 @@ bool projectDataBase::createDataBase()
|
||||
"uuid VARCHAR(50) PRIMARY KEY NOT NULL, "
|
||||
"diagram_uuid VARCHAR(50) NOT NULL, "
|
||||
"pos VARCHAR(6), "
|
||||
"x REAL, y REAL, width REAL, height REAL, ");
|
||||
"x REAL, y REAL, width REAL, height REAL, "
|
||||
"group_uuid VARCHAR(50), ");
|
||||
for (const QString &table : {
|
||||
QStringLiteral("CREATE TABLE shape (") + drawing_columns +
|
||||
"type VARCHAR(20), color VARCHAR(20), fill VARCHAR(20), "
|
||||
@@ -1516,8 +1554,8 @@ void projectDataBase::prepareQuery()
|
||||
|
||||
//DRAWING ITEMS. OR REPLACE: a row is rewritten in place on every
|
||||
//change, see writeDrawingItem().
|
||||
const QString drawing_columns("uuid, diagram_uuid, pos, x, y, width, height");
|
||||
const QString drawing_values(":uuid, :diagram_uuid, :pos, :x, :y, :width, :height");
|
||||
const QString drawing_columns("uuid, diagram_uuid, pos, x, y, width, height, group_uuid");
|
||||
const QString drawing_values(":uuid, :diagram_uuid, :pos, :x, :y, :width, :height, :group_uuid");
|
||||
m_insert_shape_query = QSqlQuery(m_data_base);
|
||||
m_insert_shape_query.prepare("INSERT OR REPLACE INTO shape (" + drawing_columns +
|
||||
", type, color, fill) VALUES (" + drawing_values +
|
||||
@@ -1561,7 +1599,7 @@ void projectDataBase::prepareQuery()
|
||||
m_diagram_info_order_changed.prepare("UPDATE diagram_info SET folio = :folio WHERE diagram_uuid = :uuid");
|
||||
|
||||
//INSERT ELEMENT
|
||||
QString insert_element_query("INSERT INTO element (uuid, diagram_uuid, pos, type, sub_type) VALUES (:uuid, :diagram_uuid, :pos, :type, :sub_type)");
|
||||
QString insert_element_query("INSERT INTO element (uuid, diagram_uuid, pos, type, sub_type, group_uuid) VALUES (:uuid, :diagram_uuid, :pos, :type, :sub_type, :group_uuid)");
|
||||
m_insert_elements_query = QSqlQuery(m_data_base);
|
||||
m_insert_elements_query.prepare(insert_element_query);
|
||||
|
||||
@@ -1675,6 +1713,7 @@ void projectDataBase::bindElementValues(QSqlQuery &query, Element *element, Diag
|
||||
query.bindValue(QStringLiteral(":pos"), diagram->convertPosition(element->scenePos()).toString());
|
||||
query.bindValue(QStringLiteral(":type"), element_data.typeToString());
|
||||
query.bindValue(QStringLiteral(":sub_type"), element_data.masterTypeToString());
|
||||
query.bindValue(QStringLiteral(":group_uuid"), groupValue(element));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -105,6 +105,7 @@ class projectDataBase : public QObject
|
||||
//furniture. Anything else passed here is ignored.
|
||||
void addDrawingItem (QGraphicsItem *item);
|
||||
void removeDrawingItem (QGraphicsItem *item);
|
||||
void itemGroupChanged (QGraphicsItem *item);
|
||||
|
||||
private slots:
|
||||
//Refresh the sender()'s row after Conductor::setProperties().
|
||||
|
||||
+85
-2
@@ -42,7 +42,9 @@
|
||||
#include "qetinformation.h"
|
||||
#include "qetproject.h"
|
||||
#include "diagramsortkeys.h"
|
||||
#include "itemgroups.h"
|
||||
#include "textgrid.h"
|
||||
#include <QGraphicsView>
|
||||
#include <QTextStream>
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
@@ -121,6 +123,7 @@ namespace {
|
||||
for (T *item : items) {
|
||||
Entry entry{stack_rank.value(item, INT_MAX), QString(),
|
||||
item->toXml(document)};
|
||||
ItemGroups::write(entry.xml, item);
|
||||
// Only an item the stacking query missed needs a tiebreak.
|
||||
if (entry.rank == INT_MAX) {
|
||||
QTextStream stream(&entry.xml_text);
|
||||
@@ -210,6 +213,7 @@ Diagram::Diagram(QETProject *project) :
|
||||
connect(&border_and_titleblock,
|
||||
&BorderTitleBlock::needTitleBlockTemplate,
|
||||
this, &Diagram::setTitleBlockTemplate);
|
||||
|
||||
connect(&border_and_titleblock,
|
||||
&BorderTitleBlock::informationChanged,
|
||||
this, &Diagram::titleChanged);
|
||||
@@ -432,7 +436,9 @@ void Diagram::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
rememberSelection();
|
||||
QGraphicsScene::mousePressEvent(event);
|
||||
completeGroupSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -471,6 +477,9 @@ void Diagram::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
}
|
||||
|
||||
QGraphicsScene::mouseReleaseEvent(event);
|
||||
//A click on an already selected item changes the selection on
|
||||
//release, not on press (Ctrl toggles it, a plain click keeps only it).
|
||||
completeGroupSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1245,8 +1254,9 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
|
||||
if (!list_elements.isEmpty()) {
|
||||
auto dom_elements = document.createElement(QStringLiteral("elements"));
|
||||
for (auto elmt : list_elements) {
|
||||
dom_elements.appendChild(elmt->toXml(document,
|
||||
table_adr_id));
|
||||
QDomElement dom_element = elmt->toXml(document, table_adr_id);
|
||||
ItemGroups::write(dom_element, elmt);
|
||||
dom_elements.appendChild(dom_element);
|
||||
// If copy is active we have to undo the changes we have made during creating(filling) 'list_elements'
|
||||
if(is_copy_command && (elmt->linkType() == Element::Slave || elmt->linkType()&Element::AllReport))
|
||||
restoreText(elmt);
|
||||
@@ -1666,6 +1676,7 @@ bool Diagram::fromXml(QDomElement &document,
|
||||
delete nvel_elmt;
|
||||
qDebug() << QStringLiteral("Diagram::fromXml() : Le chargement des parametres d'un element a echoue");
|
||||
} else {
|
||||
ItemGroups::setGroup(nvel_elmt, ItemGroups::read(element_xml));
|
||||
added_elements << nvel_elmt;
|
||||
}
|
||||
}
|
||||
@@ -1701,6 +1712,7 @@ bool Diagram::fromXml(QDomElement &document,
|
||||
IndependentTextItem *iti = new IndependentTextItem();
|
||||
iti -> fromXml(text_xml);
|
||||
settle_uuid(iti, text_xml, QStringLiteral("input"), added_texts.size());
|
||||
ItemGroups::setGroup(iti, ItemGroups::read(text_xml));
|
||||
addItem(iti);
|
||||
added_texts << iti;
|
||||
}
|
||||
@@ -1713,6 +1725,7 @@ bool Diagram::fromXml(QDomElement &document,
|
||||
DiagramImageItem *dii = new DiagramImageItem ();
|
||||
dii -> fromXml(image_xml);
|
||||
settle_uuid(dii, image_xml, QStringLiteral("image"), added_images.size());
|
||||
ItemGroups::setGroup(dii, ItemGroups::read(image_xml));
|
||||
addItem(dii);
|
||||
added_images << dii;
|
||||
}
|
||||
@@ -1725,6 +1738,7 @@ bool Diagram::fromXml(QDomElement &document,
|
||||
QetShapeItem *dii = new QetShapeItem (QPointF(0,0));
|
||||
dii -> fromXml(shape_xml);
|
||||
settle_uuid(dii, shape_xml, QStringLiteral("shape"), added_shapes.size());
|
||||
ItemGroups::setGroup(dii, ItemGroups::read(shape_xml));
|
||||
addItem(dii);
|
||||
added_shapes << dii;
|
||||
}
|
||||
@@ -2127,6 +2141,75 @@ void Diagram::selectAllTextFields()
|
||||
emit selectionChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::setItemGroup
|
||||
Put @a item in @a group, or take it out of its group if @a group is
|
||||
null, and keep its row in the project database in step.
|
||||
@param item
|
||||
@param group
|
||||
*/
|
||||
void Diagram::setItemGroup(QGraphicsItem *item, const QUuid &group)
|
||||
{
|
||||
ItemGroups::setGroup(item, group);
|
||||
if (m_project) {
|
||||
m_project->dataBase()->itemGroupChanged(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::completeGroupSelection
|
||||
Make the selection whole groups again after the user changed it with a
|
||||
click, see ItemGroups::completeSelection(). Called from the mouse
|
||||
handlers and, when a rubber band is released, from DiagramView -- not
|
||||
on every selectionChanged(): code that selects items itself (export,
|
||||
search, Tab cycling) deselects and reselects one item at a time and
|
||||
must not have groups pulled back in behind it.
|
||||
Left alone while a rubber band is being dragged, which reselects exactly
|
||||
what it covers on every mouse step.
|
||||
*/
|
||||
void Diagram::completeGroupSelection()
|
||||
{
|
||||
for (QGraphicsView *view : views()) {
|
||||
if (!view->rubberBandRect().isNull()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QList<QGraphicsItem *> previous;
|
||||
for (const QPointer<QGraphicsObject> &item : std::as_const(m_previous_selection)) {
|
||||
if (item) {
|
||||
previous << item.data();
|
||||
}
|
||||
}
|
||||
|
||||
//One selectionChanged() for the whole group, not one per member:
|
||||
//the properties dock rebuilds on each.
|
||||
blockSignals(true);
|
||||
const bool changed = ItemGroups::completeSelection(
|
||||
this, previous,
|
||||
QApplication::keyboardModifiers().testFlag(Qt::ControlModifier));
|
||||
blockSignals(false);
|
||||
if (changed) {
|
||||
emit selectionChanged();
|
||||
}
|
||||
rememberSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::rememberSelection
|
||||
Keep the current selection, the "before" completeGroupSelection() needs
|
||||
to tell a member being Ctrl+clicked off.
|
||||
*/
|
||||
void Diagram::rememberSelection()
|
||||
{
|
||||
m_previous_selection.clear();
|
||||
for (QGraphicsItem *item : selectedItems()) {
|
||||
if (QGraphicsObject *object = item->toGraphicsObject()) {
|
||||
m_previous_selection << object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::selectNextItem
|
||||
Select the next (or, if @a forward is false, the previous) selectable
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "qgimanager.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QPointer>
|
||||
#include <QUuid>
|
||||
#include <QtWidgets>
|
||||
#include <QtXml>
|
||||
@@ -138,6 +139,10 @@ class Diagram : public QGraphicsScene
|
||||
bool m_freeze_new_conductors_;
|
||||
QUuid m_uuid = QUuid::createUuid();
|
||||
|
||||
//Selection before the current click, see completeGroupSelection()
|
||||
QList<QPointer<QGraphicsObject>> m_previous_selection;
|
||||
void rememberSelection();
|
||||
|
||||
bool uuidUsedByOtherDiagram(const QUuid &uuid) const;
|
||||
QUuid derivedUuid(const QDomElement &root, const QString &reason) const;
|
||||
|
||||
@@ -282,6 +287,7 @@ class Diagram : public QGraphicsScene
|
||||
const QString& title, const QString& seq,
|
||||
NumerotationContext *nc);
|
||||
void changeZValue(QET::DepthOption option);
|
||||
void setItemGroup(QGraphicsItem *item, const QUuid &group);
|
||||
|
||||
public slots:
|
||||
void adjustSceneRect ();
|
||||
@@ -299,6 +305,7 @@ class Diagram : public QGraphicsScene
|
||||
void invertSelection();
|
||||
void selectAllConductors();
|
||||
void selectAllTextFields();
|
||||
void completeGroupSelection();
|
||||
|
||||
signals:
|
||||
void showDiagram (Diagram *);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "diagramcommands.h"
|
||||
|
||||
#include "diagram.h"
|
||||
#include "itemgroups.h"
|
||||
#include "qetgraphicsitem/conductortextitem.h"
|
||||
#include "qetgraphicsitem/diagramimageitem.h"
|
||||
#include "qetgraphicsitem/dynamicelementtextitem.h"
|
||||
@@ -216,6 +217,27 @@ void PasteDiagramCommand::redo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Pasted groups become new groups: the members of one source
|
||||
//group all get the same new uuid, never the source's, or the
|
||||
//copy would join the original's group. After the elements got
|
||||
//their own uuids: the database row is found by uuid, and before
|
||||
//that it would have been the source element's row.
|
||||
QHash<QUuid, QUuid> renewed_groups;
|
||||
for (QGraphicsItem *item : content.items(DiagramContent::Elements
|
||||
| DiagramContent::TextFields
|
||||
| DiagramContent::Images
|
||||
| DiagramContent::Shapes))
|
||||
{
|
||||
const QUuid source_group = ItemGroups::groupOf(item);
|
||||
if (source_group.isNull()) {
|
||||
continue;
|
||||
}
|
||||
if (!renewed_groups.contains(source_group)) {
|
||||
renewed_groups.insert(source_group, QUuid::createUuid());
|
||||
}
|
||||
diagram -> setItemGroup(item, renewed_groups.value(source_group));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -122,6 +122,13 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
|
||||
});
|
||||
|
||||
connect(m_diagram, &Diagram::showDiagram, this, &DiagramView::showDiagram);
|
||||
//The diagram leaves group completion alone while a rubber band is
|
||||
//dragged; finish it when the band is released (null rect).
|
||||
connect(this, &QGraphicsView::rubberBandChanged, this, [this](QRect rect) {
|
||||
if (rect.isNull()) {
|
||||
m_diagram->completeGroupSelection();
|
||||
}
|
||||
});
|
||||
connect(m_diagram, &QGraphicsScene::sceneRectChanged, this, &DiagramView::adjustSceneRect);
|
||||
connect(&(m_diagram -> border_and_titleblock), &BorderTitleBlock::informationChanged, this, &DiagramView::updateWindowTitle);
|
||||
connect(diagram, &Diagram::findElementRequired, this, &DiagramView::findElementRequired);
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "qetgraphicsitem/diagramimageitem.h"
|
||||
#include "qetgraphicsitem/independenttextitem.h"
|
||||
#include "qetgraphicsitem/qetshapeitem.h"
|
||||
#include "itemgroups.h"
|
||||
#include "qetinformation.h"
|
||||
|
||||
/*
|
||||
@@ -833,6 +834,20 @@ void ElementsPanelWidget::duplicateDiagram()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Groups too: a group of the copy is its own, so selecting it
|
||||
// is the same on both folios but the database tells them apart.
|
||||
QHash<QUuid, QUuid> renewed_groups;
|
||||
for (QGraphicsItem *item : new_diagram->items()) {
|
||||
const QUuid source_group = ItemGroups::groupOf(item);
|
||||
if (source_group.isNull()) {
|
||||
continue;
|
||||
}
|
||||
if (!renewed_groups.contains(source_group)) {
|
||||
renewed_groups.insert(source_group, QUuid::createUuid());
|
||||
}
|
||||
new_diagram->setItemGroup(item, renewed_groups.value(source_group));
|
||||
}
|
||||
}
|
||||
|
||||
elements_panel->reload();
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
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)));
|
||||
}
|
||||
|
||||
/**
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
#ifndef ITEMGROUPS_H
|
||||
#define ITEMGROUPS_H
|
||||
|
||||
#include <QList>
|
||||
#include <QUuid>
|
||||
|
||||
class QDomElement;
|
||||
class QGraphicsItem;
|
||||
class QGraphicsScene;
|
||||
|
||||
/**
|
||||
Groups of items on a folio (discussion #1070): symbols, free texts,
|
||||
shapes and pictures that select, move, copy and delete together.
|
||||
|
||||
A group is not an object in the scene. Each member keeps its place and
|
||||
carries the group's uuid, and selecting one member selects them all;
|
||||
moving, copying and deleting then need nothing new, because they act on
|
||||
the selection. Re-parenting the members under a QGraphicsItemGroup would
|
||||
make every position group-relative, which every piece of code reading a
|
||||
symbol's position would have to learn about.
|
||||
|
||||
This part knows nothing of Diagram, so it can be tested on a plain scene.
|
||||
*/
|
||||
namespace ItemGroups
|
||||
{
|
||||
/// QGraphicsItem::data() key holding an item's group uuid.
|
||||
inline constexpr int data_key = 0x475250; // "GRP"
|
||||
|
||||
/// XML attribute holding it, on <element>, <input>, <shape> and <image>.
|
||||
inline constexpr char xml_attribute[] = "group";
|
||||
|
||||
QUuid groupOf(const QGraphicsItem *item);
|
||||
void setGroup(QGraphicsItem *item, const QUuid &group);
|
||||
|
||||
void write(QDomElement &xml, const QGraphicsItem *item);
|
||||
QUuid read(const QDomElement &xml);
|
||||
|
||||
bool completeSelection(QGraphicsScene *scene,
|
||||
const QList<QGraphicsItem *> &previous,
|
||||
bool toggling);
|
||||
}
|
||||
|
||||
#endif // ITEMGROUPS_H
|
||||
@@ -66,6 +66,7 @@
|
||||
#include "undocommand/addelementtextcommand.h"
|
||||
#include "utils/qetsettings.h"
|
||||
#include "utils/qetutils.h"
|
||||
#include "undocommand/groupitemscommand.h"
|
||||
#include "undocommand/rotateselectioncommand.h"
|
||||
#include "undocommand/rotatetextscommand.h"
|
||||
#include "diagram.h"
|
||||
@@ -813,6 +814,8 @@ void QETDiagramEditor::setUpActions()
|
||||
m_find_element = m_selection_actions_group.addAction( QET::Icons::ZoomDraw, tr("Retrouver dans le panel") );
|
||||
m_edit_selection = m_selection_actions_group.addAction( QET::Icons::ElementEdit, tr("Éditer l'item sélectionné") );
|
||||
m_group_selected_texts = m_selection_actions_group.addAction( QET::Icons::textGroup, tr("Grouper les textes sélectionnés"));
|
||||
m_group_selection = m_selection_actions_group.addAction( tr("Grouper") );
|
||||
m_ungroup_selection = m_selection_actions_group.addAction( tr("Dégrouper") );
|
||||
|
||||
ShortcutManager::instance().registerAction(m_delete_selection, "diagrameditor.delete_selection", tr("Éditeur de schémas"), Qt::Key_Delete);
|
||||
ShortcutManager::instance().registerAction(m_rotate_selection, "diagrameditor.rotate_selection", tr("Éditeur de schémas"), Qt::Key_Space);
|
||||
@@ -918,6 +921,14 @@ void QETDiagramEditor::setUpActions()
|
||||
m_find_element ->setData("find_selected_element");
|
||||
m_edit_selection ->setData("edit_selected_element");
|
||||
m_group_selected_texts->setData("group_selected_texts");
|
||||
m_group_selection ->setData("group_selection");
|
||||
m_ungroup_selection ->setData("ungroup_selection");
|
||||
|
||||
//No default key: Ctrl+G is "jump to element". A user can bind one.
|
||||
ShortcutManager::instance().registerAction(m_group_selection, "diagrameditor.group_selection", tr("Éditeur de schémas"), QKeySequence());
|
||||
ShortcutManager::instance().registerAction(m_ungroup_selection, "diagrameditor.ungroup_selection", tr("Éditeur de schémas"), QKeySequence());
|
||||
m_group_selection ->setStatusTip(tr("Groupe les éléments, textes, formes et images sélectionnés : ils se sélectionnent, se déplacent et se copient ensemble", "status bar tip"));
|
||||
m_ungroup_selection->setStatusTip(tr("Défait les groupes sélectionnés", "status bar tip"));
|
||||
|
||||
connect(&m_selection_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::selectionGroupTriggered);
|
||||
|
||||
@@ -2055,6 +2066,15 @@ void QETDiagramEditor::selectionGroupTriggered(QAction *action)
|
||||
findElementInPanel(currentElement()->location());
|
||||
else if (value == "edit_selected_element")
|
||||
dv->editSelection();
|
||||
else if (value == "group_selection" || value == "ungroup_selection")
|
||||
{
|
||||
GroupItemsCommand *command = value == "group_selection"
|
||||
? GroupItemsCommand::group(diagram)
|
||||
: GroupItemsCommand::ungroup(diagram);
|
||||
if (command) {
|
||||
diagram->undoStack().push(command);
|
||||
}
|
||||
}
|
||||
else if (value == "group_selected_texts")
|
||||
{
|
||||
QList<DynamicElementTextItem *> deti_list = dc.m_element_texts.values();
|
||||
@@ -2198,7 +2218,9 @@ void QETDiagramEditor::slot_updateComplexActions()
|
||||
<< m_rotate_selection
|
||||
<< m_rotate_group_selection
|
||||
<< m_edit_selection
|
||||
<< m_group_selected_texts;
|
||||
<< m_group_selected_texts
|
||||
<< m_group_selection
|
||||
<< m_ungroup_selection;
|
||||
for(QAction *action : action_list)
|
||||
action->setEnabled(false);
|
||||
|
||||
@@ -2262,6 +2284,9 @@ void QETDiagramEditor::slot_updateComplexActions()
|
||||
else
|
||||
m_group_selected_texts->setDisabled(true);
|
||||
|
||||
m_group_selection ->setEnabled(GroupItemsCommand::canGroup(diagram_));
|
||||
m_ungroup_selection->setEnabled(GroupItemsCommand::canUngroup(diagram_));
|
||||
|
||||
// actions need only one editable item
|
||||
int selected_image = dc.count(DiagramContent::Images);
|
||||
|
||||
|
||||
@@ -253,6 +253,8 @@ class QETDiagramEditor : public QETMainWindow
|
||||
*m_rotate_texts, ///< Direct selected text items to a specific angle
|
||||
*m_find_element, ///< Find the selected element in the panel
|
||||
*m_group_selected_texts = nullptr,
|
||||
*m_group_selection = nullptr, ///< Group the selected items (#1070)
|
||||
*m_ungroup_selection = nullptr, ///< Ungroup the selected groups
|
||||
*m_close_file, ///< Close current project file
|
||||
*m_save_file, ///< Save current project
|
||||
*m_save_file_as, ///< Save current project as a specific file
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
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 "groupitemscommand.h"
|
||||
|
||||
#include "../diagram.h"
|
||||
#include "../itemgroups.h"
|
||||
#include "../qetgraphicsitem/diagramimageitem.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "../qetgraphicsitem/independenttextitem.h"
|
||||
#include "../qetgraphicsitem/qetshapeitem.h"
|
||||
|
||||
#include <QSet>
|
||||
|
||||
namespace {
|
||||
QList<QGraphicsObject *> selectedGroupable(Diagram *diagram)
|
||||
{
|
||||
QList<QGraphicsObject *> items;
|
||||
for (QGraphicsItem *item : diagram->selectedItems()) {
|
||||
if (GroupItemsCommand::isGroupable(item)) {
|
||||
items << item->toGraphicsObject();
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@return a command grouping the selected symbols, free texts, shapes and
|
||||
pictures of @a diagram into one new group, or nullptr if fewer than two
|
||||
are selected. The selection already holds whole groups (see
|
||||
ItemGroups::completeSelection()), so grouping items that were in groups
|
||||
merges those groups into the new one.
|
||||
*/
|
||||
GroupItemsCommand *GroupItemsCommand::group(Diagram *diagram)
|
||||
{
|
||||
if (!canGroup(diagram)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto command = new GroupItemsCommand(diagram, selectedGroupable(diagram), QUuid::createUuid());
|
||||
command->setText(QObject::tr("Grouper %n objet(s)", "", command->m_changes.size()));
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
@return a command taking every member of the selected groups of
|
||||
@a diagram out of its group, or nullptr if no group is selected.
|
||||
*/
|
||||
GroupItemsCommand *GroupItemsCommand::ungroup(Diagram *diagram)
|
||||
{
|
||||
if (!canUngroup(diagram)) {
|
||||
return nullptr;
|
||||
}
|
||||
QList<QGraphicsObject *> items;
|
||||
for (QGraphicsObject *item : selectedGroupable(diagram)) {
|
||||
if (!ItemGroups::groupOf(item).isNull()) {
|
||||
items << item;
|
||||
}
|
||||
}
|
||||
auto command = new GroupItemsCommand(diagram, items, QUuid());
|
||||
command->setText(QObject::tr("Dégrouper %n objet(s)", "", command->m_changes.size()));
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
@return true for the kinds of item a group can hold: symbols, free texts,
|
||||
shapes and pictures. Never conductors, which follow their symbols, and
|
||||
never a symbol's own texts, which belong to it.
|
||||
*/
|
||||
bool GroupItemsCommand::isGroupable(const QGraphicsItem *item)
|
||||
{
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
switch (item->type())
|
||||
{
|
||||
case Element::Type:
|
||||
case IndependentTextItem::Type:
|
||||
case QetShapeItem::Type:
|
||||
case DiagramImageItem::Type:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@return true if the selection of @a diagram holds at least two items
|
||||
that could be grouped, and is not already exactly one group.
|
||||
*/
|
||||
bool GroupItemsCommand::canGroup(Diagram *diagram)
|
||||
{
|
||||
if (!diagram || diagram->isReadOnly()) {
|
||||
return false;
|
||||
}
|
||||
const QList<QGraphicsObject *> items = selectedGroupable(diagram);
|
||||
if (items.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
QSet<QUuid> groups;
|
||||
for (QGraphicsObject *item : items) {
|
||||
groups << ItemGroups::groupOf(item);
|
||||
}
|
||||
return groups.size() > 1 || groups.contains(QUuid());
|
||||
}
|
||||
|
||||
/**
|
||||
@return true if the selection of @a diagram holds a grouped item.
|
||||
*/
|
||||
bool GroupItemsCommand::canUngroup(Diagram *diagram)
|
||||
{
|
||||
if (!diagram || diagram->isReadOnly()) {
|
||||
return false;
|
||||
}
|
||||
for (QGraphicsObject *item : selectedGroupable(diagram)) {
|
||||
if (!ItemGroups::groupOf(item).isNull()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief GroupItemsCommand::GroupItemsCommand
|
||||
@param diagram : diagram holding @a items
|
||||
@param items : items to put in @a group
|
||||
@param group : the new group, or a null uuid to ungroup
|
||||
*/
|
||||
GroupItemsCommand::GroupItemsCommand(Diagram *diagram,
|
||||
const QList<QGraphicsObject *> &items,
|
||||
const QUuid &group) :
|
||||
m_diagram(diagram),
|
||||
m_group(group)
|
||||
{
|
||||
for (QGraphicsObject *item : items) {
|
||||
m_changes.append({item, ItemGroups::groupOf(item)});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief GroupItemsCommand::undo
|
||||
*/
|
||||
void GroupItemsCommand::undo()
|
||||
{
|
||||
apply(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief GroupItemsCommand::redo
|
||||
*/
|
||||
void GroupItemsCommand::redo()
|
||||
{
|
||||
apply(true);
|
||||
}
|
||||
|
||||
void GroupItemsCommand::apply(bool redo)
|
||||
{
|
||||
if (!m_diagram) {
|
||||
return;
|
||||
}
|
||||
m_diagram->showMe();
|
||||
for (const Change &change : std::as_const(m_changes)) {
|
||||
if (change.item) {
|
||||
m_diagram->setItemGroup(change.item, redo ? m_group : change.before);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
#ifndef GROUPITEMSCOMMAND_H
|
||||
#define GROUPITEMSCOMMAND_H
|
||||
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QUndoCommand>
|
||||
#include <QUuid>
|
||||
|
||||
class Diagram;
|
||||
class QGraphicsItem;
|
||||
class QGraphicsObject;
|
||||
|
||||
/**
|
||||
@brief The GroupItemsCommand class
|
||||
Groups or ungroups the selected items of a diagram (discussion #1070).
|
||||
Undo gives every item back the exact group it had, so a redo after it
|
||||
still refers to the same groups.
|
||||
*/
|
||||
class GroupItemsCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
static GroupItemsCommand *group(Diagram *diagram);
|
||||
static GroupItemsCommand *ungroup(Diagram *diagram);
|
||||
static bool isGroupable(const QGraphicsItem *item);
|
||||
static bool canGroup(Diagram *diagram);
|
||||
static bool canUngroup(Diagram *diagram);
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
GroupItemsCommand(Diagram *diagram, const QList<QGraphicsObject *> &items, const QUuid &group);
|
||||
void apply(bool redo);
|
||||
|
||||
struct Change {
|
||||
QPointer<QGraphicsObject> item;
|
||||
QUuid before;
|
||||
};
|
||||
|
||||
QPointer<Diagram> m_diagram;
|
||||
QList<Change> m_changes;
|
||||
QUuid m_group;
|
||||
};
|
||||
|
||||
#endif // GROUPITEMSCOMMAND_H
|
||||
@@ -104,6 +104,13 @@ add_test(NAME tst_textgrid COMMAND tst_textgrid)
|
||||
target_include_directories(tst_textgrid PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_textgrid PRIVATE Qt::Test)
|
||||
|
||||
# itemgroups.cpp keeps group membership and the selection rule apart from
|
||||
# Diagram, so it is tested here on a plain scene of rectangles.
|
||||
add_executable(tst_itemgroups tst_itemgroups.cpp ${QET_DIR}/sources/itemgroups.cpp)
|
||||
add_test(NAME tst_itemgroups COMMAND tst_itemgroups)
|
||||
target_include_directories(tst_itemgroups PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_itemgroups PRIVATE Qt::Test Qt::Widgets Qt::Xml)
|
||||
|
||||
add_executable(
|
||||
tst_qetpalette
|
||||
tst_qetpalette.cpp
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
#include <QtTest>
|
||||
#include <QDomDocument>
|
||||
#include <QGraphicsRectItem>
|
||||
#include <QGraphicsScene>
|
||||
|
||||
#include "itemgroups.h"
|
||||
|
||||
class tst_itemgroups : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QGraphicsScene *scene = nullptr;
|
||||
QGraphicsRectItem *a = nullptr, *b = nullptr, *c = nullptr, *d = nullptr, *e = nullptr;
|
||||
QUuid g1, g2;
|
||||
|
||||
QGraphicsRectItem *add()
|
||||
{
|
||||
auto item = scene->addRect(0, 0, 10, 10);
|
||||
item->setFlag(QGraphicsItem::ItemIsSelectable);
|
||||
return item;
|
||||
}
|
||||
|
||||
QList<QGraphicsItem *> selection() const { return scene->selectedItems(); }
|
||||
|
||||
// Select exactly @a items, as Qt leaves the scene after a click.
|
||||
void select(const QList<QGraphicsItem *> &items)
|
||||
{
|
||||
scene->clearSelection();
|
||||
for (auto item : items) {
|
||||
item->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool selected(std::initializer_list<QGraphicsItem *> expected) const
|
||||
{
|
||||
QList<QGraphicsItem *> got = selection();
|
||||
if (got.size() != int(expected.size())) {
|
||||
return false;
|
||||
}
|
||||
for (auto item : expected) {
|
||||
if (!got.contains(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private slots:
|
||||
// a, b in g1; c alone; d, e in g2
|
||||
void init()
|
||||
{
|
||||
scene = new QGraphicsScene;
|
||||
a = add(); b = add(); c = add(); d = add(); e = add();
|
||||
g1 = QUuid::createUuid();
|
||||
g2 = QUuid::createUuid();
|
||||
ItemGroups::setGroup(a, g1);
|
||||
ItemGroups::setGroup(b, g1);
|
||||
ItemGroups::setGroup(d, g2);
|
||||
ItemGroups::setGroup(e, g2);
|
||||
}
|
||||
|
||||
void cleanup()
|
||||
{
|
||||
delete scene;
|
||||
scene = nullptr;
|
||||
}
|
||||
|
||||
void clickingAMemberSelectsTheGroup()
|
||||
{
|
||||
select({a});
|
||||
QVERIFY(ItemGroups::completeSelection(scene, {}, false));
|
||||
QVERIFY(selected({a, b}));
|
||||
}
|
||||
|
||||
// Releasing a click on one member of the selected group leaves only it
|
||||
// selected in Qt; the group must stay whole.
|
||||
void reclickingAMemberKeepsTheGroup()
|
||||
{
|
||||
select({b});
|
||||
ItemGroups::completeSelection(scene, {a, b}, false);
|
||||
QVERIFY(selected({a, b}));
|
||||
}
|
||||
|
||||
void ctrlClickingAMemberOffDeselectsTheGroup()
|
||||
{
|
||||
select({b});
|
||||
ItemGroups::completeSelection(scene, {a, b}, true);
|
||||
QVERIFY(selected({}));
|
||||
}
|
||||
|
||||
void ctrlClickingAMemberOnAddsTheGroup()
|
||||
{
|
||||
select({c, a});
|
||||
ItemGroups::completeSelection(scene, {c}, true);
|
||||
QVERIFY(selected({a, b, c}));
|
||||
}
|
||||
|
||||
void otherGroupsAreUntouched()
|
||||
{
|
||||
select({b, d, e});
|
||||
ItemGroups::completeSelection(scene, {a, b, d, e}, true);
|
||||
QVERIFY(selected({d, e}));
|
||||
}
|
||||
|
||||
void ungroupedItemsAreUntouched()
|
||||
{
|
||||
select({c});
|
||||
QVERIFY(!ItemGroups::completeSelection(scene, {}, false));
|
||||
QVERIFY(selected({c}));
|
||||
}
|
||||
|
||||
// A member taken off the scene (deleted, kept by the undo stack) must
|
||||
// not count as "deselected".
|
||||
void itemsLeavingTheSceneAreIgnored()
|
||||
{
|
||||
select({a, b});
|
||||
scene->removeItem(b);
|
||||
ItemGroups::completeSelection(scene, {a, b}, true);
|
||||
QVERIFY(selected({a}));
|
||||
delete b;
|
||||
b = nullptr;
|
||||
}
|
||||
|
||||
void xmlRoundTrip()
|
||||
{
|
||||
QDomDocument doc;
|
||||
QDomElement grouped = doc.createElement(QStringLiteral("element"));
|
||||
ItemGroups::write(grouped, a);
|
||||
QCOMPARE(ItemGroups::read(grouped), g1);
|
||||
|
||||
QDomElement alone = doc.createElement(QStringLiteral("element"));
|
||||
ItemGroups::write(alone, c);
|
||||
QVERIFY(!alone.hasAttribute(QStringLiteral("group")));
|
||||
QVERIFY(ItemGroups::read(alone).isNull());
|
||||
|
||||
ItemGroups::setGroup(a, QUuid());
|
||||
QVERIFY(ItemGroups::groupOf(a).isNull());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_itemgroups)
|
||||
#include "tst_itemgroups.moc"
|
||||
Reference in New Issue
Block a user