File indexing completed on 2025-02-02 04:14:50

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2006 Thomas Zander <zander@kde.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "KoShapeReorderCommand.h"
0008 #include "KoShape.h"
0009 #include "KoShape_p.h"
0010 #include "KoShapeManager.h"
0011 #include "KoShapeContainer.h"
0012 
0013 #include <klocalizedstring.h>
0014 #include <FlakeDebug.h>
0015 #include <limits.h>
0016 
0017 KoShapeReorderCommand::IndexedShape::IndexedShape()
0018 {
0019 }
0020 
0021 KoShapeReorderCommand::IndexedShape::IndexedShape(KoShape *_shape)
0022     : zIndex(_shape->zIndex()), shape(_shape)
0023 {
0024 }
0025 
0026 bool KoShapeReorderCommand::IndexedShape::operator<(const KoShapeReorderCommand::IndexedShape &rhs) const
0027 {
0028     return zIndex < rhs.zIndex;
0029 }
0030 
0031 class KoShapeReorderCommandPrivate
0032 {
0033 public:
0034     KoShapeReorderCommandPrivate() {}
0035     KoShapeReorderCommandPrivate(const QList<KoShape*> &s, QList<int> &ni)
0036         : shapes(s), newIndexes(ni)
0037     {
0038     }
0039 
0040     QList<KoShape*> shapes;
0041     QList<int> previousIndexes;
0042     QList<int> newIndexes;
0043 };
0044 
0045 KoShapeReorderCommand::KoShapeReorderCommand(const QList<KoShape*> &shapes, QList<int> &newIndexes, KUndo2Command *parent)
0046     : KUndo2Command(parent),
0047       d(new KoShapeReorderCommandPrivate(shapes, newIndexes))
0048 {
0049     Q_ASSERT(shapes.count() == newIndexes.count());
0050     foreach (KoShape *shape, shapes)
0051         d->previousIndexes.append(shape->zIndex());
0052 
0053     setText(kundo2_i18n("Reorder shapes"));
0054 }
0055 
0056 KoShapeReorderCommand::KoShapeReorderCommand(const QList<IndexedShape> &shapes, KUndo2Command *parent)
0057     : KUndo2Command(parent),
0058       d(new KoShapeReorderCommandPrivate())
0059 {
0060     Q_FOREACH (const IndexedShape &index, shapes) {
0061         d->shapes.append(index.shape);
0062         d->newIndexes.append(index.zIndex);
0063         d->previousIndexes.append(index.shape->zIndex());
0064     }
0065 
0066     setText(kundo2_i18n("Reorder shapes"));
0067 }
0068 
0069 KoShapeReorderCommand::~KoShapeReorderCommand()
0070 {
0071     delete d;
0072 }
0073 
0074 void KoShapeReorderCommand::redo()
0075 {
0076     KUndo2Command::redo();
0077     for (int i = 0; i < d->shapes.count(); i++) {
0078         // z-index cannot change the bounding rect of the shape, so
0079         // no united updates needed
0080         d->shapes.at(i)->setZIndex(d->newIndexes.at(i));
0081         d->shapes.at(i)->update();
0082     }
0083 }
0084 
0085 void KoShapeReorderCommand::undo()
0086 {
0087     KUndo2Command::undo();
0088     for (int i = 0; i < d->shapes.count(); i++) {
0089         // z-index cannot change the bounding rect of the shape, so
0090         // no united updates needed
0091         d->shapes.at(i)->setZIndex(d->previousIndexes.at(i));
0092         d->shapes.at(i)->update();
0093     }
0094 }
0095 
0096 static void prepare(KoShape *s, QMap<KoShape*, QList<KoShape*> > &newOrder, KoShapeManager *manager, KoShapeReorderCommand::MoveShapeType move)
0097 {
0098     KoShapeContainer *parent = s->parent();
0099     QMap<KoShape*, QList<KoShape*> >::iterator it(newOrder.find(parent));
0100     if (it == newOrder.end()) {
0101         QList<KoShape*> children;
0102         if (parent != 0) {
0103             children = parent->shapes();
0104         }
0105         else {
0106             // get all toplevel shapes
0107             children = manager->topLevelShapes();
0108         }
0109         std::sort(children.begin(), children.end(), KoShape::compareShapeZIndex);
0110         // the append and prepend are needed so that the raise/lower of all shapes works as expected.
0111         children.append(0);
0112         children.prepend(0);
0113         it = newOrder.insert(parent, children);
0114     }
0115     QList<KoShape *> & shapes(newOrder[parent]);
0116     int index = shapes.indexOf(s);
0117     if (index != -1) {
0118         shapes.removeAt(index);
0119         switch (move) {
0120         case KoShapeReorderCommand::BringToFront:
0121             index = shapes.size();
0122             break;
0123         case KoShapeReorderCommand::RaiseShape:
0124             if (index < shapes.size()) {
0125                 ++index;
0126             }
0127             break;
0128         case KoShapeReorderCommand::LowerShape:
0129             if (index > 0) {
0130                 --index;
0131             }
0132             break;
0133         case KoShapeReorderCommand::SendToBack:
0134             index = 0;
0135             break;
0136         }
0137         shapes.insert(index,s);
0138     }
0139 }
0140 
0141 // static
0142 KoShapeReorderCommand *KoShapeReorderCommand::createCommand(const QList<KoShape*> &shapes, KoShapeManager *manager, MoveShapeType move, KUndo2Command *parent)
0143 {
0144     /**
0145      * TODO: this method doesn't handle the case when one of the shapes
0146      *       has maximum or minimum zIndex value (which is 16-bit in our case)!
0147      */
0148 
0149     QList<int> newIndexes;
0150     QList<KoShape*> changedShapes;
0151     QMap<KoShape*, QList<KoShape*> > newOrder;
0152     QList<KoShape*> sortedShapes(shapes);
0153     std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
0154     if (move == BringToFront || move == LowerShape) {
0155         for (int i = 0; i < sortedShapes.size(); ++i) {
0156             prepare(sortedShapes.at(i), newOrder, manager, move);
0157         }
0158     }
0159     else {
0160         for (int i = sortedShapes.size() - 1; i >= 0; --i) {
0161             prepare(sortedShapes.at(i), newOrder, manager, move);
0162         }
0163     }
0164 
0165     QMap<KoShape*, QList<KoShape*> >::iterator newIt(newOrder.begin());
0166     for (; newIt!= newOrder.end(); ++newIt) {
0167         QList<KoShape*> order(newIt.value());
0168         order.removeAll(0);
0169         int index = -KoShape::maxZIndex - 1; // set minimum zIndex
0170         int pos = 0;
0171         for (; pos < order.size(); ++pos) {
0172             if (order[pos]->zIndex() > index) {
0173                 index = order[pos]->zIndex();
0174             }
0175             else {
0176                 break;
0177             }
0178         }
0179 
0180         if (pos == order.size()) {
0181             //nothing needs to be done
0182             continue;
0183         }
0184         else if (pos <= order.size() / 2) {
0185             // new index for the front
0186             int startIndex = order[pos]->zIndex() - pos;
0187             for (int i = 0; i < pos; ++i) {
0188                 changedShapes.append(order[i]);
0189                 newIndexes.append(startIndex++);
0190             }
0191         }
0192         else {
0193             //new index for the end
0194             for (int i = pos; i < order.size(); ++i) {
0195                 changedShapes.append(order[i]);
0196                 newIndexes.append(++index);
0197             }
0198         }
0199     }
0200     Q_ASSERT(changedShapes.count() == newIndexes.count());
0201     return changedShapes.isEmpty() ? 0: new KoShapeReorderCommand(changedShapes, newIndexes, parent);
0202 }
0203 
0204 KoShapeReorderCommand *KoShapeReorderCommand::mergeInShape(QList<KoShape *> shapes, KoShape *newShape, KUndo2Command *parent)
0205 {
0206     std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
0207 
0208     QList<KoShape*> reindexedShapes;
0209     QList<int> reindexedIndexes;
0210 
0211     const int originalShapeZIndex = newShape->zIndex();
0212     int newShapeZIndex = originalShapeZIndex;
0213     int lastOccupiedShapeZIndex = originalShapeZIndex + 1;
0214 
0215     Q_FOREACH (KoShape *shape, shapes) {
0216         if (shape == newShape) continue;
0217 
0218         const int zIndex = shape->zIndex();
0219 
0220         if (newShapeZIndex == originalShapeZIndex) {
0221             if (zIndex == originalShapeZIndex) {
0222                 newShapeZIndex = originalShapeZIndex + 1;
0223                 lastOccupiedShapeZIndex = newShapeZIndex;
0224 
0225                 reindexedShapes << newShape;
0226                 reindexedIndexes << newShapeZIndex;
0227             }
0228         } else {
0229             if (zIndex >= newShapeZIndex &&
0230                 zIndex <= lastOccupiedShapeZIndex) {
0231 
0232                 lastOccupiedShapeZIndex = zIndex + 1;
0233                 reindexedShapes << shape;
0234                 reindexedIndexes << lastOccupiedShapeZIndex;
0235             }
0236         }
0237     }
0238 
0239     return !reindexedShapes.isEmpty() ? new KoShapeReorderCommand(reindexedShapes, reindexedIndexes, parent) : 0;
0240 }
0241 
0242 QList<KoShapeReorderCommand::IndexedShape>
0243 KoShapeReorderCommand::homogenizeZIndexes(QList<KoShapeReorderCommand::IndexedShape> shapes)
0244 {
0245     if (shapes.isEmpty()) return shapes;
0246 
0247     // the shapes are expected to be sorted, we just need to adjust the indexes
0248 
0249     int lastIndex = shapes.begin()->zIndex;
0250 
0251     auto it = shapes.begin() + 1;
0252     while (it != shapes.end()) {
0253         if (it->zIndex <= lastIndex) {
0254             it->zIndex = lastIndex + 1;
0255         }
0256         lastIndex = it->zIndex;
0257         ++it;
0258     }
0259 
0260     const int overflowSize = shapes.last().zIndex - int(std::numeric_limits<qint16>::max());
0261 
0262     if (overflowSize > 0) {
0263         if (shapes.first().zIndex - overflowSize > int(std::numeric_limits<qint16>::min())) {
0264             for (auto it = shapes.begin(); it != shapes.end(); ++it) {
0265                 it->zIndex -= overflowSize;
0266             }
0267         } else {
0268             int index = shapes.size() < int(std::numeric_limits<qint16>::max()) ?
0269                         0 :
0270                         int(std::numeric_limits<qint16>::max()) - shapes.size();
0271 
0272             for (auto it = shapes.begin(); it != shapes.end(); ++it) {
0273                 it->zIndex = index;
0274                 index++;
0275             }
0276         }
0277     }
0278 
0279     return shapes;
0280 }
0281 
0282 QList<KoShapeReorderCommand::IndexedShape>
0283 KoShapeReorderCommand::homogenizeZIndexesLazy(QList<KoShapeReorderCommand::IndexedShape> shapes)
0284 {
0285     shapes = homogenizeZIndexes(shapes);
0286     // remove shapes that didn't change
0287     for (auto it = shapes.begin(); it != shapes.end();) {
0288         if (it->zIndex == it->shape->zIndex()) {
0289             it = shapes.erase(it);
0290         } else {
0291             ++it;
0292         }
0293     }
0294 
0295     return shapes;
0296 }
0297 
0298 QList<KoShapeReorderCommand::IndexedShape>
0299 KoShapeReorderCommand::mergeDownShapes(QList<KoShape *> shapesBelow, QList<KoShape *> shapesAbove)
0300 {
0301     std::sort(shapesBelow.begin(), shapesBelow.end(), KoShape::compareShapeZIndex);
0302     std::sort(shapesAbove.begin(), shapesAbove.end(), KoShape::compareShapeZIndex);
0303 
0304     QList<IndexedShape> shapes;
0305     Q_FOREACH (KoShape *shape, shapesBelow) {
0306         shapes.append(IndexedShape(shape));
0307     }
0308 
0309     Q_FOREACH (KoShape *shape, shapesAbove) {
0310         shapes.append(IndexedShape(shape));
0311     }
0312 
0313     return homogenizeZIndexesLazy(shapes);
0314 }
0315 
0316 QDebug operator<<(QDebug dbg, const KoShapeReorderCommand::IndexedShape &indexedShape)
0317 {
0318     dbg.nospace() << "IndexedShape (" << indexedShape.shape << ", " << indexedShape.zIndex << ")";
0319     return dbg.space();
0320 }