...
 
Commits (2)
......@@ -141,6 +141,7 @@ QPointF KoFlake::toAbsolute(const QPointF &relative, const QSizeF &size)
#include <QTransform>
#include "kis_debug.h"
#include "kis_algebra_2d.h"
#include "KoShapeGroup.h"
namespace {
......@@ -446,6 +447,17 @@ bool KoFlake::ReorderShapes::changeShapesZIndexes(const QList<KoShape *> &shapes
return true;
}
bool KoFlake::ReorderShapes::changeShapesZIndexes(const QList<KoFlake::ReorderShapes::IndexedShape>& indexedShapes)
{
QList<KoShape *> shapes;
QList<int> indexes;
Q_FOREACH (const IndexedShape &index, indexedShapes) {
shapes.append(index.shape);
indexes.append(index.zIndex);
}
return changeShapesZIndexes(shapes, indexes);
}
bool KoFlake::ReorderShapes::doReordering(const QList<KoShape*> &shapes, KoShapeManager *manager, MoveShapeType move)
{
/**
......@@ -538,12 +550,12 @@ bool KoFlake::ReorderShapes::mergeInShape(QList<KoShape *> shapes, KoShape *newS
}
} else {
if (zIndex >= newShapeZIndex &&
zIndex <= lastOccupiedShapeZIndex) {
zIndex <= lastOccupiedShapeZIndex) {
lastOccupiedShapeZIndex = zIndex + 1;
reindexedShapes << shape;
reindexedIndexes << lastOccupiedShapeZIndex;
}
reindexedShapes << shape;
reindexedIndexes << lastOccupiedShapeZIndex;
}
}
}
......@@ -628,6 +640,129 @@ KoFlake::ReorderShapes::mergeDownShapes(QList<KoShape *> shapesBelow, QList<KoSh
return homogenizeZIndexesLazy(shapes);
}
void KoFlake::GroupShapes::groupShapes(KoShapeContainer* container, QList<KoShape *> shapes, bool shouldNormalize)
{
std::stable_sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
std::function<QRectF()> containerBoundingRect = [container, shapes]() {
QRectF bound;
if (container->shapeCount() > 0) {
bound = container->absoluteTransformation(0).mapRect(container->outlineRect());
}
Q_FOREACH (KoShape* shape, shapes) {
bound |= shape->absoluteTransformation(0).mapRect(shape->outlineRect());
}
return bound;
};
if (shouldNormalize && dynamic_cast<KoShapeGroup*>(container)) {
QRectF bound = containerBoundingRect();
QPointF oldGroupPosition = container->absolutePosition(KoFlake::TopLeft);
container->setAbsolutePosition(bound.topLeft(), KoFlake::TopLeft);
container->setSize(bound.size());
if (container->shapeCount() > 0) {
// the group has changed position and so have the group child shapes
// -> we need compensate the group position change
QPointF positionOffset = oldGroupPosition - bound.topLeft();
Q_FOREACH (KoShape * child, container->shapes()) {
child->setAbsolutePosition(child->absolutePosition() + positionOffset);
}
}
}
QTransform groupTransform = container->absoluteTransformation(0).inverted();
QList<KoShape*> containerShapes(container->shapes());
std::stable_sort(containerShapes.begin(), containerShapes.end(), KoShape::compareShapeZIndex);
QList<KoFlake::ReorderShapes::IndexedShape> indexedShapes;
Q_FOREACH (KoShape *shape, containerShapes) {
indexedShapes.append(KoFlake::ReorderShapes::IndexedShape(shape));
}
QList<KoFlake::ReorderShapes::IndexedShape> prependIndexedShapes;
Q_FOREACH (KoShape *shape, shapes) {
// test if they inherit the same parent
if (!shape->hasCommonParent(container) ||
!KoShape::compareShapeZIndex(shape, container)) {
indexedShapes.append(KoFlake::ReorderShapes::IndexedShape(shape));
} else {
prependIndexedShapes.append(KoFlake::ReorderShapes::IndexedShape(shape));
}
}
indexedShapes = prependIndexedShapes + indexedShapes;
indexedShapes = KoFlake::ReorderShapes::homogenizeZIndexesLazy(indexedShapes);
if (!indexedShapes.isEmpty()) {
KoFlake::ReorderShapes::changeShapesZIndexes(indexedShapes);
}
uint shapeCount = shapes.count();
for (uint i = 0; i < shapeCount; ++i) {
KoShape * shape = shapes[i];
shape->applyAbsoluteTransformation(groupTransform);
container->addShape(shape);
}
}
void KoFlake::GroupShapes::ungroupShapes(KoShapeContainer* container, QList<KoShape *> shapes, QList<KoShape *> topLevelShapes)
{
std::stable_sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
std::sort(topLevelShapes.begin(), topLevelShapes.end(), KoShape::compareShapeZIndex);
using IndexedShape = KoFlake::ReorderShapes::IndexedShape;
KoShapeContainer *newParent = container->parent();
QList<IndexedShape> indexedSiblings;
QList<KoShape*> perspectiveSiblings;
if (newParent) {
perspectiveSiblings = newParent->shapes();
std::sort(perspectiveSiblings.begin(), perspectiveSiblings.end(), KoShape::compareShapeZIndex);
} else {
perspectiveSiblings = topLevelShapes;
}
Q_FOREACH (KoShape *shape, perspectiveSiblings) {
indexedSiblings.append(shape);
}
// find the place where the ungrouped shapes should be inserted
// (right on the top of their current container)
auto insertIt = std::upper_bound(indexedSiblings.begin(),
indexedSiblings.end(),
IndexedShape(container));
std::copy(shapes.begin(), shapes.end(),
std::inserter(indexedSiblings, insertIt));
indexedSiblings = ReorderShapes::homogenizeZIndexesLazy(indexedSiblings);
const QTransform ungroupTransform = container->absoluteTransformation(0);
for (auto it = shapes.begin(); it != shapes.end(); ++it) {
KoShape *shape = *it;
KIS_SAFE_ASSERT_RECOVER(shape->parent() == container) { continue; }
shape->setParent(newParent);
shape->applyAbsoluteTransformation(ungroupTransform);
}
if (!indexedSiblings.isEmpty()) {
ReorderShapes::changeShapesZIndexes(indexedSiblings);
}
}
QDebug operator<<(QDebug dbg, const KoFlake::ReorderShapes::IndexedShape &indexedShape)
{
dbg.nospace() << "IndexedShape (" << indexedShape.shape << ", " << indexedShape.zIndex << ")";
......
......@@ -24,6 +24,7 @@
#include <boost/operators.hpp>
#include "kritaflake_export.h"
#include <QList>
class QGradient;
class QRectF;
......@@ -33,6 +34,7 @@ class QSizeF;
class KoShape;
class QTransform;
class KoShapeManager;
class KoShapeContainer;
#include <Qt>
......@@ -177,6 +179,8 @@ namespace KoFlake
KRITAFLAKE_EXPORT bool changeShapesZIndexes(const QList<KoShape *> &shapes, QList<int> &newIndexes);
KRITAFLAKE_EXPORT bool changeShapesZIndexes(const QList<IndexedShape> &indexedShapes);
/**
* Create a new KoShapeReorderCommand by calculating the new indexes required to move the shapes
* according to the move parameter.
......@@ -188,6 +192,7 @@ namespace KoFlake
*/
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.
......@@ -224,6 +229,29 @@ namespace KoFlake
QList<IndexedShape> mergeDownShapes(QList<KoShape*> shapesBelow, QList<KoShape*> shapesAbove);
}
// from KoShapeGroupCommand/KoShapeUngroupCommand
namespace GroupShapes
{
/**
* Command to group a set of shapes into a predefined container.
* @param container the container to group the shapes under.
* @param shapes a list of all the shapes that should be grouped.
* @param shouldNormalize shows whether the shapes should be normalized by the container
*/
KRITAFLAKE_EXPORT void groupShapes(KoShapeContainer* container, QList<KoShape*> shapes,
bool shouldNormalize = false);
/**
* Command to ungroup a set of shapes from one parent container.
* @param container the group to ungroup the shapes from.
* @param shapes a list of all the shapes that should be ungrouped.
* @param topLevelShapes a list of top level shapes.
*/
KRITAFLAKE_EXPORT void ungroupShapes(KoShapeContainer* container, QList<KoShape*> shapes,
QList<KoShape*> topLevelShapes = QList<KoShape*>());
}
}
KRITAFLAKE_EXPORT QDebug operator<<(QDebug dbg, const KoFlake::ReorderShapes::IndexedShape &indexedShape);
......
......@@ -590,7 +590,7 @@ void KoShapeManager::notifyShapeChanged(KoShape *shape)
if (d->aggregate4update.contains(shape)) {
return;
}
const bool wasEmpty = d->aggregate4update.isEmpty();
d->aggregate4update.insert(shape);
d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex());
......@@ -599,10 +599,6 @@ void KoShapeManager::notifyShapeChanged(KoShape *shape)
Q_FOREACH (KoShape *child, container->shapes())
notifyShapeChanged(child);
}
if (wasEmpty && !d->aggregate4update.isEmpty()) {
d->updateTreeCompressor.start();
}
}
QList<KoShape*> KoShapeManager::shapes() const
......
......@@ -44,10 +44,8 @@ public:
canvas(c),
tree(4, 2),
q(shapeManager),
shapeInterface(shapeManager),
updateTreeCompressor(100, KisSignalCompressor::FIRST_INACTIVE)
shapeInterface(shapeManager)
{
connect(&updateTreeCompressor, SIGNAL(timeout()), q, SLOT(updateTree()));
}
~Private() {
......@@ -118,7 +116,6 @@ public:
QHash<KoShape*, int> shapeIndexesBeforeUpdate;
KoShapeManager *q;
KoShapeManager::ShapeInterface shapeInterface;
KisThreadSafeSignalCompressor updateTreeCompressor;
};
#endif
......@@ -172,6 +172,7 @@ public:
#include <KoGradientBackground.h>
#include "KoShapeGradientHandles.h"
#include "ShapeGradientEditStrategy.h"
#include <KoCanvasStrokeHelperBase.h>
class DefaultTool::MoveGradientHandleInteractionFactory : public KoInteractionStrategyFactory
{
......@@ -829,11 +830,11 @@ bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers)
QList<KoShape *> shapes = koSelection()->selectedEditableShapes();
if (!shapes.isEmpty()) {
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2 *>(canvas());
KisNodeReplaceBasedStrokeStrategy::runInStrokeOnce(kisCanvas->image().data(),
kisCanvas,
kundo2_i18n("Move shapes (new) (lambda)"),
[shapes, x, y]() { KoFlake::moveShapes(shapes, QPointF(x, y)); return KisNodeReplaceBasedStrokeStrategy::COMMAND_NEEDED; });
canvas()->strokeHelper()->run(kundo2_i18n("Move shapes (new) (lambda)"),
[shapes, x, y]() {
KoFlake::moveShapes(shapes, QPointF(x, y));
return true;
});
result = true;
}
}
......@@ -897,13 +898,12 @@ void DefaultTool::deleteSelection()
shapes << s;
}
if (!shapes.empty()) {
KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
if (kisCanvas) {
KisNodeSP currentNode = kisCanvas->imageView()->currentNode();
KisNodeSP originalState(currentNode->clone());
canvas()->shapeController()->removeShapes(shapes);
canvas()->addCommand(new KisShapeDeleteCommand(currentNode, originalState));
}
KoShapeController* shapeController = canvas()->shapeController();
canvas()->strokeHelper()->run(kundo2_i18n("Remove shapes"),
[shapeController, shapes]() {
shapeController->removeShapes(shapes);
return true;
});
}
}
......@@ -1096,19 +1096,20 @@ void DefaultTool::selectionGroup()
const int groupZIndex = selectedShapes.last()->zIndex();
KoShapeGroup *group = new KoShapeGroup();
group->setZIndex(groupZIndex);
// TODO what if only one shape is left?
KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes"));
new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
canvas()->shapeController()->addShapeDirect(group, 0, cmd);
new KoShapeGroupCommand(group, selectedShapes, true, cmd);
new KoKeepShapesSelectedCommand({}, {group}, canvas()->selectedShapesProxy(), true, cmd);
canvas()->addCommand(cmd);
// update selection so we can ungroup immediately again
selection->deselectAll();
selection->select(group);
canvas()->strokeHelper()->run(kundo2_i18n("Group shapes"),
[selectedShapes, this, selection, groupZIndex]() {
KoShapeGroup *group = new KoShapeGroup();
group->setZIndex(groupZIndex);
// TODO what if only one shape is left?
selection->deselectAll();
canvas()->shapeController()->addShapeDirect(group, 0);
KoFlake::GroupShapes::groupShapes(group, selectedShapes, /* shouldNormalize = */ true);
selection->deselectAll();
selection->select(group);
return true;
});
}
void DefaultTool::selectionUngroup()
......@@ -1119,28 +1120,33 @@ void DefaultTool::selectionUngroup()
QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
KUndo2Command *cmd = 0;
QList<KoShape*> newShapes;
// add a ungroup command for each found shape container to the macro command
Q_FOREACH (KoShape *shape, selectedShapes) {
KoShapeGroup *group = dynamic_cast<KoShapeGroup *>(shape);
if (group) {
if (!cmd) {
cmd = new KUndo2Command(kundo2_i18n("Ungroup shapes"));
new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
canvas()->strokeHelper()->run(kundo2_i18n("Ungroup shapes"),
[selectedShapes, selection, this]() {
QList<KoShape*> newShapes;
bool changed = false;
Q_FOREACH (KoShape* shape, selectedShapes) {
KoShapeGroup* group = dynamic_cast<KoShapeGroup*>(shape);
if (group) {
if (!changed) {
changed = true;
selection->deselectAll();
}
newShapes << group->shapes();
KoFlake::GroupShapes::ungroupShapes(group, group->shapes(),
group->parent() ? QList<KoShape*>() : shapeManager()->topLevelShapes());
canvas()->shapeController()->removeShape(group);
}
newShapes << group->shapes();
new KoShapeUngroupCommand(group, group->shapes(),
group->parent() ? QList<KoShape *>() : shapeManager()->topLevelShapes(),
cmd);
canvas()->shapeController()->removeShape(group, cmd);
}
}
if (cmd) {
new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd);
canvas()->addCommand(cmd);
}
if (changed) {
selection->deselectAll();
Q_FOREACH (KoShape *shape, newShapes) {
selection->select(shape);
}
}
return changed;
});
}
void DefaultTool::selectionTransform(int transformAction)
......