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:
ispyisail
2026-09-27 18:40:25 +13:00
parent 44cabbcfe4
commit f7591e0c9d
16 changed files with 788 additions and 7 deletions
+26 -1
View File
@@ -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);