1 /* This file is part of the KDE project
2  * Copyright (C) 2006 Thomas Zander <zander@kde.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #include "KoShapeReorderCommand.h"
21 #include "KoShape.h"
22 #include "KoShape_p.h"
23 #include "KoShapeManager.h"
24 #include "KoShapeContainer.h"
25 
26 #include <klocalizedstring.h>
27 #include <FlakeDebug.h>
28 #include <limits.h>
29 #include <algorithm>
30 
31 
32 class KoShapeReorderCommandPrivate
33 {
34 public:
KoShapeReorderCommandPrivate(const QList<KoShape * > & s,QList<int> & ni)35     KoShapeReorderCommandPrivate(const QList<KoShape*> &s, QList<int> &ni)
36         : shapes(s), newIndexes(ni)
37     {
38     }
39 
40     QList<KoShape*> shapes;
41     QList<int> previousIndexes;
42     QList<int> newIndexes;
43 };
44 
KoShapeReorderCommand(const QList<KoShape * > & shapes,QList<int> & newIndexes,KUndo2Command * parent)45 KoShapeReorderCommand::KoShapeReorderCommand(const QList<KoShape*> &shapes, QList<int> &newIndexes, KUndo2Command *parent)
46     : KUndo2Command(parent),
47     d(new KoShapeReorderCommandPrivate(shapes, newIndexes))
48 {
49     Q_ASSERT(shapes.count() == newIndexes.count());
50     foreach (KoShape *shape, shapes)
51         d->previousIndexes.append(shape->zIndex());
52 
53     setText(kundo2_i18n("Reorder shapes"));
54 }
55 
~KoShapeReorderCommand()56 KoShapeReorderCommand::~KoShapeReorderCommand()
57 {
58     delete d;
59 }
60 
redo()61 void KoShapeReorderCommand::redo()
62 {
63     KUndo2Command::redo();
64     for (int i = 0; i < d->shapes.count(); i++) {
65         d->shapes.at(i)->update();
66         d->shapes.at(i)->setZIndex(d->newIndexes.at(i));
67         d->shapes.at(i)->update();
68     }
69 }
70 
undo()71 void KoShapeReorderCommand::undo()
72 {
73     KUndo2Command::undo();
74     for (int i = 0; i < d->shapes.count(); i++) {
75         d->shapes.at(i)->update();
76         d->shapes.at(i)->setZIndex(d->previousIndexes.at(i));
77         d->shapes.at(i)->update();
78     }
79 }
80 
prepare(KoShape * s,QHash<KoShape *,QList<KoShape * >> & newOrder,KoShapeManager * manager,KoShapeReorderCommand::MoveShapeType move)81 static void prepare(KoShape *s, QHash<KoShape*, QList<KoShape*> > &newOrder, KoShapeManager *manager, KoShapeReorderCommand::MoveShapeType move)
82 {
83     KoShapeContainer *parent = s->parent();
84     QHash<KoShape*, QList<KoShape*> >::iterator it(newOrder.find(parent));
85     if (it == newOrder.end()) {
86         QList<KoShape*> children;
87         if (parent != 0) {
88             children = parent->shapes();
89         }
90         else {
91             // get all toplevel shapes
92             children = manager->topLevelShapes();
93         }
94         std::sort(children.begin(), children.end(), KoShape::compareShapeZIndex);
95         // the append and prepend are needed so that the raise/lower of all shapes works as expected.
96         children.append(0);
97         children.prepend(0);
98         it = newOrder.insert(parent, children);
99     }
100     QList<KoShape *> & shapes(newOrder[parent]);
101     int index = shapes.indexOf(s);
102     if (index != -1) {
103         shapes.removeAt(index);
104         switch (move) {
105         case KoShapeReorderCommand::BringToFront:
106             index = shapes.size();
107             break;
108         case KoShapeReorderCommand::RaiseShape:
109             if (index < shapes.size()) {
110                 ++index;
111             }
112             break;
113         case KoShapeReorderCommand::LowerShape:
114             if (index > 0) {
115                 --index;
116             }
117             break;
118         case KoShapeReorderCommand::SendToBack:
119             index = 0;
120             break;
121         }
122         shapes.insert(index,s);
123     }
124 }
125 
126 // static
createCommand(const QList<KoShape * > & shapes,KoShapeManager * manager,MoveShapeType move,KUndo2Command * parent)127 KoShapeReorderCommand *KoShapeReorderCommand::createCommand(const QList<KoShape*> &shapes, KoShapeManager *manager, MoveShapeType move, KUndo2Command *parent)
128 {
129     QList<int> newIndexes;
130     QList<KoShape*> changedShapes;
131     QHash<KoShape*, QList<KoShape*> > newOrder;
132     QList<KoShape*> sortedShapes(shapes);
133     std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
134     if (move == BringToFront || move == LowerShape) {
135         for (int i = 0; i < sortedShapes.size(); ++i) {
136             prepare(sortedShapes.at(i), newOrder, manager, move);
137         }
138     }
139     else {
140         for (int i = sortedShapes.size() - 1; i >= 0; --i) {
141             prepare(sortedShapes.at(i), newOrder, manager, move);
142         }
143     }
144 
145     QHash<KoShape*, QList<KoShape*> >::ConstIterator newIt(newOrder.constBegin());
146     for (; newIt!= newOrder.constEnd(); ++newIt) {
147         QList<KoShape*> order(newIt.value());
148         order.removeAll(0);
149         int index = -KoShapePrivate::MaxZIndex - 1; // set minimum zIndex
150         int pos = 0;
151         for (; pos < order.size(); ++pos) {
152             if (order[pos]->zIndex() > index) {
153                 index = order[pos]->zIndex();
154             }
155             else {
156                 break;
157             }
158         }
159 
160         if (pos == order.size()) {
161             //nothing needs to be done
162             continue;
163         }
164         else if (pos <= order.size() / 2) {
165             // new index for the front
166             int startIndex = order[pos]->zIndex() - pos;
167             for (int i = 0; i < pos; ++i) {
168                 changedShapes.append(order[i]);
169                 newIndexes.append(startIndex++);
170             }
171         }
172         else {
173             //new index for the end
174             for (int i = pos; i < order.size(); ++i) {
175                 changedShapes.append(order[i]);
176                 newIndexes.append(++index);
177             }
178         }
179     }
180     Q_ASSERT(changedShapes.count() == newIndexes.count());
181     return changedShapes.isEmpty() ? 0: new KoShapeReorderCommand(changedShapes, newIndexes, parent);
182 }
183