...
 
Commits (82)
......@@ -84,8 +84,8 @@ if (WIN32)
ExternalProject_Add(
ext_qt
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
URL https://download.qt.io/official_releases/qt/5.12/5.12.4/single/qt-everywhere-src-5.12.4.tar.xz
URL_MD5 dda95b0239d13c5276834177af3a8588
URL https://download.qt.io/official_releases/qt/5.12/5.12.4/single/qt-everywhere-src-5.12.4.zip
URL_MD5 16526e08adfad46e8e686b8af984b9b5
PATCH_COMMAND ${ext_qt_PATCH_COMMAND}
......
......@@ -2,11 +2,11 @@ project(krita)
message(STATUS "Using CMake version: ${CMAKE_VERSION}")
cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR)
set(MIN_QT_VERSION 5.9.0)
set(MIN_FRAMEWORKS_VERSION 5.18.0)
set(MIN_FRAMEWORKS_VERSION 5.44.0)
if (POLICY CMP0002)
cmake_policy(SET CMP0002 OLD)
......
......@@ -63,8 +63,6 @@
#define MEMALIGN_FREE(p) free((p))
#endif
const int alpha_pos = 3;
enum AlphaRange {
ALPHA_ZERO,
ALPHA_UNIT,
......
......@@ -89,7 +89,7 @@
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>Move right</text>
<text>Move right more</text>
</Action>
</Actions>
</ActionCollection>
......@@ -14,7 +14,7 @@
[General]
name=Krita Default
version=3
version=4
[Pan Canvas]
0={0;4;[];0;0;2}
......@@ -68,3 +68,6 @@ version=3
7={7;2;[1000021,20];1;0;0}
8={6;1;[33];0;0;0}
9={8;2;[1000021,1000023,20];1;0;0}
[Zoom and Rotate Canvas]
0={0;4;[];0;0;5}
......@@ -29,7 +29,7 @@ Name[lt]=Manga šablonas
Name[nb]=Manga-mal
Name[nds]=Manga-Vörlaag
Name[nl]=Manga-sjabloon
Name[nn]=Manga-mal
Name[nn]=Japansk teikneserie (manga)
Name[pl]=Szablon Mangi
Name[pt]=Modelo Manga
Name[pt_BR]=Modelo de mangá
......
......@@ -23,7 +23,7 @@ Name[kk]=Текстура 1k 32 бит скаляр
Name[ko]=텍스처 1k 32비트 스칼라
Name[nb]=Tekstur 1k 32bit skalar
Name[nl]=Textuur 1k 32bit scalar
Name[nn]=Tekstur 1k 32-bits skalar
Name[nn]=Tekstur 1k 32-bits gråtonar
Name[pl]=Tekstura 1k 32bit skalar
Name[pt]=Textura 1k 32-bits escalar
Name[pt_BR]=Textura 1k 32bits escalar
......
......@@ -23,7 +23,7 @@ Name[kk]=Текстура 2k 32 бит скаляр
Name[ko]=텍스처 2k 32비트 스칼라
Name[nb]=Tekstur 2k 32bit skalar
Name[nl]=Textuur 2k 32bit scalar
Name[nn]=Tekstur 2k 32-bits skalar
Name[nn]=Tekstur 2k 32-bits gråtonar
Name[pl]=Tekstura 2k 32bit skalar
Name[pt]=Textura 2k 32-bits escalar
Name[pt_BR]=Textura 2k 32bits escalar
......
......@@ -23,7 +23,7 @@ Name[kk]=Текстура 4k 32 бит скаляр
Name[ko]=텍스처 4k 32비트 스칼라
Name[nb]=Tekstur 4k 32bit skalar
Name[nl]=Textuur 4k 32bit scalar
Name[nn]=Tekstur 4k 32-bits skalar
Name[nn]=Tekstur 4k 32-bits gråtonar
Name[pl]=Tekstura 4k 32bit skalar
Name[pt]=Textura 4k 32-bits escalar
Name[pt_BR]=Textura 4k 32bits escalar
......
......@@ -23,7 +23,7 @@ Name[kk]=Текстура 8k 32 бит скаляр
Name[ko]=텍스처 8k 32비트 스칼라
Name[nb]=Tekstur 8k 32bit skalar
Name[nl]=Textuur 8k 32bit scalar
Name[nn]=Tekstur 8k 32-bits skalar
Name[nn]=Tekstur 8k 32-bits gråtonar
Name[pl]=Tekstura 8k 32bit skalar
Name[pt]=Textura 8k 32-bits escalar
Name[pt_BR]=Textura 8k 32bits escalar
......
......@@ -326,8 +326,8 @@ xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0 http://www.kde.org
<Action name="color_filters"/>
<Action name="decor_filters"/>
<Action name="edge_filters"/>
<Action name="enhance_filters"/>
<Action name="emboss_filters"/>
<Action name="enhance_filters"/>
<Action name="map_filters"/>
<Action name="nonphotorealistic_filters"/>
<Action name="other_filters"/>
......
......@@ -248,7 +248,7 @@
<caption xml:lang="ca">La finestra d'inici ara proporciona les darreres noticies quant al Krita.</caption>
<caption xml:lang="ca-valencia">La finestra d'inici ara proporciona les darreres noticies quant al Krita.</caption>
<caption xml:lang="es">La ventana de bienvenida también le proporciona ahora las últimas noticias sobre Krita.</caption>
<caption xml:lang="id">Window pemulaian kini juga memberikan kamu kabar terbaru tentang Krita.</caption>
<caption xml:lang="id">Window pemulaian kini juga memberikan kamu kabar terkini tentang Krita.</caption>
<caption xml:lang="it">La finestra di avvio ora fornisce anche le ultime novità su Krita.</caption>
<caption xml:lang="ko">시작 창에서 Krita의 최신 소식을 볼 수 있습니다.</caption>
<caption xml:lang="nl">Het opstartvenster geeft u nu ook you het laatste nieuws over Krita.</caption>
......@@ -338,6 +338,8 @@
</screenshots>
<categories>
<category>Graphics</category>
<category>2DGraphics</category>
<category>RasterGraphics</category>
</categories>
<project_group>KDE</project_group>
<provides>
......
......@@ -149,7 +149,7 @@ Comment[zh_CN]=数字绘画
Comment[zh_TW]=數位繪畫
Type=Application
Icon=calligrakrita
Categories=Qt;KDE;Graphics;
Categories=Qt;KDE;Graphics;2DGraphics;RasterGraphics;
X-KDE-NativeMimeType=application/x-krita
X-KDE-ExtraNativeMimeTypes=
StartupNotify=true
......
......@@ -16,6 +16,7 @@ add_subdirectory( image )
add_subdirectory( ui )
add_subdirectory( impex )
add_subdirectory( libkis )
add_subdirectory( stroke )
if (NOT APPLE AND HAVE_QT_QUICK)
add_subdirectory( libqml )
endif()
......
......@@ -501,7 +501,7 @@ bool KoCreatePathTool::addPathShapeImpl(KoPathShape *pathShape, bool tryMergeOnl
return false;
}
KUndo2Command *cmd = canvas()->shapeController()->addShape(pathShape, 0);
KUndo2Command *cmd = canvas()->shapeController()->addShape(pathShape, 0, 0);
KIS_SAFE_ASSERT_RECOVER(cmd) {
canvas()->updateCanvas(pathShape->boundingRect());
delete pathShape;
......
......@@ -402,7 +402,7 @@ void KoPencilTool::addPathShape(KoPathShape* path, bool closePath)
}
}
KUndo2Command * cmd = canvas()->shapeController()->addShape(path, 0);
KUndo2Command * cmd = canvas()->shapeController()->addShape(path, 0, 0);
if (cmd) {
KoSelection *selection = canvas()->shapeManager()->selection();
selection->deselectAll();
......
......@@ -33,7 +33,8 @@ enum CommandId {
ChangeRectangleShapeId,
ChangePathShapePointId,
ChangePathShapeControlPointId,
ChangePaletteId
ChangePaletteId,
TransformToolId
};
}
......
......@@ -26,6 +26,7 @@ class KRITACOMMAND_EXPORT KUndo2CommandExtraData
{
public:
virtual ~KUndo2CommandExtraData();
virtual KUndo2CommandExtraData* clone() const = 0;
};
#endif /* __KUNDO2COMMANDEXTRADATA_H */
......@@ -16,6 +16,7 @@ set(kritaflake_SRCS
KoGradientHelper.cpp
KoFlake.cpp
KoCanvasBase.cpp
KoCanvasStrokeHelperBase.cpp
KoResourceManager_p.cpp
KoDerivedResourceConverter.cpp
KoResourceUpdateMediator.cpp
......@@ -242,7 +243,7 @@ target_include_directories(kritaflake
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/text>
)
target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand KF5::WidgetsAddons Qt5::Svg)
target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand kritastroke KF5::WidgetsAddons Qt5::Svg)
set_target_properties(kritaflake PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
......
......@@ -129,3 +129,8 @@ KoSnapGuide * KoCanvasBase::snapGuide() const
{
return d->snapGuide;
}
KoCanvasStrokeHelperBase *KoCanvasBase::strokeHelper() const
{
return 0;
}
......@@ -20,6 +20,7 @@
Boston, MA 02110-1301, USA.
*/
class KoCanvasStrokeHelperBase;
#ifndef KOCANVASBASE_H
#define KOCANVASBASE_H
......@@ -243,6 +244,8 @@ public:
/// called by KoCanvasController to set the controller that handles this canvas.
void setCanvasController(KoCanvasController *controller);
virtual KoCanvasStrokeHelperBase *strokeHelper() const;
private:
// we need a KoShapeControllerBase so that it can work
KoCanvasBase();
......
......@@ -220,7 +220,7 @@ void Viewport::handleDropEvent(QDropEvent *event)
m_draggedShape->setAbsolutePosition(newPos);
KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape, 0);
KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape, 0, 0);
if (cmd) {
m_parent->canvas()->addCommand(cmd);
......
/* This file is part of the KDE project
*
* Copyright (C) 2009 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com>
* Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
......@@ -18,21 +18,29 @@
* Boston, MA 02110-1301, USA.
*/
#ifndef CONNECTIONTOOLFACTORY_H
#define CONNECTIONTOOLFACTORY_H
#include "KoCanvasStrokeHelperBase.h"
#include "KoToolFactoryBase.h"
#include <KoCanvasBase.h>
/// The factory for the ConnectionTool
class ConnectionToolFactory : public KoToolFactoryBase
struct KoCanvasStrokeHelperBase::Private
{
public:
/// Constructor
ConnectionToolFactory();
/// Destructor
~ConnectionToolFactory() override;
/// reimplemented
KoToolBase *createTool(KoCanvasBase *canvas) override;
Private(KoCanvasBase *canvas) : canvas(canvas) {}
~Private() = default;
KoCanvasBase *canvas;
};
#endif
KoCanvasStrokeHelperBase::KoCanvasStrokeHelperBase(KoCanvasBase* canvas)
: m_d(new Private(canvas))
{
}
KoCanvasStrokeHelperBase::~KoCanvasStrokeHelperBase()
{
}
KoCanvasBase *KoCanvasStrokeHelperBase::canvas() const
{
return m_d->canvas;
}
/* This file is part of the KDE project
*
* Copyright (C) 2011 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
......@@ -18,43 +18,44 @@
* Boston, MA 02110-1301, USA.
*/
#ifndef MOVECONNECTIONPOINTSTRATEGY_H
#define MOVECONNECTIONPOINTSTRATEGY_H
#ifndef KO_CANVAS_STROKE_HELPER_BASE_H_
#define KO_CANVAS_STROKE_HELPER_BASE_H_
#include <KoInteractionStrategy.h>
#include <KoConnectionPoint.h>
#include <QPointF>
#include <kundo2magicstring.h>
class KoShape;
#include <kis_stroke_job_strategy.h>
class MoveConnectionPointStrategy : public KoInteractionStrategy
{
public:
/// Constructor
MoveConnectionPointStrategy(KoShape *shape, int connectionPointId, KoToolBase *parent);
/// Destructor
~MoveConnectionPointStrategy() override;
/// reimplemented from KoInteractionStrategy
void paint(QPainter &painter, const KoViewConverter &converter) override;
/// reimplemented from KoInteractionStrategy
void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) override;
#include <kritaflake_export.h>
/// reimplemented from KoInteractionStrategy
KUndo2Command *createCommand() override;
class KoCanvasBase;
/// reimplemented from KoInteractionStrategy
void cancelInteraction() override;
/// reimplemented from KoInteractionStrategy
void finishInteraction(Qt::KeyboardModifiers modifiers) override;
/**
* Base class for a helper that lets code in lambda functions run in a stroke.
*/
class KRITAFLAKE_EXPORT KoCanvasStrokeHelperBase
{
public:
KoCanvasStrokeHelperBase(KoCanvasBase *canvas);
virtual ~KoCanvasStrokeHelperBase();
KoCanvasBase *canvas() const;
/**
* Runs lambda in a stroke which replaces the state of the current node.
* If lambda() returns true, the stroke will create an undo command named name
* and add it to the document.
*
* We set the default sequentiality and exclusivity to barrier and exclusive
* respectively because the old code is often not guaranteed to be thread-safe.
*/
virtual void run(const KUndo2MagicString &name,
std::function<bool()> lambda,
KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::BARRIER,
KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::EXCLUSIVE) = 0;
private:
KoShape *m_shape;
int m_connectionPointId;
KoConnectionPoint m_oldPoint;
KoConnectionPoint m_newPoint;
struct Private;
QScopedPointer<Private> m_d;
};
#endif // MOVECONNECTIONPOINTSTRATEGY_H
#endif
......@@ -60,7 +60,7 @@ public:
// reimplemented from KoShapeBackground
bool loadStyle(KoOdfLoadingContext & context, const QSizeF &shapeSize) override;
private:
struct Private;
class Private;
QSharedDataPointer<Private> d;
};
......
......@@ -22,6 +22,8 @@
#include "KoFlake.h"
#include "KoShape.h"
#include <KoShapeContainer.h>
#include <KoShapeManager.h>
#include <QGradient>
#include <math.h>
......@@ -344,3 +346,290 @@ QPointF KoFlake::anchorToPoint(AnchorPosition anchor, const QRectF rect, bool *v
return rect.topLeft();
}
}
void KoFlake::moveShapes(const QList<KoShape *>& shapes, const QPointF& offset)
{
QList<QPointF> newPositions;
Q_FOREACH (KoShape *shape, shapes) {
const QPointF pos = shape->absolutePosition();
newPositions << pos + offset;
}
moveShapes(shapes, newPositions, KoFlake::Center);
}
void KoFlake::moveShapes(const QList<KoShape *>& shapes, const QList<QPointF>& newPositions, KoFlake::AnchorPosition anchor)
{
KIS_ASSERT_RECOVER_NOOP(shapes.length() == newPositions.length());
for (int i = 0; i < shapes.count(); i++) {
KoShape *shape = shapes.at(i);
const QRectF oldDirtyRect = shape->boundingRect();
shape->setAbsolutePosition(newPositions.at(i), anchor);
shape->updateAbsolute(oldDirtyRect | shape->boundingRect());
}
}
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>
......@@ -145,6 +148,84 @@ namespace KoFlake
const QPointF &absoluteStillPoint,
bool useGlobalMode,
bool usePostScaling, const QTransform &postScalingCoveringTransform);
KRITAFLAKE_EXPORT void moveShapes(const QList<KoShape *> &shapes, const QPointF &offset);
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
......@@ -29,11 +29,12 @@
namespace KoFlake {
/// @return true if shapes is not empty, false otherwise
template <typename ModifyFunction>
auto modifyShapesStrokes(QList<KoShape*> shapes, ModifyFunction modifyFunction)
-> decltype(modifyFunction(KoShapeStrokeSP()), (KUndo2Command*)(0))
-> decltype(modifyFunction(KoShapeStrokeSP()), bool())
{
if (shapes.isEmpty()) return 0;
if (shapes.isEmpty()) return false;
QList<KoShapeStrokeModelSP> newStrokes;
......@@ -49,10 +50,12 @@ template <typename ModifyFunction>
modifyFunction(newStroke);
newStrokes << newStroke;
shape->update();
shape->setStroke(newStroke);
shape->update();
}
return new KoShapeStrokeCommand(shapes, newStrokes);
return true;
}
template <class Policy>
......
......@@ -468,10 +468,10 @@ QRectF KoPathShape::outlineRect() const
QPainterPath KoPathShape::outline() const
{
QPainterPath path;
Q_FOREACH (KoSubpath * subpath, d->subpaths) {
KoPathPoint * lastPoint = subpath->first();
Q_FOREACH (const KoSubpath * subpath, d->subpaths) {
const KoPathPoint * lastPoint = subpath->first();
bool activeCP = false;
Q_FOREACH (KoPathPoint * currPoint, *subpath) {
Q_FOREACH (const KoPathPoint * currPoint, *subpath) {
KoPathPoint::PointProperties currProperties = currPoint->properties();
if (currPoint == subpath->first()) {
if (currProperties & KoPathPoint::StartSubpath) {
......@@ -498,7 +498,7 @@ QPainterPath KoPathShape::outline() const
}
if (currProperties & KoPathPoint::CloseSubpath && currProperties & KoPathPoint::StopSubpath) {
// add curve when there is a curve on the way to the first point
KoPathPoint * firstPoint = subpath->first();
const KoPathPoint * firstPoint = subpath->first();
Q_ASSERT(!qIsNaNPoint(firstPoint->point()));
if (currPoint->activeControlPoint2() && firstPoint->activeControlPoint1()) {
path.cubicTo(
......
......@@ -3,6 +3,7 @@
* Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2011 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
......@@ -30,6 +31,7 @@
#include "commands/KoShapeCreateCommand.h"
#include "commands/KoShapeDeleteCommand.h"
#include "commands/KoShapeConnectionChangeCommand.h"
#include "commands/KoShapeReorderCommand.h"
#include "KoCanvasBase.h"
#include "KoShapeConfigWidgetBase.h"
#include "KoShapeFactoryBase.h"
......@@ -102,11 +104,85 @@ public:
return addShapesDirect({shape}, parentShape, parent);
}
void addShape(KoShape *shape, bool showDialog, KoShapeContainer *parentShape) {
if (canvas) {
if (showDialog && !shape->shapeId().isEmpty()) {
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shape->shapeId());
Q_ASSERT(factory);
qint16 z = 0;
Q_FOREACH (KoShape *sh, canvas->shapeManager()->shapes()) {
z = qMax(z, sh->zIndex());
}
shape->setZIndex(z + 1);
// show config dialog.
KPageDialog *dialog = new KPageDialog(canvas->canvasWidget());
dialog->setWindowTitle(i18n("%1 Options", factory->name()));
int pageCount = 0;
QList<KoShapeConfigWidgetBase*> widgets;
Q_FOREACH (KoShapeConfigWidgetBase* panel, factory->createShapeOptionPanels()) {
if (! panel->showOnShapeCreate())
continue;
panel->open(shape);
panel->connect(panel, SIGNAL(accept()), dialog, SLOT(accept()));
widgets.append(panel);
panel->setResourceManager(canvas->resourceManager());
panel->setUnit(canvas->unit());
QString title = panel->windowTitle().isEmpty() ? panel->objectName() : panel->windowTitle();
dialog->addPage(panel, title);
pageCount ++;
}
if (pageCount > 0) {
if (pageCount > 1)
dialog->setFaceType(KPageDialog::Tabbed);
if (dialog->exec() != KPageDialog::Accepted) {
delete dialog;
return; /// FIXME how to indicate failure?
}
Q_FOREACH (KoShapeConfigWidgetBase *widget, widgets)
widget->save();
}
delete dialog;
}
}
addShapesDirect({shape}, parentShape);
}
KUndo2Command* addShapesDirect(const QList<KoShape*> shapes, KoShapeContainer *parentShape, KUndo2Command *parent)
{
return new KoShapeCreateCommand(shapeController, shapes, parentShape, parent);
}
void addShapesDirect(const QList<KoShape*> shapes, KoShapeContainer *parentShape)
{
// from KoShapeCreateCommand::redo()
Q_FOREACH (KoShape *shape, shapes) {
if (parentShape) {
shape->setParent(parentShape);
}
shapeController->addShape(shape);
KoShapeContainer *shapeParent = shape->parent();
KIS_SAFE_ASSERT_RECOVER_NOOP(shape->parent() ||
dynamic_cast<KoShapeLayer*>(shape));
if (shapeParent) {
// TODO get rid of undo commands here
QScopedPointer<KUndo2Command> cmd(KoShapeReorderCommand::mergeInShape(shapeParent->shapes(), shape));
if (cmd) {
cmd->redo();
}
}
}
}
void handleAttachedConnections(KoShape *shape, KUndo2Command *parentCmd) {
foreach (KoShape *dependee, shape->dependees()) {
KoConnectionShape *connection = dynamic_cast<KoConnectionShape*>(dependee);
......@@ -121,6 +197,19 @@ public:
}
}
}
void handleAttachedConnections(KoShape *shape) {
foreach (KoShape *dependee, shape->dependees()) {
KoConnectionShape *connection = dynamic_cast<KoConnectionShape*>(dependee);
if (connection) {
if (shape == connection->firstShape()) {
connection->connectFirst(/* shape = */ 0, /* connectionPointId = */ -1);
} else if (shape == connection->secondShape()) {
connection->connectSecond(/* shape = */ 0, /* connectionPointId = */ -1);
}
}
}
}
};
KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeControllerBase *shapeController)
......@@ -146,25 +235,52 @@ KUndo2Command* KoShapeController::addShape(KoShape *shape, KoShapeContainer *par
return d->addShape(shape, true, parentShape, parent);
}
void KoShapeController::addShape(KoShape *shape, KoShapeContainer *parentShape)
{
d->addShape(shape, true, parentShape);
}
KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent)
{
return d->addShapesDirect({shape}, parentShape, parent);
}
void KoShapeController::addShapeDirect(KoShape *shape, KoShapeContainer *parentShape)
{
d->addShapesDirect({shape}, parentShape);
}
KUndo2Command *KoShapeController::addShapesDirect(const QList<KoShape *> shapes, KoShapeContainer *parentShape, KUndo2Command *parent)
{
return d->addShapesDirect(shapes, parentShape, parent);
}
KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent)
void KoShapeController::addShapesDirect(const QList<KoShape *> shapes, KoShapeContainer *parentShape)
{
KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeController, shape, parent);
QList<KoShape*> shapes;
shapes.append(shape);
d->shapeController->shapesRemoved(shapes, cmd);
// detach shape from any attached connection shapes
d->handleAttachedConnections(shape, cmd);
return cmd;
d->addShapesDirect(shapes, parentShape);
}
void KoShapeController::removeShape(KoShape *shape)
{
removeShapes({shape});
}
void KoShapeController