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 }