1 /* This file is part of the KDE project
2  * Copyright (C) 2006,2010 Thomas Zander <zander@kde.org>
3  * Copyright (C) 2006,2007 Jan Hambrecht <jaham@gmx.net>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 #include "KoShapeGroupCommand.h"
22 #include "KoShape.h"
23 #include "KoShapeGroup.h"
24 #include "KoShapeContainer.h"
25 
26 #include <commands/KoShapeReorderCommand.h>
27 
28 #include <klocalizedstring.h>
29 
30 // static
createCommand(KoShapeContainer * container,const QList<KoShape * > & shapes,bool shouldNormalize)31 KoShapeGroupCommand * KoShapeGroupCommand::createCommand(KoShapeContainer *container, const QList<KoShape *> &shapes, bool shouldNormalize)
32 {
33     QList<KoShape*> orderedShapes(shapes);
34     if (!orderedShapes.isEmpty()) {
35         KoShape * top = orderedShapes.last();
36         container->setParent(top->parent());
37         container->setZIndex(top->zIndex());
38     }
39 
40     return new KoShapeGroupCommand(container, orderedShapes, shouldNormalize, 0);
41 }
42 
43 class KoShapeGroupCommandPrivate
44 {
45 public:
46     KoShapeGroupCommandPrivate(KoShapeContainer *container, const QList<KoShape *> &shapes, bool _shouldNormalize);
47     QRectF containerBoundingRect();
48 
49     QList<KoShape*> shapes; ///<list of shapes to be grouped
50     bool shouldNormalize; ///< Adjust the coordinate system of the group to its origin into the topleft of the group
51     KoShapeContainer *container; ///< the container where the grouping should be for.
52     QList<KoShapeContainer*> oldParents; ///< the old parents of the shapes
53 
54     QScopedPointer<KUndo2Command> shapesReorderCommand;
55 };
56 
KoShapeGroupCommandPrivate(KoShapeContainer * c,const QList<KoShape * > & s,bool _shouldNormalize)57 KoShapeGroupCommandPrivate::KoShapeGroupCommandPrivate(KoShapeContainer *c, const QList<KoShape *> &s, bool _shouldNormalize)
58     : shapes(s),
59       shouldNormalize(_shouldNormalize),
60       container(c)
61 {
62     std::stable_sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
63 }
64 
KoShapeGroupCommand(KoShapeContainer * container,const QList<KoShape * > & shapes,KUndo2Command * parent)65 KoShapeGroupCommand::KoShapeGroupCommand(KoShapeContainer *container, const QList<KoShape *> &shapes, KUndo2Command *parent)
66     : KoShapeGroupCommand(container, shapes, false, parent)
67 {
68 }
69 
KoShapeGroupCommand(KoShapeContainer * container,const QList<KoShape * > & shapes,bool shouldNormalize,KUndo2Command * parent)70 KoShapeGroupCommand::KoShapeGroupCommand(KoShapeContainer *container, const QList<KoShape *> &shapes,
71                                          bool shouldNormalize, KUndo2Command *parent)
72     : KUndo2Command(parent),
73       d(new KoShapeGroupCommandPrivate(container, shapes, shouldNormalize))
74 {
75     Q_FOREACH (KoShape* shape, d->shapes) {
76         d->oldParents.append(shape->parent());
77     }
78 
79     if (d->container->shapes().isEmpty()) {
80         setText(kundo2_i18n("Group shapes"));
81     } else {
82         setText(kundo2_i18n("Add shapes to group"));
83     }
84 }
85 
~KoShapeGroupCommand()86 KoShapeGroupCommand::~KoShapeGroupCommand()
87 {
88 }
89 
redo()90 void KoShapeGroupCommand::redo()
91 {
92     KUndo2Command::redo();
93 
94     if (d->shouldNormalize &&  dynamic_cast<KoShapeGroup*>(d->container)) {
95         QRectF bound = d->containerBoundingRect();
96         QPointF oldGroupPosition = d->container->absolutePosition(KoFlake::TopLeft);
97         d->container->setAbsolutePosition(bound.topLeft(), KoFlake::TopLeft);
98         d->container->setSize(bound.size());
99 
100         if (d->container->shapeCount() > 0) {
101             // the group has changed position and so have the group child shapes
102             // -> we need compensate the group position change
103             QPointF positionOffset = oldGroupPosition - bound.topLeft();
104             Q_FOREACH (KoShape * child, d->container->shapes())
105                 child->setAbsolutePosition(child->absolutePosition() + positionOffset);
106         }
107     }
108 
109     QTransform groupTransform = d->container->absoluteTransformation().inverted();
110 
111     QList<KoShape*> containerShapes(d->container->shapes());
112     std::stable_sort(containerShapes.begin(), containerShapes.end(), KoShape::compareShapeZIndex);
113 
114     QList<KoShapeReorderCommand::IndexedShape> indexedShapes;
115     Q_FOREACH (KoShape *shape, containerShapes) {
116         indexedShapes.append(KoShapeReorderCommand::IndexedShape(shape));
117     }
118 
119     QList<KoShapeReorderCommand::IndexedShape> prependIndexedShapes;
120 
121     Q_FOREACH (KoShape *shape, d->shapes) {
122         // test if they inherit the same parent
123 
124         if (!shape->hasCommonParent(d->container) ||
125             !KoShape::compareShapeZIndex(shape, d->container)) {
126 
127             indexedShapes.append(KoShapeReorderCommand::IndexedShape(shape));
128         } else {
129             prependIndexedShapes.append(KoShapeReorderCommand::IndexedShape(shape));
130         }
131     }
132 
133     indexedShapes = prependIndexedShapes + indexedShapes;
134     indexedShapes = KoShapeReorderCommand::homogenizeZIndexesLazy(indexedShapes);
135 
136     if (!indexedShapes.isEmpty()) {
137         d->shapesReorderCommand.reset(new KoShapeReorderCommand(indexedShapes));
138         d->shapesReorderCommand->redo();
139     }
140 
141     uint shapeCount = d->shapes.count();
142     for (uint i = 0; i < shapeCount; ++i) {
143         KoShape * shape = d->shapes[i];
144 
145         shape->applyAbsoluteTransformation(groupTransform);
146         d->container->addShape(shape);
147     }
148 
149 
150 }
151 
undo()152 void KoShapeGroupCommand::undo()
153 {
154     KUndo2Command::undo();
155 
156     QTransform ungroupTransform = d->container->absoluteTransformation();
157     for (int i = 0; i < d->shapes.count(); i++) {
158         KoShape * shape = d->shapes[i];
159         d->container->removeShape(shape);
160         if (d->oldParents.at(i)) {
161             d->oldParents.at(i)->addShape(shape);
162         }
163         shape->applyAbsoluteTransformation(ungroupTransform);
164     }
165 
166     if (d->shapesReorderCommand) {
167         d->shapesReorderCommand->undo();
168         d->shapesReorderCommand.reset();
169     }
170 
171     if (d->shouldNormalize && dynamic_cast<KoShapeGroup*>(d->container)) {
172         QPointF oldGroupPosition = d->container->absolutePosition(KoFlake::TopLeft);
173         if (d->container->shapeCount() > 0) {
174             bool boundingRectInitialized = false;
175             QRectF bound;
176             Q_FOREACH (KoShape * shape, d->container->shapes()) {
177                 if (! boundingRectInitialized) {
178                     bound = shape->boundingRect();
179                     boundingRectInitialized = true;
180                 } else
181                     bound = bound.united(shape->boundingRect());
182             }
183             // the group has changed position and so have the group child shapes
184             // -> we need compensate the group position change
185             QPointF positionOffset = oldGroupPosition - bound.topLeft();
186             Q_FOREACH (KoShape * child, d->container->shapes())
187                 child->setAbsolutePosition(child->absolutePosition() + positionOffset);
188 
189             d->container->setAbsolutePosition(bound.topLeft(), KoFlake::TopLeft);
190             d->container->setSize(bound.size());
191         }
192     }
193 }
194 
containerBoundingRect()195 QRectF KoShapeGroupCommandPrivate::containerBoundingRect()
196 {
197     QRectF bound;
198     if (container->shapeCount() > 0) {
199         bound = container->absoluteTransformation().mapRect(container->outlineRect());
200     }
201 
202     Q_FOREACH (KoShape *shape, shapes) {
203         bound |= shape->absoluteTransformation().mapRect(shape->outlineRect());
204     }
205 
206     return bound;
207 }
208