Commit 80d9a036 authored by Tusooa Zhu's avatar Tusooa Zhu 🅱

Port shape reordering to strokes

parent 7e755c3e
......@@ -22,6 +22,8 @@
#include "KoFlake.h"
#include "KoShape.h"
#include <KoShapeContainer.h>
#include <KoShapeManager.h>
#include <QGradient>
#include <math.h>
......@@ -371,3 +373,263 @@ void KoFlake::moveShapes(const QList<KoShape *>& shapes, const QList<QPointF>& n
}
}
KoFlake::ReorderShapes::IndexedShape::IndexedShape()
{
}
KoFlake::ReorderShapes::IndexedShape::IndexedShape(KoShape *_shape)
: zIndex(_shape->zIndex()), shape(_shape)
{
}
bool KoFlake::ReorderShapes::IndexedShape::operator<(const KoFlake::ReorderShapes::IndexedShape& rhs) const
{
return zIndex < rhs.zIndex;
}
static void prepareForReordering(KoShape *s, QMap<KoShape*, QList<KoShape*> > &newOrder, KoShapeManager *manager, KoFlake::ReorderShapes::MoveShapeType move)
{
KoShapeContainer *parent = s->parent();
QMap<KoShape*, QList<KoShape*> >::iterator it(newOrder.find(parent));
if (it == newOrder.end()) {
QList<KoShape*> children;
if (parent != 0) {
children = parent->shapes();
}
else {
// get all toplevel shapes
children = manager->topLevelShapes();
}
std::sort(children.begin(), children.end(), KoShape::compareShapeZIndex);
// the append and prepend are needed so that the raise/lower of all shapes works as expected.
children.append(0);
children.prepend(0);
it = newOrder.insert(parent, children);
}
QList<KoShape *> & shapes(newOrder[parent]);
int index = shapes.indexOf(s);
if (index != -1) {
shapes.removeAt(index);
switch (move) {
case KoFlake::ReorderShapes::BringToFront:
index = shapes.size();
break;
case KoFlake::ReorderShapes::RaiseShape:
if (index < shapes.size()) {
++index;
}
break;
case KoFlake::ReorderShapes::LowerShape:
if (index > 0) {
--index;
}
break;
case KoFlake::ReorderShapes::SendToBack:
index = 0;
break;
}
shapes.insert(index,s);
}
}
bool KoFlake::ReorderShapes::changeShapesZIndexes(const QList<KoShape *> &shapes, QList<int> &newIndexes)
{
if (shapes.isEmpty()) {
return false;
}
for (int i = 0; i < shapes.count(); i++) {
// z-index cannot change the bounding rect of the shape, so
// no united updates needed
shapes.at(i)->setZIndex(newIndexes.at(i));
shapes.at(i)->update();
}
return true;
}
bool KoFlake::ReorderShapes::doReordering(const QList<KoShape*> &shapes, KoShapeManager *manager, MoveShapeType move)
{
/**
* TODO: this method doesn't handle the case when one of the shapes
* has maximum or minimum zIndex value (which is 16-bit in our case)!
*/
QList<int> newIndexes;
QList<KoShape*> changedShapes;
QMap<KoShape*, QList<KoShape*> > newOrder;
QList<KoShape*> sortedShapes(shapes);
std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
if (move == BringToFront || move == LowerShape) {
for (int i = 0; i < sortedShapes.size(); ++i) {
prepareForReordering(sortedShapes.at(i), newOrder, manager, move);
}
}
else {
for (int i = sortedShapes.size() - 1; i >= 0; --i) {
prepareForReordering(sortedShapes.at(i), newOrder, manager, move);
}
}
QMap<KoShape*, QList<KoShape*> >::iterator newIt(newOrder.begin());
for (; newIt!= newOrder.end(); ++newIt) {
QList<KoShape*> order(newIt.value());
order.removeAll(0);
int index = -KoShape::maxZIndex - 1; // set minimum zIndex
int pos = 0;
for (; pos < order.size(); ++pos) {
if (order[pos]->zIndex() > index) {
index = order[pos]->zIndex();
}
else {
break;
}
}
if (pos == order.size()) {
//nothing needs to be done
continue;
}
else if (pos <= order.size() / 2) {
// new index for the front
int startIndex = order[pos]->zIndex() - pos;
for (int i = 0; i < pos; ++i) {
changedShapes.append(order[i]);
newIndexes.append(startIndex++);
}
}
else {
//new index for the end
for (int i = pos; i < order.size(); ++i) {
changedShapes.append(order[i]);
newIndexes.append(++index);
}
}
}
Q_ASSERT(changedShapes.count() == newIndexes.count());
if (! changedShapes.isEmpty()) {
return changeShapesZIndexes(changedShapes, newIndexes);
} else {
return false;
}
}
bool KoFlake::ReorderShapes::mergeInShape(QList<KoShape *> shapes, KoShape *newShape)
{
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QList<KoShape*> reindexedShapes;
QList<int> reindexedIndexes;
const int originalShapeZIndex = newShape->zIndex();
int newShapeZIndex = originalShapeZIndex;
int lastOccupiedShapeZIndex = originalShapeZIndex + 1;
Q_FOREACH (KoShape *shape, shapes) {
if (shape == newShape) continue;
const int zIndex = shape->zIndex();
if (newShapeZIndex == originalShapeZIndex) {
if (zIndex == originalShapeZIndex) {
newShapeZIndex = originalShapeZIndex + 1;
lastOccupiedShapeZIndex = newShapeZIndex;
reindexedShapes << newShape;
reindexedIndexes << newShapeZIndex;
}
} else {
if (zIndex >= newShapeZIndex &&
zIndex <= lastOccupiedShapeZIndex) {
lastOccupiedShapeZIndex = zIndex + 1;
reindexedShapes << shape;
reindexedIndexes << lastOccupiedShapeZIndex;
}
}
}
if (!reindexedShapes.isEmpty()) {
return changeShapesZIndexes(reindexedShapes, reindexedIndexes);
} else {
return false;
}
}
QList<KoFlake::ReorderShapes::IndexedShape>
KoFlake::ReorderShapes::homogenizeZIndexes(QList<KoFlake::ReorderShapes::IndexedShape> shapes)
{
if (shapes.isEmpty()) return shapes;
// the shapes are expected to be sorted, we just need to adjust the indexes
int lastIndex = shapes.begin()->zIndex;
auto it = shapes.begin() + 1;
while (it != shapes.end()) {
if (it->zIndex <= lastIndex) {
it->zIndex = lastIndex + 1;
}
lastIndex = it->zIndex;
++it;
}
const int overflowSize = shapes.last().zIndex - int(std::numeric_limits<qint16>::max());
if (overflowSize > 0) {
if (shapes.first().zIndex - overflowSize > int(std::numeric_limits<qint16>::min())) {
for (auto it = shapes.begin(); it != shapes.end(); ++it) {
it->zIndex -= overflowSize;
}
} else {
int index = shapes.size() < int(std::numeric_limits<qint16>::max()) ?
0 :
int(std::numeric_limits<qint16>::max()) - shapes.size();
for (auto it = shapes.begin(); it != shapes.end(); ++it) {
it->zIndex = index;
index++;
}
}
}
return shapes;
}
QList<KoFlake::ReorderShapes::IndexedShape>
KoFlake::ReorderShapes::homogenizeZIndexesLazy(QList<KoFlake::ReorderShapes::IndexedShape> shapes)
{
shapes = homogenizeZIndexes(shapes);
// remove shapes that didn't change
for (auto it = shapes.begin(); it != shapes.end();) {
if (it->zIndex == it->shape->zIndex()) {
it = shapes.erase(it);
} else {
++it;
}
}
return shapes;
}
QList<KoFlake::ReorderShapes::IndexedShape>
KoFlake::ReorderShapes::mergeDownShapes(QList<KoShape *> shapesBelow, QList<KoShape *> shapesAbove)
{
std::sort(shapesBelow.begin(), shapesBelow.end(), KoShape::compareShapeZIndex);
std::sort(shapesAbove.begin(), shapesAbove.end(), KoShape::compareShapeZIndex);
QList<IndexedShape> shapes;
Q_FOREACH (KoShape *shape, shapesBelow) {
shapes.append(IndexedShape(shape));
}
Q_FOREACH (KoShape *shape, shapesAbove) {
shapes.append(IndexedShape(shape));
}
return homogenizeZIndexesLazy(shapes);
}
QDebug operator<<(QDebug dbg, const KoFlake::ReorderShapes::IndexedShape &indexedShape)
{
dbg.nospace() << "IndexedShape (" << indexedShape.shape << ", " << indexedShape.zIndex << ")";
return dbg.space();
}
......@@ -18,9 +18,11 @@
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOFLAKE_H
#define KOFLAKE_H
#include <boost/operators.hpp>
#include "kritaflake_export.h"
class QGradient;
......@@ -30,6 +32,7 @@ class QSizeF;
class KoShape;
class QTransform;
class KoShapeManager;
#include <Qt>
......@@ -151,6 +154,78 @@ namespace KoFlake
KRITAFLAKE_EXPORT void moveShapes(const QList<KoShape *> &shapes,
const QList<QPointF> &newPositions,
KoFlake::AnchorPosition anchor);
// the following comes from KoShapeReorderCommand
namespace ReorderShapes
{
enum MoveShapeType {
RaiseShape, ///< raise the selected shape to the level that it is above the shape that is on top of it.
LowerShape, ///< Lower the selected shape to the level that it is below the shape that is below it.
BringToFront, ///< Raise the selected shape to be on top of all shapes.
SendToBack ///< Lower the selected shape to be below all other shapes.
};
struct KRITAFLAKE_EXPORT IndexedShape : boost::less_than_comparable<IndexedShape> {
IndexedShape();
IndexedShape(KoShape *_shape);
bool operator<(const IndexedShape &rhs) const;
int zIndex = 0;
KoShape *shape = 0;
};
KRITAFLAKE_EXPORT bool changeShapesZIndexes(const QList<KoShape *> &shapes, QList<int> &newIndexes);
/**
* Create a new KoShapeReorderCommand by calculating the new indexes required to move the shapes
* according to the move parameter.
* @param shapes all the shapes that should be moved.
* @param manager the shapeManager that contains all the shapes that could have their indexes changed.
* @param move the moving type.
* @param parent the parent command for grouping purposes.
* @return true if reordering happened, false otherwise
*/
KRITAFLAKE_EXPORT bool doReordering(const QList<KoShape*> &shapes, KoShapeManager *manager, MoveShapeType move);
/**
* @brief mergeInShape adjust zIndex of all the \p shapes and \p newShape to
* avoid collisions between \p shapes and \p newShape.
*
* Note1: \p newShape may or may not be contained in \p shapes, there
* is no difference.
* Note2: the collisions inside \p shapes are ignored. They are just
* adjusted to avoid collisions with \p newShape only
* @param shapes list of shapes
* @param newShape the new shape
* @return true if reordering happened, false otherwise
*/
KRITAFLAKE_EXPORT bool mergeInShape(QList<KoShape*> shapes, KoShape *newShape);
/**
* Recalculates the attached z-indexes of \p shapes so that all indexes go
* strictly in ascending order and no shapes have repetitive indexes. The
* physical order of the shapes in the array is not changed, on the indexes
* in IndexedShape are corrected.
*/
QList<IndexedShape> homogenizeZIndexes(QList<IndexedShape> shapes);
/**
* Convenience version of homogenizeZIndexes() that removes all the IndexedShape
* objects, which z-index didn't change during homogenization. In a result
* you get a list that can be passed to KoShapeReorderCommand directly.
*/
QList<IndexedShape> homogenizeZIndexesLazy(QList<IndexedShape> shapes);
/**
* Put all the shapes in \p shapesAbove above the shapes in \p shapesBelow, adjusting their
* z-index values.
*/
QList<IndexedShape> mergeDownShapes(QList<KoShape*> shapesBelow, QList<KoShape*> shapesAbove);
}
}
KRITAFLAKE_EXPORT QDebug operator<<(QDebug dbg, const KoFlake::ReorderShapes::IndexedShape &indexedShape);
#endif
......@@ -140,16 +140,19 @@ void KisNodeReplaceBasedStrokeStrategy::setChanged(bool changed)
m_d->nodeChanged = changed;
}
void KisNodeReplaceBasedStrokeStrategy::runInStrokeOnce(KisStrokesFacade *strokesFacade, KisCanvas2 *canvas, const KUndo2MagicString &name, std::function<void()> callback, bool createCommand)
void KisNodeReplaceBasedStrokeStrategy::runInStrokeOnce(KisStrokesFacade* strokesFacade, KisCanvas2* canvas, const KUndo2MagicString& name, std::function<CallbackStatus()> callback)
{
KisNodeReplaceBasedStrokeStrategy *strategy = new KisNodeReplaceBasedStrokeStrategy("LAMBDA_STROKE", canvas, name);
KisStrokeId stroke = strokesFacade->startStroke(strategy);
strokesFacade->addJob(stroke, new KisRunnableStrokeJobData(callback,
KisStrokeJobData::BARRIER,
KisStrokeJobData::EXCLUSIVE));
strokesFacade->addJob(stroke, new KisRunnableStrokeJobData(
[strategy, callback]()
{
if (callback() == COMMAND_NEEDED) {
strategy->setChanged(true);
}
},
KisStrokeJobData::BARRIER,
KisStrokeJobData::EXCLUSIVE));
if (createCommand) {
strokesFacade->addJob(stroke, new KisRunnableStrokeJobData([strategy]() { strategy->setChanged(true); }));
}
strokesFacade->endStroke(stroke);
}
......@@ -51,7 +51,13 @@ public:
void setChanged(bool changed);
public:
static void runInStrokeOnce(KisStrokesFacade *strokesFacade, KisCanvas2 *canvas, const KUndo2MagicString &name, std::function<void()> callback, bool createCommand);
enum CallbackStatus : int
{
COMMAND_NEEDED,
COMMAND_UNNEEDED
};
static void runInStrokeOnce(KisStrokesFacade *strokesFacade, KisCanvas2 *canvas, const KUndo2MagicString &name, std::function<CallbackStatus()> callback);
private:
struct Private;
......
......@@ -833,8 +833,7 @@ bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers)
KisNodeReplaceBasedStrokeStrategy::runInStrokeOnce(kisCanvas->image().data(),
kisCanvas,
kundo2_i18n("Move shapes (new) (lambda)"),
[shapes, x, y]() { KoFlake::moveShapes(shapes, QPointF(x, y)); },
/* createCommand = */ true);
[shapes, x, y]() { KoFlake::moveShapes(shapes, QPointF(x, y)); return KisNodeReplaceBasedStrokeStrategy::COMMAND_NEEDED; });
result = true;
}
}
......@@ -1392,25 +1391,25 @@ void DefaultTool::selectionDistribute(int _distribute)
void DefaultTool::selectionBringToFront()
{
selectionReorder(KoShapeReorderCommand::BringToFront);
selectionReorder(KoFlake::ReorderShapes::BringToFront);
}
void DefaultTool::selectionMoveUp()
{
selectionReorder(KoShapeReorderCommand::RaiseShape);
selectionReorder(KoFlake::ReorderShapes::RaiseShape);
}
void DefaultTool::selectionMoveDown()
{
selectionReorder(KoShapeReorderCommand::LowerShape);
selectionReorder(KoFlake::ReorderShapes::LowerShape);
}
void DefaultTool::selectionSendToBack()
{
selectionReorder(KoShapeReorderCommand::SendToBack);
selectionReorder(KoFlake::ReorderShapes::SendToBack);
}
void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order)
void DefaultTool::selectionReorder(KoFlake::ReorderShapes::MoveShapeType order)
{
KoSelection *selection = koSelection();
if (!selection) {
......@@ -1422,10 +1421,11 @@ void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order)
return;
}
KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, shapeManager(), order);
if (cmd) {
canvas()->addCommand(cmd);
}
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2 *>(canvas());
KoShapeManager *manager = shapeManager();
KisNodeReplaceBasedStrokeStrategy::runInStrokeOnce(kisCanvas->image().data(),
kisCanvas, kundo2_i18n("Reorder shapes (new)"),
[selectedShapes, manager, order]() { return KoFlake::ReorderShapes::doReordering(selectedShapes, manager, order) ? KisNodeReplaceBasedStrokeStrategy::COMMAND_NEEDED : KisNodeReplaceBasedStrokeStrategy::COMMAND_UNNEEDED; });
}
QList<QPointer<QWidget> > DefaultTool::createOptionWidgets()
......
......@@ -161,7 +161,7 @@ private:
void addMappedAction(QSignalMapper *mapper, const QString &actionId, int type);
void selectionReorder(KoShapeReorderCommand::MoveShapeType order);
void selectionReorder(KoFlake::ReorderShapes::MoveShapeType order);
bool moveSelection(int direction, Qt::KeyboardModifiers modifiers);
/// Returns selection rectangle adjusted by handle proximity threshold
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment