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