mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-05 10:34: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:
+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
|
||||
|
||||
Reference in New Issue
Block a user