diff --git a/libs/global/kis_algebra_2d.h b/libs/global/kis_algebra_2d.h
index e8c74f44dd..d8ec006c9b 100644
--- a/libs/global/kis_algebra_2d.h
+++ b/libs/global/kis_algebra_2d.h
@@ -1,624 +1,648 @@
/*
* Copyright (c) 2014 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_ALGEBRA_2D_H
#define __KIS_ALGEBRA_2D_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
class QPainterPath;
class QTransform;
namespace KisAlgebra2D {
template
struct PointTypeTraits
{
};
template <>
struct PointTypeTraits
{
typedef int value_type;
typedef qreal calculation_type;
typedef QRect rect_type;
};
template <>
struct PointTypeTraits
{
typedef qreal value_type;
typedef qreal calculation_type;
typedef QRectF rect_type;
};
template
typename PointTypeTraits::value_type dotProduct(const T &a, const T &b)
{
return a.x() * b.x() + a.y() * b.y();
}
template
typename PointTypeTraits::value_type crossProduct(const T &a, const T &b)
{
return a.x() * b.y() - a.y() * b.x();
}
template
qreal norm(const T &a)
{
return std::sqrt(pow2(a.x()) + pow2(a.y()));
}
template
Point normalize(const Point &a)
{
const qreal length = norm(a);
return (1.0 / length) * a;
}
/**
* Usual sign() function with positive zero
*/
template
T signPZ(T x) {
return x >= T(0) ? T(1) : T(-1);
}
/**
* Usual sign() function with zero returning zero
*/
template
T signZZ(T x) {
return x == T(0) ? T(0) : x > T(0) ? T(1) : T(-1);
}
/**
* Copies the sign of \p y into \p x and returns the result
*/
template
inline T copysign(T x, T y) {
T strippedX = qAbs(x);
return y >= T(0) ? strippedX : -strippedX;
}
template
typename std::enable_if::value, T>::type
divideFloor(T a, T b)
{
const bool a_neg = a < T(0);
const bool b_neg = b < T(0);
if (a == T(0)) {
return 0;
} else if (a_neg == b_neg) {
return a / b;
} else {
const T a_abs = qAbs(a);
const T b_abs = qAbs(b);
return - 1 - (a_abs - T(1)) / b_abs;
}
}
template
T leftUnitNormal(const T &a)
{
T result = a.x() != 0 ? T(-a.y() / a.x(), 1) : T(-1, 0);
qreal length = norm(result);
result *= (crossProduct(a, result) >= 0 ? 1 : -1) / length;
return -result;
}
template
T rightUnitNormal(const T &a)
{
return -leftUnitNormal(a);
}
template
T inwardUnitNormal(const T &a, int polygonDirection)
{
return polygonDirection * leftUnitNormal(a);
}
/**
* \return 1 if the polygon is counterclockwise
* -1 if the polygon is clockwise
*
* Note: the sign is flipped because our 0y axis
* is reversed
*/
template
int polygonDirection(const QVector &polygon) {
typename PointTypeTraits::value_type doubleSum = 0;
const int numPoints = polygon.size();
for (int i = 1; i <= numPoints; i++) {
int prev = i - 1;
int next = i == numPoints ? 0 : i;
doubleSum +=
(polygon[next].x() - polygon[prev].x()) *
(polygon[next].y() + polygon[prev].y());
}
return doubleSum >= 0 ? 1 : -1;
}
template
bool isInRange(T x, T a, T b) {
T length = qAbs(a - b);
return qAbs(x - a) <= length && qAbs(x - b) <= length;
}
void KRITAGLOBAL_EXPORT adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt);
/**
* Let \p pt, \p base1 are two vectors. \p base1 is uniformly scaled
* and then rotated into \p base2 using transformation matrix S *
* R. The function applies the same transformation to \pt and returns
* the result.
**/
QPointF KRITAGLOBAL_EXPORT transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2);
qreal KRITAGLOBAL_EXPORT angleBetweenVectors(const QPointF &v1, const QPointF &v2);
/**
* Computes an angle indicating the direction from p1 to p2. If p1 and p2 are too close together to
* compute an angle, defaultAngle is returned.
*/
qreal KRITAGLOBAL_EXPORT directionBetweenPoints(const QPointF &p1, const QPointF &p2,
qreal defaultAngle);
namespace Private {
inline void resetEmptyRectangle(const QPoint &pt, QRect *rc) {
*rc = QRect(pt, QSize(1, 1));
}
inline void resetEmptyRectangle(const QPointF &pt, QRectF *rc) {
static const qreal eps = 1e-10;
*rc = QRectF(pt, QSizeF(eps, eps));
}
}
template
inline void accumulateBounds(const Point &pt, Rect *bounds)
{
if (bounds->isEmpty()) {
Private::resetEmptyRectangle(pt, bounds);
}
if (pt.x() > bounds->right()) {
bounds->setRight(pt.x());
}
if (pt.x() < bounds->left()) {
bounds->setLeft(pt.x());
}
if (pt.y() > bounds->bottom()) {
bounds->setBottom(pt.y());
}
if (pt.y() < bounds->top()) {
bounds->setTop(pt.y());
}
}
template class Container, class Point, class Rect>
inline void accumulateBounds(const Container &points, Rect *bounds)
{
Q_FOREACH (const Point &pt, points) {
accumulateBounds(pt, bounds);
}
}
template class Container, class Point>
inline typename PointTypeTraits::rect_type
accumulateBounds(const Container &points)
{
typename PointTypeTraits::rect_type result;
Q_FOREACH (const Point &pt, points) {
accumulateBounds(pt, &result);
}
return result;
}
template
inline Point clampPoint(Point pt, const Rect &bounds)
{
if (pt.x() > bounds.right()) {
pt.rx() = bounds.right();
}
if (pt.x() < bounds.left()) {
pt.rx() = bounds.left();
}
if (pt.y() > bounds.bottom()) {
pt.ry() = bounds.bottom();
}
if (pt.y() < bounds.top()) {
pt.ry() = bounds.top();
}
return pt;
}
template
auto maxDimension(Size size) -> decltype(size.width()) {
return qMax(size.width(), size.height());
}
template
auto minDimension(Size size) -> decltype(size.width()) {
return qMin(size.width(), size.height());
}
QPainterPath KRITAGLOBAL_EXPORT smallArrow();
/**
* Multiply width and height of \p rect by \p coeff keeping the
* center of the rectangle pinned
*/
template
Rect blowRect(const Rect &rect, qreal coeff)
{
typedef decltype(rect.x()) CoordType;
CoordType w = rect.width() * coeff;
CoordType h = rect.height() * coeff;
return rect.adjusted(-w, -h, w, h);
}
QPoint KRITAGLOBAL_EXPORT ensureInRect(QPoint pt, const QRect &bounds);
QPointF KRITAGLOBAL_EXPORT ensureInRect(QPointF pt, const QRectF &bounds);
template
Rect ensureRectNotSmaller(Rect rc, const decltype(Rect().size()) &size)
{
typedef decltype(Rect().size()) Size;
typedef decltype(Rect().top()) ValueType;
if (rc.width() < size.width() ||
rc.height() < size.height()) {
ValueType width = qMax(rc.width(), size.width());
ValueType height = qMax(rc.height(), size.height());
rc = Rect(rc.topLeft(), Size(width, height));
}
return rc;
}
template
Size ensureSizeNotSmaller(const Size &size, const Size &bounds)
{
Size result = size;
const auto widthBound = qAbs(bounds.width());
auto width = result.width();
if (qAbs(width) < widthBound) {
width = copysign(widthBound, width);
result.setWidth(width);
}
const auto heightBound = qAbs(bounds.height());
auto height = result.height();
if (qAbs(height) < heightBound) {
height = copysign(heightBound, height);
result.setHeight(height);
}
return result;
}
/**
* Attempt to intersect a line to the area of the a rectangle.
*
* If the line intersects the rectangle, it will be modified to represent the intersecting line segment and true is returned.
* If the line does not intersect the area, it remains unmodified and false will be returned.
*
* @param segment
* @param area
* @return true if successful
*/
bool KRITAGLOBAL_EXPORT intersectLineRect(QLineF &line, const QRect rect);
template
inline Point abs(const Point &pt) {
return Point(qAbs(pt.x()), qAbs(pt.y()));
}
+template::value, T>::type* = nullptr>
+inline T wrapValue(T value, T wrapBounds) {
+ value %= wrapBounds;
+ if (value < 0) {
+ value += wrapBounds;
+ }
+ return value;
+}
+
+template::value, T>::type* = nullptr>
+inline T wrapValue(T value, T wrapBounds) {
+ value = std::fmod(value, wrapBounds);
+ if (value < 0) {
+ value += wrapBounds;
+ }
+ return value;
+}
+
+template::value, T>::type* = nullptr>
+inline T wrapValue(T value, T wrapBounds) {
+ value.rx() = wrapValue(value.x(), wrapBounds.x());
+ value.ry() = wrapValue(value.y(), wrapBounds.y());
+ return value;
+}
class RightHalfPlane {
public:
RightHalfPlane(const QPointF &a, const QPointF &b)
: m_a(a), m_p(b - a), m_norm_p_inv(1.0 / norm(m_p))
{
}
RightHalfPlane(const QLineF &line)
: RightHalfPlane(line.p1(), line.p2())
{
}
qreal valueSq(const QPointF &pt) const {
const qreal val = value(pt);
return signZZ(val) * pow2(val);
}
qreal value(const QPointF &pt) const {
return crossProduct(m_p, pt - m_a) * m_norm_p_inv;
}
int pos(const QPointF &pt) const {
return signZZ(value(pt));
}
QLineF getLine() const {
return QLineF(m_a, m_a + m_p);
}
private:
const QPointF m_a;
const QPointF m_p;
const qreal m_norm_p_inv;
};
class OuterCircle {
public:
OuterCircle(const QPointF &c, qreal radius)
: m_c(c),
m_radius(radius),
m_radius_sq(pow2(radius)),
m_fadeCoeff(1.0 / (pow2(radius + 1.0) - m_radius_sq))
{
}
qreal valueSq(const QPointF &pt) const {
const qreal val = value(pt);
return signZZ(val) * pow2(val);
}
qreal value(const QPointF &pt) const {
return kisDistance(pt, m_c) - m_radius;
}
int pos(const QPointF &pt) const {
return signZZ(valueSq(pt));
}
qreal fadeSq(const QPointF &pt) const {
const qreal valSq = kisSquareDistance(pt, m_c);
return (valSq - m_radius_sq) * m_fadeCoeff;
}
private:
const QPointF m_c;
const qreal m_radius;
const qreal m_radius_sq;
const qreal m_fadeCoeff;
};
QVector KRITAGLOBAL_EXPORT sampleRectWithPoints(const QRect &rect);
QVector KRITAGLOBAL_EXPORT sampleRectWithPoints(const QRectF &rect);
QRect KRITAGLOBAL_EXPORT approximateRectFromPoints(const QVector &points);
QRectF KRITAGLOBAL_EXPORT approximateRectFromPoints(const QVector &points);
QRect KRITAGLOBAL_EXPORT approximateRectWithPointTransform(const QRect &rect, std::function func);
/**
* Cuts off a portion of a rect \p rc defined by a half-plane \p p
* \return the bounding rect of the resulting polygon
*/
KRITAGLOBAL_EXPORT
QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p);
/**
* Solves a quadratic equation in a form:
*
* a * x^2 + b * x + c = 0
*
* WARNING: Please note that \p a *must* be nonzero! Otherwise the
* equation is not quadratic! And this function doesn't check that!
*
* \return the number of solutions. It can be 0, 1 or 2.
*
* \p x1, \p x2 --- the found solution. The variables are filled with
* data iff the corresponding solution is found. That
* is: 0 solutions --- variabled are not touched, 1
* solution --- x1 is filled with the result, 2
* solutions --- x1 and x2 are filled.
*/
KRITAGLOBAL_EXPORT
int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2);
/**
* Finds the points of intersections between two circles
* \return the found circles, the result can have 0, 1 or 2 points
*/
KRITAGLOBAL_EXPORT
QVector intersectTwoCircles(const QPointF &c1, qreal r1,
const QPointF &c2, qreal r2);
KRITAGLOBAL_EXPORT
QTransform mapToRect(const QRectF &rect);
/**
* Scale the relative point \pt into the bounds of \p rc. The point might be
* outside the rectangle.
*/
inline QPointF relativeToAbsolute(const QPointF &pt, const QRectF &rc) {
return rc.topLeft() + QPointF(pt.x() * rc.width(), pt.y() * rc.height());
}
/**
* Get the relative position of \p pt inside rectangle \p rc. The point can be
* outside the rectangle.
*/
inline QPointF absoluteToRelative(const QPointF &pt, const QRectF &rc) {
if (!rc.isValid()) return QPointF();
const QPointF rel = pt - rc.topLeft();
return QPointF(rel.x() / rc.width(), rel.y() / rc.height());
}
/**
* Scales relative isotropic value from relative to absolute coordinate system
* using SVG 1.1 rules (see chapter 7.10)
*/
inline qreal relativeToAbsolute(qreal value, const QRectF &rc) {
const qreal coeff = std::sqrt(pow2(rc.width()) + pow2(rc.height())) / std::sqrt(2.0);
return value * coeff;
}
/**
* Scales absolute isotropic value from absolute to relative coordinate system
* using SVG 1.1 rules (see chapter 7.10)
*/
inline qreal absoluteToRelative(const qreal value, const QRectF &rc) {
const qreal coeff = std::sqrt(pow2(rc.width()) + pow2(rc.height())) / std::sqrt(2.0);
return coeff != 0 ? value / coeff : 0;
}
/**
* Compare the matrices with tolerance \p delta
*/
bool KRITAGLOBAL_EXPORT fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta);
/**
* Returns true if the two points are equal within some tolerance, where the tolerance is determined
* by Qt's built-in fuzzy comparison functions.
*/
bool KRITAGLOBAL_EXPORT fuzzyPointCompare(const QPointF &p1, const QPointF &p2);
/**
* Returns true if the two points are equal within the specified tolerance
*/
bool KRITAGLOBAL_EXPORT fuzzyPointCompare(const QPointF &p1, const QPointF &p2, qreal delta);
/**
* Compare two rectangles with tolerance \p tolerance. The tolerance means that the
* coordinates of top left and bottom right corners should not differ more than \p tolerance
* pixels.
*/
template
bool fuzzyCompareRects(const Rect &r1, const Rect &r2, Difference tolerance) {
typedef decltype(r1.topLeft()) Point;
const Point d1 = abs(r1.topLeft() - r2.topLeft());
const Point d2 = abs(r1.bottomRight() - r2.bottomRight());
const Difference maxError = std::max({d1.x(), d1.y(), d2.x(), d2.y()});
return maxError < tolerance;
}
struct KRITAGLOBAL_EXPORT DecomposedMatix {
DecomposedMatix();
DecomposedMatix(const QTransform &t0);
inline QTransform scaleTransform() const
{
return QTransform::fromScale(scaleX, scaleY);
}
inline QTransform shearTransform() const
{
QTransform t;
t.shear(shearXY, 0);
return t;
}
inline QTransform rotateTransform() const
{
QTransform t;
t.rotate(angle);
return t;
}
inline QTransform translateTransform() const
{
return QTransform::fromTranslate(dx, dy);
}
inline QTransform projectTransform() const
{
return
QTransform(
1,0,proj[0],
0,1,proj[1],
0,0,proj[2]);
}
inline QTransform transform() const {
return
scaleTransform() *
shearTransform() *
rotateTransform() *
translateTransform() *
projectTransform();
}
inline bool isValid() const {
return valid;
}
qreal scaleX = 1.0;
qreal scaleY = 1.0;
qreal shearXY = 0.0;
qreal angle = 0.0;
qreal dx = 0.0;
qreal dy = 0.0;
qreal proj[3] = {0.0, 0.0, 1.0};
private:
bool valid = true;
};
}
#endif /* __KIS_ALGEBRA_2D_H */
diff --git a/libs/image/kis_layer_utils.h b/libs/image/kis_layer_utils.h
index 06aad322e7..b81e2cc65c 100644
--- a/libs/image/kis_layer_utils.h
+++ b/libs/image/kis_layer_utils.h
@@ -1,226 +1,234 @@
/*
* Copyright (c) 2015 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_LAYER_UTILS_H
#define __KIS_LAYER_UTILS_H
#include
#include "kundo2command.h"
#include "kis_types.h"
#include "kritaimage_export.h"
#include "kis_command_utils.h"
class KoProperties;
class KoColor;
class QUuid;
namespace KisMetaData
{
class MergeStrategy;
}
namespace KisLayerUtils
{
KRITAIMAGE_EXPORT void sortMergableNodes(KisNodeSP root, QList &inputNodes, QList &outputNodes);
KRITAIMAGE_EXPORT KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes);
KRITAIMAGE_EXPORT void filterMergableNodes(KisNodeList &nodes, bool allowMasks = false);
KRITAIMAGE_EXPORT bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents);
KRITAIMAGE_EXPORT void filterUnlockedNodes(KisNodeList &nodes);
/**
* Returns true if:
* o \p node is a clone of some layer in \p nodes
* o \p node is a clone any child layer of any layer in \p nodes
* o \p node is a clone of a clone of a ..., that in the end points
* to any layer in \p nodes of their children.
*/
KRITAIMAGE_EXPORT bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes);
KRITAIMAGE_EXPORT void forceAllDelayedNodesUpdate(KisNodeSP root);
KRITAIMAGE_EXPORT bool hasDelayedNodeWithUpdates(KisNodeSP root);
KRITAIMAGE_EXPORT KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks = false);
KRITAIMAGE_EXPORT void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy);
KRITAIMAGE_EXPORT QSet fetchLayerFrames(KisNodeSP node);
KRITAIMAGE_EXPORT QSet fetchLayerFramesRecursive(KisNodeSP rootNode);
KRITAIMAGE_EXPORT void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter);
KRITAIMAGE_EXPORT void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter);
KRITAIMAGE_EXPORT bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter);
KRITAIMAGE_EXPORT void flattenLayer(KisImageSP image, KisLayerSP layer);
KRITAIMAGE_EXPORT void flattenImage(KisImageSP image, KisNodeSP activeNode);
KRITAIMAGE_EXPORT void addCopyOfNameTag(KisNodeSP node);
KRITAIMAGE_EXPORT KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot);
KRITAIMAGE_EXPORT void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color);
typedef QMap > FrameJobs;
void updateFrameJobs(FrameJobs *jobs, KisNodeSP node);
void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode);
struct SwitchFrameCommand : public KisCommandUtils::FlipFlopCommand {
struct SharedStorage {
/**
* For some reason the absence of a destructor in the SharedStorage
* makes Krita crash on exit. Seems like some compiler weirdness... (DK)
*/
~SharedStorage();
int value;
};
typedef QSharedPointer SharedStorageSP;
public:
SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage);
~SwitchFrameCommand() override;
private:
void partA() override;
void partB() override;
private:
KisImageWSP m_image;
int m_newTime;
SharedStorageSP m_storage;
};
/**
* A command to keep correct set of selected/active nodes thoroughout
* the action.
*/
class KRITAIMAGE_EXPORT KeepNodesSelectedCommand : public KisCommandUtils::FlipFlopCommand
{
public:
KeepNodesSelectedCommand(const KisNodeList &selectedBefore,
const KisNodeList &selectedAfter,
KisNodeSP activeBefore,
KisNodeSP activeAfter,
KisImageSP image,
bool finalize, KUndo2Command *parent = 0);
void partB() override;
private:
KisNodeList m_selectedBefore;
KisNodeList m_selectedAfter;
KisNodeSP m_activeBefore;
KisNodeSP m_activeAfter;
KisImageWSP m_image;
};
struct KRITAIMAGE_EXPORT SelectGlobalSelectionMask : public KUndo2Command
{
SelectGlobalSelectionMask(KisImageSP image);
void redo() override;
KisImageSP m_image;
};
class KRITAIMAGE_EXPORT RemoveNodeHelper {
public:
virtual ~RemoveNodeHelper();
protected:
virtual void addCommandImpl(KUndo2Command *cmd) = 0;
void safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image);
private:
bool checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes);
static bool scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove);
};
struct SimpleRemoveLayers : private KisLayerUtils::RemoveNodeHelper, public KisCommandUtils::AggregateCommand {
SimpleRemoveLayers(const KisNodeList &nodes,
KisImageSP image);
void populateChildCommands() override;
protected:
void addCommandImpl(KUndo2Command *cmd) override;
private:
KisNodeList m_nodes;
KisImageSP m_image;
KisNodeList m_selectedNodes;
KisNodeSP m_activeNode;
};
class KRITAIMAGE_EXPORT KisSimpleUpdateCommand : public KisCommandUtils::FlipFlopCommand
{
public:
KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent = 0);
void partB() override;
static void updateNodes(const KisNodeList &nodes);
private:
KisNodeList m_nodes;
};
template
bool checkNodesDiffer(KisNodeList nodes, std::function checkerFunc)
{
bool valueDiffers = false;
bool initialized = false;
T currentValue = T();
Q_FOREACH (KisNodeSP node, nodes) {
if (!initialized) {
currentValue = checkerFunc(node);
initialized = true;
} else if (currentValue != checkerFunc(node)) {
valueDiffers = true;
break;
}
}
return valueDiffers;
}
/**
* Applies \p func to \p node and all its children recursively
*/
template
void recursiveApplyNodes(NodePointer node, Functor func)
{
func(node);
node = node->firstChild();
while (node) {
recursiveApplyNodes(node, func);
node = node->nextSibling();
}
}
/**
* Walks through \p node and all its children recursively until
* \p func returns true. When \p func returns true, the node is
* considered to be found, the search is stopped and the found
* node is returned to the caller.
*/
KisNodeSP KRITAIMAGE_EXPORT recursiveFindNode(KisNodeSP node, std::function func);
/**
* Recursively searches for a node with specified Uuid
*/
KisNodeSP KRITAIMAGE_EXPORT findNodeByUuid(KisNodeSP root, const QUuid &uuid);
KisImageSP KRITAIMAGE_EXPORT findImageByHierarchy(KisNodeSP node);
+
+ template
+ T* findNodeByType(KisNodeSP root) {
+ return dynamic_cast(recursiveFindNode(root, [] (KisNodeSP node) {
+ return bool(dynamic_cast(node.data()));
+ }).data());
+ }
+
}
#endif /* __KIS_LAYER_UTILS_H */
diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt
index 0148cabd7d..38ffb0cee8 100644
--- a/libs/ui/CMakeLists.txt
+++ b/libs/ui/CMakeLists.txt
@@ -1,617 +1,619 @@
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${OCIO_INCLUDE_DIR}
)
add_subdirectory( tests )
if (APPLE)
find_library(FOUNDATION_LIBRARY Foundation)
find_library(APPKIT_LIBRARY AppKit)
endif ()
set(kritaui_LIB_SRCS
canvas/kis_canvas_widget_base.cpp
canvas/kis_canvas2.cpp
canvas/kis_canvas_updates_compressor.cpp
canvas/kis_canvas_controller.cpp
canvas/kis_display_color_converter.cpp
canvas/kis_display_filter.cpp
canvas/kis_exposure_gamma_correction_interface.cpp
canvas/kis_tool_proxy.cpp
canvas/kis_canvas_decoration.cc
canvas/kis_coordinates_converter.cpp
canvas/kis_grid_manager.cpp
canvas/kis_grid_decoration.cpp
canvas/kis_grid_config.cpp
canvas/kis_prescaled_projection.cpp
canvas/kis_qpainter_canvas.cpp
canvas/kis_projection_backend.cpp
canvas/kis_update_info.cpp
canvas/kis_image_patch.cpp
canvas/kis_image_pyramid.cpp
canvas/kis_infinity_manager.cpp
canvas/kis_change_guides_command.cpp
canvas/kis_guides_decoration.cpp
canvas/kis_guides_manager.cpp
canvas/kis_guides_config.cpp
canvas/kis_snap_config.cpp
canvas/kis_snap_line_strategy.cpp
canvas/KisSnapPointStrategy.cpp
canvas/KisSnapPixelStrategy.cpp
canvas/KisMirrorAxisConfig.cpp
dialogs/kis_about_application.cpp
dialogs/kis_dlg_adj_layer_props.cc
dialogs/kis_dlg_adjustment_layer.cc
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_generator_layer.cpp
dialogs/kis_dlg_file_layer.cpp
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_stroke_selection_properties.cpp
dialogs/kis_dlg_image_properties.cc
dialogs/kis_dlg_layer_properties.cc
dialogs/kis_dlg_preferences.cc
dialogs/slider_and_spin_box_sync.cpp
dialogs/kis_dlg_blacklist_cleanup.cpp
dialogs/kis_dlg_layer_style.cpp
dialogs/kis_dlg_png_import.cpp
dialogs/kis_dlg_import_image_sequence.cpp
dialogs/kis_delayed_save_dialog.cpp
dialogs/KisSessionManagerDialog.cpp
dialogs/KisNewWindowLayoutDialog.cpp
dialogs/KisDlgChangeCloneSource.cpp
flake/kis_node_dummies_graph.cpp
flake/kis_dummies_facade_base.cpp
flake/kis_dummies_facade.cpp
flake/kis_node_shapes_graph.cpp
flake/kis_node_shape.cpp
flake/kis_shape_controller.cpp
flake/kis_shape_layer.cc
flake/kis_shape_layer_canvas.cpp
flake/kis_shape_selection.cpp
flake/kis_shape_selection_canvas.cpp
flake/kis_shape_selection_model.cpp
flake/kis_take_all_shapes_command.cpp
brushhud/kis_uniform_paintop_property_widget.cpp
brushhud/kis_brush_hud.cpp
brushhud/kis_round_hud_button.cpp
brushhud/kis_dlg_brush_hud_config.cpp
brushhud/kis_brush_hud_properties_list.cpp
brushhud/kis_brush_hud_properties_config.cpp
kis_aspect_ratio_locker.cpp
kis_autogradient.cc
kis_bookmarked_configurations_editor.cc
kis_bookmarked_configurations_model.cc
kis_bookmarked_filter_configurations_model.cc
KisPaintopPropertiesBase.cpp
kis_canvas_resource_provider.cpp
kis_derived_resources.cpp
kis_categories_mapper.cpp
kis_categorized_list_model.cpp
kis_categorized_item_delegate.cpp
kis_clipboard.cc
kis_config.cc
KisOcioConfiguration.cpp
kis_control_frame.cpp
kis_composite_ops_model.cc
kis_paint_ops_model.cpp
kis_cursor.cc
kis_cursor_cache.cpp
kis_custom_pattern.cc
kis_file_layer.cpp
kis_change_file_layer_command.h
kis_safe_document_loader.cpp
kis_splash_screen.cpp
kis_filter_manager.cc
kis_filters_model.cc
KisImageBarrierLockerWithFeedback.cpp
kis_image_manager.cc
kis_image_view_converter.cpp
kis_import_catcher.cc
kis_layer_manager.cc
kis_mask_manager.cc
kis_mimedata.cpp
kis_node_commands_adapter.cpp
kis_node_manager.cpp
kis_node_juggler_compressed.cpp
kis_node_selection_adapter.cpp
kis_node_insertion_adapter.cpp
KisNodeDisplayModeAdapter.cpp
kis_node_model.cpp
kis_node_filter_proxy_model.cpp
kis_model_index_converter_base.cpp
kis_model_index_converter.cpp
kis_model_index_converter_show_all.cpp
kis_painting_assistant.cc
kis_painting_assistants_decoration.cpp
KisDecorationsManager.cpp
kis_paintop_box.cc
kis_paintop_option.cpp
kis_paintop_options_model.cpp
kis_paintop_settings_widget.cpp
kis_popup_palette.cpp
kis_png_converter.cpp
kis_preference_set_registry.cpp
KisResourceServerProvider.cpp
KisResourceBundleServerProvider.cpp
KisSelectedShapesProxy.cpp
kis_selection_decoration.cc
kis_selection_manager.cc
KisSelectionActionsAdapter.cpp
kis_statusbar.cc
kis_zoom_manager.cc
kis_favorite_resource_manager.cpp
kis_workspace_resource.cpp
kis_action.cpp
kis_action_manager.cpp
KisActionPlugin.cpp
kis_canvas_controls_manager.cpp
kis_tooltip_manager.cpp
kis_multinode_property.cpp
kis_stopgradient_editor.cpp
KisWelcomePageWidget.cpp
KisChangeCloneLayersCommand.cpp
kisexiv2/kis_exif_io.cpp
kisexiv2/kis_exiv2.cpp
kisexiv2/kis_iptc_io.cpp
kisexiv2/kis_xmp_io.cpp
opengl/kis_opengl.cpp
opengl/kis_opengl_canvas2.cpp
opengl/kis_opengl_canvas_debugger.cpp
opengl/kis_opengl_image_textures.cpp
opengl/kis_texture_tile.cpp
opengl/kis_opengl_shader_loader.cpp
opengl/kis_texture_tile_info_pool.cpp
opengl/KisOpenGLUpdateInfoBuilder.cpp
opengl/KisOpenGLModeProber.cpp
opengl/KisScreenInformationAdapter.cpp
kis_fps_decoration.cpp
tool/KisToolChangesTracker.cpp
tool/KisToolChangesTrackerData.cpp
tool/kis_selection_tool_helper.cpp
tool/kis_selection_tool_config_widget_helper.cpp
tool/kis_rectangle_constraint_widget.cpp
tool/kis_shape_tool_helper.cpp
tool/kis_tool.cc
tool/kis_delegated_tool_policies.cpp
tool/kis_tool_freehand.cc
tool/kis_speed_smoother.cpp
tool/kis_painting_information_builder.cpp
tool/kis_stabilized_events_sampler.cpp
tool/kis_tool_freehand_helper.cpp
tool/kis_tool_multihand_helper.cpp
tool/kis_figure_painting_tool_helper.cpp
tool/KisAsyncronousStrokeUpdateHelper.cpp
tool/kis_tool_paint.cc
tool/kis_tool_shape.cc
tool/kis_tool_ellipse_base.cpp
tool/kis_tool_rectangle_base.cpp
tool/kis_tool_polyline_base.cpp
tool/kis_tool_utils.cpp
tool/kis_resources_snapshot.cpp
tool/kis_smoothing_options.cpp
tool/KisStabilizerDelayedPaintHelper.cpp
tool/KisStrokeSpeedMonitor.cpp
tool/strokes/freehand_stroke.cpp
tool/strokes/KisStrokeEfficiencyMeasurer.cpp
tool/strokes/kis_painter_based_stroke_strategy.cpp
tool/strokes/kis_filter_stroke_strategy.cpp
tool/strokes/kis_color_picker_stroke_strategy.cpp
tool/strokes/KisFreehandStrokeInfo.cpp
tool/strokes/KisMaskedFreehandStrokePainter.cpp
tool/strokes/KisMaskingBrushRenderer.cpp
tool/strokes/KisMaskingBrushCompositeOpFactory.cpp
tool/strokes/move_stroke_strategy.cpp
tool/KisSelectionToolFactoryBase.cpp
tool/KisToolPaintFactoryBase.cpp
widgets/kis_cmb_composite.cc
widgets/kis_cmb_contour.cpp
widgets/kis_cmb_gradient.cpp
widgets/kis_paintop_list_widget.cpp
widgets/kis_cmb_idlist.cc
widgets/kis_color_space_selector.cc
widgets/kis_advanced_color_space_selector.cc
widgets/kis_cie_tongue_widget.cpp
widgets/kis_tone_curve_widget.cpp
widgets/kis_curve_widget.cpp
widgets/kis_custom_image_widget.cc
widgets/kis_image_from_clipboard_widget.cpp
widgets/kis_double_widget.cc
widgets/kis_filter_selector_widget.cc
widgets/kis_gradient_chooser.cc
widgets/kis_iconwidget.cc
widgets/kis_mask_widgets.cpp
widgets/kis_meta_data_merge_strategy_chooser_widget.cc
widgets/kis_multi_bool_filter_widget.cc
widgets/kis_multi_double_filter_widget.cc
widgets/kis_multi_integer_filter_widget.cc
widgets/kis_multipliers_double_slider_spinbox.cpp
widgets/kis_paintop_presets_popup.cpp
widgets/kis_tool_options_popup.cpp
widgets/kis_paintop_presets_chooser_popup.cpp
widgets/kis_paintop_presets_save.cpp
widgets/kis_paintop_preset_icon_library.cpp
widgets/kis_pattern_chooser.cc
widgets/kis_preset_chooser.cpp
widgets/kis_progress_widget.cpp
widgets/kis_selection_options.cc
widgets/kis_scratch_pad.cpp
widgets/kis_scratch_pad_event_filter.cpp
widgets/kis_preset_selector_strip.cpp
widgets/kis_slider_spin_box.cpp
widgets/KisSelectionPropertySlider.cpp
widgets/kis_size_group.cpp
widgets/kis_size_group_p.cpp
widgets/kis_wdg_generator.cpp
widgets/kis_workspace_chooser.cpp
widgets/kis_categorized_list_view.cpp
widgets/kis_widget_chooser.cpp
widgets/kis_tool_button.cpp
widgets/kis_floating_message.cpp
widgets/kis_lod_availability_widget.cpp
widgets/kis_color_label_selector_widget.cpp
widgets/kis_color_filter_combo.cpp
widgets/kis_elided_label.cpp
widgets/kis_stopgradient_slider_widget.cpp
widgets/kis_preset_live_preview_view.cpp
widgets/KisScreenColorPicker.cpp
widgets/KoDualColorButton.cpp
widgets/KoStrokeConfigWidget.cpp
widgets/KoFillConfigWidget.cpp
widgets/KisLayerStyleAngleSelector.cpp
widgets/KisMemoryReportButton.cpp
widgets/KisDitherWidget.cpp
KisPaletteEditor.cpp
dialogs/KisDlgPaletteEditor.cpp
widgets/KisNewsWidget.cpp
widgets/KisGamutMaskToolbar.cpp
utils/kis_document_aware_spin_box_unit_manager.cpp
utils/KisSpinBoxSplineUnitConverter.cpp
utils/KisClipboardUtil.cpp
utils/KisDitherUtil.cpp
input/kis_input_manager.cpp
input/kis_input_manager_p.cpp
input/kis_extended_modifiers_mapper.cpp
input/kis_abstract_input_action.cpp
input/kis_tool_invocation_action.cpp
input/kis_pan_action.cpp
input/kis_alternate_invocation_action.cpp
input/kis_rotate_canvas_action.cpp
input/kis_zoom_action.cpp
input/kis_change_frame_action.cpp
input/kis_gamma_exposure_action.cpp
input/kis_show_palette_action.cpp
input/kis_change_primary_setting_action.cpp
input/kis_abstract_shortcut.cpp
input/kis_native_gesture_shortcut.cpp
input/kis_single_action_shortcut.cpp
input/kis_stroke_shortcut.cpp
input/kis_shortcut_matcher.cpp
input/kis_select_layer_action.cpp
input/KisQtWidgetsTweaker.cpp
input/KisInputActionGroup.cpp
input/kis_zoom_and_rotate_action.cpp
operations/kis_operation.cpp
operations/kis_operation_configuration.cpp
operations/kis_operation_registry.cpp
operations/kis_operation_ui_factory.cpp
operations/kis_operation_ui_widget.cpp
operations/kis_filter_selection_operation.cpp
actions/kis_selection_action_factories.cpp
actions/KisPasteActionFactories.cpp
actions/KisTransformToolActivationCommand.cpp
input/kis_touch_shortcut.cpp
kis_document_undo_store.cpp
kis_gui_context_command.cpp
kis_gui_context_command_p.cpp
input/kis_tablet_debugger.cpp
input/kis_input_profile_manager.cpp
input/kis_input_profile.cpp
input/kis_shortcut_configuration.cpp
input/config/kis_input_configuration_page.cpp
input/config/kis_edit_profiles_dialog.cpp
input/config/kis_input_profile_model.cpp
input/config/kis_input_configuration_page_item.cpp
input/config/kis_action_shortcuts_model.cpp
input/config/kis_input_type_delegate.cpp
input/config/kis_input_mode_delegate.cpp
input/config/kis_input_button.cpp
input/config/kis_input_editor_delegate.cpp
input/config/kis_mouse_input_editor.cpp
input/config/kis_wheel_input_editor.cpp
input/config/kis_key_input_editor.cpp
processing/fill_processing_visitor.cpp
kis_asl_layer_style_serializer.cpp
kis_psd_layer_style_resource.cpp
canvas/kis_mirror_axis.cpp
kis_abstract_perspective_grid.cpp
KisApplication.cpp
KisAutoSaveRecoveryDialog.cpp
KisDetailsPane.cpp
KisDocument.cpp
KisCloneDocumentStroke.cpp
kis_node_view_color_scheme.cpp
KisImportExportFilter.cpp
KisImportExportManager.cpp
KisImportExportUtils.cpp
kis_async_action_feedback.cpp
KisMainWindow.cpp
KisOpenPane.cpp
KisPart.cpp
KisPrintJob.cpp
KisTemplate.cpp
KisTemplateCreateDia.cpp
KisTemplateGroup.cpp
KisTemplates.cpp
KisTemplatesPane.cpp
KisTemplateTree.cpp
KisUndoActionsUpdateManager.cpp
KisView.cpp
KisCanvasWindow.cpp
KisImportExportErrorCode.cpp
KisImportExportAdditionalChecks.cpp
thememanager.cpp
kis_mainwindow_observer.cpp
KisViewManager.cpp
kis_mirror_manager.cpp
qtlockedfile/qtlockedfile.cpp
qtsingleapplication/qtlocalpeer.cpp
qtsingleapplication/qtsingleapplication.cpp
KisResourceBundle.cpp
KisResourceBundleManifest.cpp
kis_md5_generator.cpp
KisApplicationArguments.cpp
KisNetworkAccessManager.cpp
KisMultiFeedRSSModel.cpp
KisRemoteFileFetcher.cpp
KisSaveGroupVisitor.cpp
KisWindowLayoutResource.cpp
KisWindowLayoutManager.cpp
KisSessionResource.cpp
KisReferenceImagesDecoration.cpp
KisReferenceImage.cpp
flake/KisReferenceImagesLayer.cpp
flake/KisReferenceImagesLayer.h
KisMouseClickEater.cpp
+
+ KisDecorationsWrapperLayer.cpp
)
if(WIN32)
# Private headers are needed for:
# * KisDlgCustomTabletResolution
# * KisScreenInformationAdapter
include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
qtlockedfile/qtlockedfile_win.cpp
)
if (NOT USE_QT_TABLET_WINDOWS)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/wintab/kis_tablet_support_win.cpp
input/wintab/kis_screen_size_choice_dialog.cpp
input/wintab/kis_tablet_support_win8.cpp
)
else()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
dialogs/KisDlgCustomTabletResolution.cpp
)
endif()
endif()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
kis_animation_frame_cache.cpp
kis_animation_cache_populator.cpp
KisAsyncAnimationRendererBase.cpp
KisAsyncAnimationCacheRenderer.cpp
KisAsyncAnimationFramesSavingRenderer.cpp
dialogs/KisAsyncAnimationRenderDialogBase.cpp
dialogs/KisAsyncAnimationCacheRenderDialog.cpp
dialogs/KisAsyncAnimationFramesSaveDialog.cpp
canvas/kis_animation_player.cpp
kis_animation_importer.cpp
KisSyncedAudioPlayback.cpp
KisFrameDataSerializer.cpp
KisFrameCacheStore.cpp
KisFrameCacheSwapper.cpp
KisAbstractFrameCacheSwapper.cpp
KisInMemoryFrameCacheSwapper.cpp
input/wintab/drawpile_tablettester/tablettester.cpp
input/wintab/drawpile_tablettester/tablettest.cpp
)
if (UNIX)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
qtlockedfile/qtlockedfile_unix.cpp
)
endif()
if(APPLE)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
osx.mm
)
endif()
ki18n_wrap_ui(kritaui_LIB_SRCS
widgets/KoFillConfigWidget.ui
widgets/KoStrokeConfigWidget.ui
widgets/KisDitherWidget.ui
forms/wdgdlgpngimport.ui
forms/wdgfullscreensettings.ui
forms/wdgautogradient.ui
forms/wdggeneralsettings.ui
forms/wdgperformancesettings.ui
forms/wdggenerators.ui
forms/wdgbookmarkedconfigurationseditor.ui
forms/wdgapplyprofile.ui
forms/wdgcustompattern.ui
forms/wdglayerproperties.ui
forms/wdgcolorsettings.ui
forms/wdgtabletsettings.ui
forms/wdgcolorspaceselector.ui
forms/wdgcolorspaceselectoradvanced.ui
forms/wdgdisplaysettings.ui
forms/kis_previewwidgetbase.ui
forms/kis_matrix_widget.ui
forms/wdgselectionoptions.ui
forms/wdggeometryoptions.ui
forms/wdgnewimage.ui
forms/wdgimageproperties.ui
forms/wdgmaskfromselection.ui
forms/wdgmasksource.ui
forms/wdgfilterdialog.ui
forms/wdgmetadatamergestrategychooser.ui
forms/wdgpaintoppresets.ui
forms/wdgpaintopsettings.ui
forms/wdgdlggeneratorlayer.ui
forms/wdgdlgfilelayer.ui
forms/wdgfilterselector.ui
forms/wdgfilternodecreation.ui
forms/wdgmultipliersdoublesliderspinbox.ui
forms/wdgnodequerypatheditor.ui
forms/wdgpresetselectorstrip.ui
forms/wdgsavebrushpreset.ui
forms/wdgpreseticonlibrary.ui
forms/wdgdlgblacklistcleanup.ui
forms/wdgrectangleconstraints.ui
forms/wdgimportimagesequence.ui
forms/wdgstrokeselectionproperties.ui
forms/KisDetailsPaneBase.ui
forms/KisOpenPaneBase.ui
forms/wdgstopgradienteditor.ui
forms/wdgsessionmanager.ui
forms/wdgnewwindowlayout.ui
forms/KisWelcomePage.ui
forms/WdgDlgPaletteEditor.ui
forms/KisNewsPage.ui
forms/wdgGamutMaskToolbar.ui
forms/wdgchangeclonesource.ui
brushhud/kis_dlg_brush_hud_config.ui
dialogs/kis_delayed_save_dialog.ui
input/config/kis_input_configuration_page.ui
input/config/kis_edit_profiles_dialog.ui
input/config/kis_input_configuration_page_item.ui
input/config/kis_mouse_input_editor.ui
input/config/kis_wheel_input_editor.ui
input/config/kis_key_input_editor.ui
layerstyles/wdgBevelAndEmboss.ui
layerstyles/wdgblendingoptions.ui
layerstyles/WdgColorOverlay.ui
layerstyles/wdgContour.ui
layerstyles/wdgdropshadow.ui
layerstyles/WdgGradientOverlay.ui
layerstyles/wdgInnerGlow.ui
layerstyles/wdglayerstyles.ui
layerstyles/WdgPatternOverlay.ui
layerstyles/WdgSatin.ui
layerstyles/WdgStroke.ui
layerstyles/wdgstylesselector.ui
layerstyles/wdgTexture.ui
layerstyles/wdgKisLayerStyleAngleSelector.ui
wdgsplash.ui
input/wintab/kis_screen_size_choice_dialog.ui
input/wintab/drawpile_tablettester/tablettest.ui
)
if(WIN32)
if(USE_QT_TABLET_WINDOWS)
ki18n_wrap_ui(kritaui_LIB_SRCS
dialogs/KisDlgCustomTabletResolution.ui
)
else()
ki18n_wrap_ui(kritaui_LIB_SRCS
input/wintab/kis_screen_size_choice_dialog.ui
)
endif()
endif()
add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} )
generate_export_header(kritaui BASE_NAME kritaui)
target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network
kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} LibExiv2::LibExiv2
)
if (HAVE_QT_MULTIMEDIA)
target_link_libraries(kritaui Qt5::Multimedia)
endif()
if (NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${X11_X11_LIB}
${X11_Xinput_LIB}
${XCB_LIBRARIES})
endif()
if(APPLE)
target_link_libraries(kritaui ${FOUNDATION_LIBRARY})
target_link_libraries(kritaui ${APPKIT_LIBRARY})
endif ()
target_link_libraries(kritaui ${OPENEXR_LIBRARIES})
# Add VSync disable workaround
if(NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras)
endif()
if(X11_FOUND)
target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES})
endif()
target_include_directories(kritaui
PUBLIC
$
$
$
$
$
$
$
)
set_target_properties(kritaui
PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS})
if (APPLE)
install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita)
endif ()
diff --git a/libs/ui/KisDecorationsWrapperLayer.cpp b/libs/ui/KisDecorationsWrapperLayer.cpp
new file mode 100644
index 0000000000..cf70ce559b
--- /dev/null
+++ b/libs/ui/KisDecorationsWrapperLayer.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2019 Dmitry Kazakov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisDecorationsWrapperLayer.h"
+
+#include "KisDocument.h"
+#include "kis_node_visitor.h"
+#include "kis_processing_visitor.h"
+#include "kis_grid_config.h"
+
+struct KisDecorationsWrapperLayer::Private
+{
+ KisDocument *document = 0;
+};
+
+KisDecorationsWrapperLayer::KisDecorationsWrapperLayer(KisDocument *document)
+ : KisExternalLayer(document->image(), "decorations-wrapper-layer", OPACITY_OPAQUE_U8),
+ m_d(new Private)
+{
+ m_d->document = document;
+}
+
+
+KisDecorationsWrapperLayer::KisDecorationsWrapperLayer(const KisDecorationsWrapperLayer &rhs)
+ : KisExternalLayer(rhs.image(), "decorations-wrapper-layer", OPACITY_OPAQUE_U8),
+ m_d(new Private)
+{
+ m_d->document = rhs.m_d->document;
+}
+
+KisDecorationsWrapperLayer::~KisDecorationsWrapperLayer()
+{
+}
+
+void KisDecorationsWrapperLayer::setDocument(KisDocument *document)
+{
+ m_d->document = document;
+ KIS_SAFE_ASSERT_RECOVER(image() == document->image()) {
+ setImage(document->image());
+ }
+}
+
+KisDocument *KisDecorationsWrapperLayer::document() const
+{
+ return m_d->document;
+}
+
+bool KisDecorationsWrapperLayer::allowAsChild(KisNodeSP) const
+{
+ return false;
+}
+
+bool KisDecorationsWrapperLayer::accept(KisNodeVisitor &visitor)
+{
+ return visitor.visit(this);
+}
+
+void KisDecorationsWrapperLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
+{
+ visitor.visit(this, undoAdapter);
+}
+
+KisNodeSP KisDecorationsWrapperLayer::clone() const {
+ return new KisDecorationsWrapperLayer(*this);
+}
+
+KisPaintDeviceSP KisDecorationsWrapperLayer::original() const
+{
+ return 0;
+}
+
+KisPaintDeviceSP KisDecorationsWrapperLayer::paintDevice() const
+{
+ return 0;
+}
+
+bool KisDecorationsWrapperLayer::isFakeNode() const
+{
+ return true;
+}
+
+bool KisDecorationsWrapperLayer::supportsPerspectiveTransform() const
+{
+ return false;
+}
+
+KUndo2Command *KisDecorationsWrapperLayer::crop(const QRect &rect)
+{
+ return transform(QTransform::fromTranslate(-rect.x(), -rect.y()));
+}
+
+KUndo2Command *KisDecorationsWrapperLayer::transform(const QTransform &transform)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->document, 0);
+
+ struct UndoCommand : public KUndo2Command
+ {
+ UndoCommand(KisDocument *document, const QTransform &transform)
+ : m_document(document),
+ m_transform(transform)
+ {}
+
+ void undo() override {
+ doTransform(m_transform.inverted());
+ }
+
+ void redo() override {
+ doTransform(m_transform);
+ }
+
+ private:
+ void doTransform(const QTransform &transform) {
+ KisGridConfig gridConfig = m_document->gridConfig();
+ if (gridConfig.showGrid()) {
+ gridConfig.transform(transform);
+ m_document->setGridConfig(gridConfig);
+ }
+ }
+
+ private:
+ KisDocument *m_document;
+ QTransform m_transform;
+ };
+
+ return new UndoCommand(m_d->document, transform);
+}
diff --git a/libs/ui/KisDecorationsWrapperLayer.h b/libs/ui/KisDecorationsWrapperLayer.h
new file mode 100644
index 0000000000..bb8e426af9
--- /dev/null
+++ b/libs/ui/KisDecorationsWrapperLayer.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2019 Dmitry Kazakov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISDECORATIONSWRAPPERLAYER_H
+#define KISDECORATIONSWRAPPERLAYER_H
+
+#include "kis_types.h"
+#include "kis_external_layer_iface.h"
+#include
+
+class KisDocument;
+
+class KisDecorationsWrapperLayer : public KisExternalLayer
+{
+ Q_OBJECT
+public:
+ KisDecorationsWrapperLayer(KisDocument *document);
+ KisDecorationsWrapperLayer(const KisDecorationsWrapperLayer &rhs);
+ ~KisDecorationsWrapperLayer();
+
+ void setDocument(KisDocument *document);
+ KisDocument* document() const;
+
+
+public:
+ // reimplemented from KisLayer
+
+ bool allowAsChild(KisNodeSP) const override;
+
+ bool accept(KisNodeVisitor&) override;
+ void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override;
+
+ KisNodeSP clone() const override;
+
+ KisPaintDeviceSP original() const override;
+ KisPaintDeviceSP paintDevice() const override;
+ bool isFakeNode() const override;
+
+ KUndo2Command* crop(const QRect & rect) override;
+
+ KUndo2Command* transform(const QTransform &transform) override;
+
+ bool supportsPerspectiveTransform() const override;
+
+private:
+ struct Private;
+ const QScopedPointer m_d;
+};
+
+typedef KisSharedPtr KisDecorationsWrapperLayerSP;
+typedef KisWeakSharedPtr KisDecorationsWrapperLayerWSP;
+
+
+#endif // KISDECORATIONSWRAPPERLAYER_H
diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp
index 76b9d014b0..574ed556dc 100644
--- a/libs/ui/KisDocument.cpp
+++ b/libs/ui/KisDocument.cpp
@@ -1,2141 +1,2198 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisMainWindow.h" // XXX: remove
#include // XXX: remove
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Krita Image
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_layer_utils.h"
// Local
#include "KisViewManager.h"
#include "kis_clipboard.h"
#include "widgets/kis_custom_image_widget.h"
#include "canvas/kis_canvas2.h"
#include "flake/kis_shape_controller.h"
#include "kis_statusbar.h"
#include "widgets/kis_progress_widget.h"
#include "kis_canvas_resource_provider.h"
#include "KisResourceServerProvider.h"
#include "kis_node_manager.h"
#include "KisPart.h"
#include "KisApplication.h"
#include "KisDocument.h"
#include "KisImportExportManager.h"
#include "KisView.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_image_barrier_lock_adapter.h"
#include "KisReferenceImagesLayer.h"
#include
#include "kis_config_notifier.h"
#include "kis_async_action_feedback.h"
#include "KisCloneDocumentStroke.h"
#include
+#include
+#include "kis_simple_stroke_strategy.h"
// Define the protocol used here for embedded documents' URL
// This used to "store" but QUrl didn't like it,
// so let's simply make it "tar" !
#define STORE_PROTOCOL "tar"
// The internal path is a hack to make QUrl happy and for document children
#define INTERNAL_PROTOCOL "intern"
#define INTERNAL_PREFIX "intern:/"
// Warning, keep it sync in koStore.cc
#include
using namespace std;
namespace {
constexpr int errorMessageTimeout = 5000;
constexpr int successMessageTimeout = 1000;
}
/**********************************************************
*
* KisDocument
*
**********************************************************/
//static
QString KisDocument::newObjectName()
{
static int s_docIFNumber = 0;
QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
return name;
}
class UndoStack : public KUndo2Stack
{
public:
UndoStack(KisDocument *doc)
: KUndo2Stack(doc),
m_doc(doc)
{
}
void setIndex(int idx) override {
KisImageWSP image = this->image();
image->requestStrokeCancellation();
if(image->tryBarrierLock()) {
KUndo2Stack::setIndex(idx);
image->unlock();
}
}
void notifySetIndexChangedOneCommand() override {
KisImageWSP image = this->image();
image->unlock();
/**
* Some very weird commands may emit blocking signals to
* the GUI (e.g. KisGuiContextCommand). Here is the best thing
* we can do to avoid the deadlock
*/
while(!image->tryBarrierLock()) {
QApplication::processEvents();
}
}
void undo() override {
KisImageWSP image = this->image();
image->requestUndoDuringStroke();
if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) {
return;
}
if(image->tryBarrierLock()) {
KUndo2Stack::undo();
image->unlock();
}
}
void redo() override {
KisImageWSP image = this->image();
if(image->tryBarrierLock()) {
KUndo2Stack::redo();
image->unlock();
}
}
private:
KisImageWSP image() {
KisImageWSP currentImage = m_doc->image();
Q_ASSERT(currentImage);
return currentImage;
}
private:
KisDocument *m_doc;
};
class Q_DECL_HIDDEN KisDocument::Private
{
public:
- Private(KisDocument *q)
- : docInfo(new KoDocumentInfo(q)) // deleted by QObject
- , importExportManager(new KisImportExportManager(q)) // deleted manually
- , autoSaveTimer(new QTimer(q))
- , undoStack(new UndoStack(q)) // deleted by QObject
+ Private(KisDocument *_q)
+ : q(_q)
+ , docInfo(new KoDocumentInfo(_q)) // deleted by QObject
+ , importExportManager(new KisImportExportManager(_q)) // deleted manually
+ , autoSaveTimer(new QTimer(_q))
+ , undoStack(new UndoStack(_q)) // deleted by QObject
, m_bAutoDetectedMime(false)
, modified(false)
, readwrite(true)
, firstMod(QDateTime::currentDateTime())
, lastMod(firstMod)
, nserver(new KisNameServer(1))
, imageIdleWatcher(2000 /*ms*/)
, globalAssistantsColor(KisConfig(true).defaultAssistantsColor())
, savingLock(&savingMutex)
, batchMode(false)
{
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
unit = KoUnit::Inch;
} else {
unit = KoUnit::Centimeter;
}
}
- Private(const Private &rhs, KisDocument *q)
- : docInfo(new KoDocumentInfo(*rhs.docInfo, q))
- , importExportManager(new KisImportExportManager(q))
- , autoSaveTimer(new QTimer(q))
- , undoStack(new UndoStack(q))
+ Private(const Private &rhs, KisDocument *_q)
+ : q(_q)
+ , docInfo(new KoDocumentInfo(*rhs.docInfo, _q))
+ , importExportManager(new KisImportExportManager(_q))
+ , autoSaveTimer(new QTimer(_q))
+ , undoStack(new UndoStack(_q))
, nserver(new KisNameServer(*rhs.nserver))
, preActivatedNode(0) // the node is from another hierarchy!
, imageIdleWatcher(2000 /*ms*/)
, savingLock(&savingMutex)
{
- copyFromImpl(rhs, q, CONSTRUCT);
+ copyFromImpl(rhs, _q, CONSTRUCT);
}
~Private() {
// Don't delete m_d->shapeController because it's in a QObject hierarchy.
delete nserver;
}
+ KisDocument *q = 0;
KoDocumentInfo *docInfo = 0;
KoUnit unit;
KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options]
QByteArray mimeType; // The actual mimetype of the document
QByteArray outputMimeType; // The mimetype to use when saving
QTimer *autoSaveTimer;
QString lastErrorMessage; // see openFile()
QString lastWarningMessage;
int autoSaveDelay = 300; // in seconds, 0 to disable.
bool modifiedAfterAutosave = false;
bool isAutosaving = false;
bool disregardAutosaveFailure = false;
int autoSaveFailureCount = 0;
KUndo2Stack *undoStack = 0;
KisGuidesConfig guidesConfig;
KisMirrorAxisConfig mirrorAxisConfig;
bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself
QUrl m_url; // local url - the one displayed to the user.
QString m_file; // Local file - the only one the part implementation should deal with.
QMutex savingMutex;
bool modified = false;
bool readwrite = false;
QDateTime firstMod;
QDateTime lastMod;
KisNameServer *nserver;
KisImageSP image;
KisImageSP savingImage;
KisNodeWSP preActivatedNode;
KisShapeController* shapeController = 0;
KoShapeController* koShapeController = 0;
KisIdleWatcher imageIdleWatcher;
QScopedPointer imageIdleConnection;
QList assistants;
QColor globalAssistantsColor;
KisSharedPtr referenceImagesLayer;
QList paletteList;
bool ownsPaletteList = false;
KisGridConfig gridConfig;
StdLockableWrapper savingLock;
bool modifiedWhileSaving = false;
QScopedPointer backgroundSaveDocument;
QPointer savingUpdater;
QFuture childSavingFuture;
KritaUtils::ExportFileJob backgroundSaveJob;
bool isRecovered = false;
bool batchMode { false };
+ void syncDecorationsWrapperLayerState();
+
void setImageAndInitIdleWatcher(KisImageSP _image) {
image = _image;
imageIdleWatcher.setTrackedImage(image);
if (image) {
imageIdleConnection.reset(
new KisSignalAutoConnection(
&imageIdleWatcher, SIGNAL(startedIdleMode()),
image.data(), SLOT(explicitRegenerateLevelOfDetail())));
}
}
void copyFrom(const Private &rhs, KisDocument *q);
void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy);
/// clones the palette list oldList
/// the ownership of the returned KoColorSet * belongs to the caller
QList clonePaletteList(const QList &oldList);
class StrippedSafeSavingLocker;
};
+
+void KisDocument::Private::syncDecorationsWrapperLayerState()
+{
+ KisImageSP image = this->image;
+ KisDecorationsWrapperLayerSP decorationsLayer =
+ KisLayerUtils::findNodeByType(image->root());
+
+ const bool needsDecorationsWrapper =
+ gridConfig.showGrid() || (guidesConfig.showGuides() && guidesConfig.hasGuides()) || !assistants.isEmpty();
+
+ struct SyncDecorationsWrapperStroke : public KisSimpleStrokeStrategy {
+ SyncDecorationsWrapperStroke(KisDocument *document, bool needsDecorationsWrapper)
+ : KisSimpleStrokeStrategy("sync-decorations-wrapper", kundo2_noi18n("start-isolated-mode")),
+ m_document(document),
+ m_needsDecorationsWrapper(needsDecorationsWrapper)
+ {
+ this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
+ setClearsRedoOnStart(false);
+ }
+
+ void initStrokeCallback() {
+ KisDecorationsWrapperLayerSP decorationsLayer =
+ KisLayerUtils::findNodeByType(m_document->image()->root());
+
+ if (m_needsDecorationsWrapper && !decorationsLayer) {
+ m_document->image()->addNode(new KisDecorationsWrapperLayer(m_document));
+ } else if (!m_needsDecorationsWrapper && decorationsLayer) {
+ m_document->image()->removeNode(decorationsLayer);
+ }
+ }
+
+ private:
+ KisDocument *m_document = 0;
+ bool m_needsDecorationsWrapper = false;
+ };
+
+ KisStrokeId id = image->startStroke(new SyncDecorationsWrapperStroke(q, needsDecorationsWrapper));
+ image->endStroke(id);
+}
+
void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q)
{
copyFromImpl(rhs, q, KisDocument::REPLACE);
}
void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy)
{
if (policy == REPLACE) {
delete docInfo;
}
docInfo = (new KoDocumentInfo(*rhs.docInfo, q));
unit = rhs.unit;
mimeType = rhs.mimeType;
outputMimeType = rhs.outputMimeType;
if (policy == REPLACE) {
q->setGuidesConfig(rhs.guidesConfig);
q->setMirrorAxisConfig(rhs.mirrorAxisConfig);
q->setModified(rhs.modified);
q->setAssistants(KisPaintingAssistant::cloneAssistantList(rhs.assistants));
q->setGridConfig(rhs.gridConfig);
} else {
// in CONSTRUCT mode, we cannot use the functions of KisDocument
// because KisDocument does not yet have a pointer to us.
guidesConfig = rhs.guidesConfig;
mirrorAxisConfig = rhs.mirrorAxisConfig;
modified = rhs.modified;
assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants);
gridConfig = rhs.gridConfig;
}
m_bAutoDetectedMime = rhs.m_bAutoDetectedMime;
m_url = rhs.m_url;
m_file = rhs.m_file;
readwrite = rhs.readwrite;
firstMod = rhs.firstMod;
lastMod = rhs.lastMod;
// XXX: the display properties will be shared between different snapshots
globalAssistantsColor = rhs.globalAssistantsColor;
if (policy == REPLACE) {
QList newPaletteList = clonePaletteList(rhs.paletteList);
q->setPaletteList(newPaletteList, /* emitSignal = */ true);
// we still do not own palettes if we did not
} else {
paletteList = rhs.paletteList;
}
batchMode = rhs.batchMode;
}
QList KisDocument::Private::clonePaletteList(const QList &oldList)
{
QList newList;
Q_FOREACH (KoColorSet *palette, oldList) {
newList << new KoColorSet(*palette);
}
return newList;
}
class KisDocument::Private::StrippedSafeSavingLocker {
public:
StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
: m_locked(false)
, m_image(image)
, m_savingLock(savingMutex)
, m_imageLock(image, true)
{
/**
* Initial try to lock both objects. Locking the image guards
* us from any image composition threads running in the
* background, while savingMutex guards us from entering the
* saving code twice by autosave and main threads.
*
* Since we are trying to lock multiple objects, so we should
* do it in a safe manner.
*/
m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
if (!m_locked) {
m_image->requestStrokeEnd();
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
// one more try...
m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
}
}
~StrippedSafeSavingLocker() {
if (m_locked) {
m_imageLock.unlock();
m_savingLock.unlock();
}
}
bool successfullyLocked() const {
return m_locked;
}
private:
bool m_locked;
KisImageSP m_image;
StdLockableWrapper m_savingLock;
KisImageBarrierLockAdapter m_imageLock;
};
KisDocument::KisDocument()
: d(new Private(this))
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setObjectName(newObjectName());
// preload the krita resources
KisResourceServerProvider::instance();
d->shapeController = new KisShapeController(this, d->nserver);
d->koShapeController = new KoShapeController(0, d->shapeController);
d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController);
slotConfigChanged();
}
KisDocument::KisDocument(const KisDocument &rhs)
: QObject(),
d(new Private(*rhs.d, this))
{
copyFromDocumentImpl(rhs, CONSTRUCT);
}
KisDocument::~KisDocument()
{
// wait until all the pending operations are in progress
waitForSavingToComplete();
/**
* Push a timebomb, which will try to release the memory after
* the document has been deleted
*/
KisPaintDevice::createMemoryReleaseObject()->deleteLater();
d->autoSaveTimer->disconnect(this);
d->autoSaveTimer->stop();
delete d->importExportManager;
// Despite being QObject they needs to be deleted before the image
delete d->shapeController;
delete d->koShapeController;
if (d->image) {
d->image->notifyAboutToBeDeleted();
/**
* WARNING: We should wait for all the internal image jobs to
* finish before entering KisImage's destructor. The problem is,
* while execution of KisImage::~KisImage, all the weak shared
* pointers pointing to the image enter an inconsistent
* state(!). The shared counter is already zero and destruction
* has started, but the weak reference doesn't know about it,
* because KisShared::~KisShared hasn't been executed yet. So all
* the threads running in background and having weak pointers will
* enter the KisImage's destructor as well.
*/
d->image->requestStrokeCancellation();
d->image->waitForDone();
// clear undo commands that can still point to the image
d->undoStack->clear();
d->image->waitForDone();
KisImageWSP sanityCheckPointer = d->image;
Q_UNUSED(sanityCheckPointer);
// The following line trigger the deletion of the image
d->image.clear();
// check if the image has actually been deleted
KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
}
if (d->ownsPaletteList) {
qDeleteAll(d->paletteList);
}
delete d;
}
bool KisDocument::reload()
{
// XXX: reimplement!
return false;
}
KisDocument *KisDocument::clone()
{
return new KisDocument(*this);
}
bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration)
{
QFileInfo filePathInfo(job.filePath);
if (filePathInfo.exists() && !filePathInfo.isWritable()) {
slotCompleteSavingDocument(job, ImportExportCodes::NoAccessToWrite,
i18n("%1 cannot be written to. Please save under a different name.", job.filePath));
//return ImportExportCodes::NoAccessToWrite;
return false;
}
KisConfig cfg(true);
if (cfg.backupFile() && filePathInfo.exists()) {
QString backupDir;
switch(cfg.readEntry("backupfilelocation", 0)) {
case 1:
backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
break;
case 2:
backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
break;
default:
// Do nothing: the empty string is user file location
break;
}
int numOfBackupsKept = cfg.readEntry("numberofbackupfiles", 1);
QString suffix = cfg.readEntry("backupfilesuffix", "~");
if (numOfBackupsKept == 1) {
KBackup::simpleBackupFile(job.filePath, backupDir, suffix);
}
else if (numOfBackupsKept > 2) {
KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept);
}
}
//KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
if (job.mimeType.isEmpty()) {
KisImportExportErrorCode error = ImportExportCodes::FileFormatIncorrect;
slotCompleteSavingDocument(job, error, error.errorMessage());
return false;
}
const QString actionName =
job.flags & KritaUtils::SaveIsExporting ?
i18n("Exporting Document...") :
i18n("Saving Document...");
bool started =
initiateSavingInBackground(actionName,
this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,QString)),
job, exportConfiguration);
if (!started) {
emit canceled(QString());
}
return started;
}
bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
SaveFlags flags = SaveIsExporting;
if (showWarnings) {
flags |= SaveShowWarnings;
}
KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8")
.arg(url.toLocalFile())
.arg(QString::fromLatin1(mimeType))
.arg(d->image->width())
.arg(d->image->height())
.arg(d->image->nlayers())
.arg(d->image->animationInterface()->totalLength())
.arg(d->image->animationInterface()->framerate())
.arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration"));
return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(),
mimeType,
flags),
exportConfiguration);
}
bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8")
.arg(_url.toLocalFile())
.arg(QString::fromLatin1(mimeType))
.arg(d->image->width())
.arg(d->image->height())
.arg(d->image->nlayers())
.arg(d->image->animationInterface()->totalLength())
.arg(d->image->animationInterface()->framerate())
.arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")
.arg(url().toLocalFile()));
return exportDocumentImpl(ExportFileJob(_url.toLocalFile(),
mimeType,
showWarnings ? SaveShowWarnings : SaveNone),
exportConfiguration);
}
bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
return saveAs(url(), mimeType(), showWarnings, exportConfiguration);
}
QByteArray KisDocument::serializeToNativeByteArray()
{
QByteArray byteArray;
QBuffer buffer(&byteArray);
QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export));
filter->setBatchMode(true);
filter->setMimeType(nativeFormatMimeType());
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return byteArray;
}
d->savingImage = d->image;
if (!filter->convert(this, &buffer).isOk()) {
qWarning() << "serializeToByteArray():: Could not export to our native format";
}
return byteArray;
}
void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage)
{
if (status.isCancelled())
return;
const QString fileName = QFileInfo(job.filePath).fileName();
if (!status.isOk()) {
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during saving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
if (!fileBatchMode()) {
const QString filePath = job.filePath;
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage)));
}
} else {
if (!(job.flags & KritaUtils::SaveIsExporting)) {
const QString existingAutoSaveBaseName = localFilePath();
const bool wasRecovered = isRecovered();
setUrl(QUrl::fromLocalFile(job.filePath));
setLocalFilePath(job.filePath);
setMimeType(job.mimeType);
updateEditingTime(true);
if (!d->modifiedWhileSaving) {
/**
* If undo stack is already clean/empty, it doesn't emit any
* signals, so we might forget update document modified state
* (which was set, e.g. while recovering an autosave file)
*/
if (d->undoStack->isClean()) {
setModified(false);
} else {
d->undoStack->setClean();
}
}
setRecovered(false);
removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered);
}
emit completed();
emit sigSavingFinished();
emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
}
}
QByteArray KisDocument::mimeType() const
{
return d->mimeType;
}
void KisDocument::setMimeType(const QByteArray & mimeType)
{
d->mimeType = mimeType;
}
bool KisDocument::fileBatchMode() const
{
return d->batchMode;
}
void KisDocument::setFileBatchMode(const bool batchMode)
{
d->batchMode = batchMode;
}
KisDocument* KisDocument::lockAndCloneForSaving()
{
// force update of all the asynchronous nodes before cloning
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root());
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
if (!window->viewManager()->blockUntilOperationsFinished(d->image)) {
return 0;
}
}
}
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return 0;
}
return new KisDocument(*this);
}
KisDocument *KisDocument::lockAndCreateSnapshot()
{
KisDocument *doc = lockAndCloneForSaving();
if (doc) {
// clone palette list
doc->d->paletteList = doc->d->clonePaletteList(doc->d->paletteList);
doc->d->ownsPaletteList = true;
}
return doc;
}
void KisDocument::copyFromDocument(const KisDocument &rhs)
{
copyFromDocumentImpl(rhs, REPLACE);
}
void KisDocument::copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy)
{
if (policy == REPLACE) {
d->copyFrom(*(rhs.d), this);
d->undoStack->clear();
} else {
// in CONSTRUCT mode, d should be already initialized
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
d->shapeController = new KisShapeController(this, d->nserver);
d->koShapeController = new KoShapeController(0, d->shapeController);
d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController);
}
setObjectName(rhs.objectName());
slotConfigChanged();
if (rhs.d->image) {
if (policy == REPLACE) {
d->image->barrierLock(/* readOnly = */ false);
rhs.d->image->barrierLock(/* readOnly = */ true);
d->image->copyFromImage(*(rhs.d->image));
d->image->unlock();
rhs.d->image->unlock();
setCurrentImage(d->image, /* forceInitialUpdate = */ true);
} else {
// clone the image with keeping the GUIDs of the layers intact
// NOTE: we expect the image to be locked!
setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false);
}
}
if (rhs.d->preActivatedNode) {
QQueue linearizedNodes;
KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(),
[&linearizedNodes](KisNodeSP node) {
linearizedNodes.enqueue(node);
});
KisLayerUtils::recursiveApplyNodes(d->image->root(),
[&linearizedNodes, &rhs, this](KisNodeSP node) {
KisNodeSP refNode = linearizedNodes.dequeue();
if (rhs.d->preActivatedNode.data() == refNode.data()) {
d->preActivatedNode = node;
}
});
}
KisNodeSP foundNode = KisLayerUtils::recursiveFindNode(image()->rootLayer(), [](KisNodeSP node) -> bool { return dynamic_cast(node.data()); });
KisReferenceImagesLayer *refLayer = dynamic_cast(foundNode.data());
setReferenceImagesLayer(refLayer, /* updateImage = */ false);
+ KisDecorationsWrapperLayerSP decorationsLayer =
+ KisLayerUtils::findNodeByType(d->image->root());
+ if (decorationsLayer) {
+ decorationsLayer->setDocument(this);
+ }
+
+
if (policy == REPLACE) {
setModified(true);
}
}
bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
{
{
/**
* The caller guarantees that noone else uses the document (usually,
* it is a temporary docuent created specifically for exporting), so
* we don't need to copy or lock the document. Instead we should just
* ensure the barrier lock is synced and then released.
*/
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return false;
}
}
d->savingImage = d->image;
const QString fileName = url.toLocalFile();
KisImportExportErrorCode status =
d->importExportManager->
exportDocument(fileName, fileName, mimeType, false, exportConfiguration);
d->savingImage = 0;
return status.isOk();
}
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration)
{
return initiateSavingInBackground(actionName, receiverObject, receiverMethod,
job, exportConfiguration, std::unique_ptr());
}
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration,
std::unique_ptr &&optionalClonedDocument)
{
KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false);
QScopedPointer clonedDocument;
if (!optionalClonedDocument) {
clonedDocument.reset(lockAndCloneForSaving());
} else {
clonedDocument.reset(optionalClonedDocument.release());
}
// we block saving until the current saving is finished!
if (!clonedDocument || !d->savingMutex.tryLock()) {
return false;
}
auto waitForImage = [] (KisImageSP image) {
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
window->viewManager()->blockUntilOperationsFinishedForced(image);
}
}
};
{
KisNodeSP newRoot = clonedDocument->image()->root();
KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) {
KisLayerUtils::forceAllDelayedNodesUpdate(newRoot);
waitForImage(clonedDocument->image());
}
}
KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) {
waitForImage(clonedDocument->image());
}
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false);
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false);
d->backgroundSaveDocument.reset(clonedDocument.take());
d->backgroundSaveJob = job;
d->modifiedWhileSaving = false;
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = true;
}
connect(d->backgroundSaveDocument.data(),
SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString)),
this,
SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString)));
connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)),
receiverObject, receiverMethod, Qt::UniqueConnection);
bool started =
d->backgroundSaveDocument->startExportInBackground(actionName,
job.filePath,
job.filePath,
job.mimeType,
job.flags & KritaUtils::SaveShowWarnings,
exportConfiguration);
if (!started) {
// the state should have been deinitialized in slotChildCompletedSavingInBackground()
KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) {
d->backgroundSaveDocument.take()->deleteLater();
d->savingMutex.unlock();
d->backgroundSaveJob = KritaUtils::ExportFileJob();
}
}
return started;
}
void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage)
{
KIS_ASSERT_RECOVER_RETURN(isSaving());
KIS_ASSERT_RECOVER(d->backgroundSaveDocument) {
d->savingMutex.unlock();
return;
}
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = false;
}
d->backgroundSaveDocument.take()->deleteLater();
KIS_ASSERT_RECOVER(d->backgroundSaveJob.isValid()) {
d->savingMutex.unlock();
return;
}
const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
d->backgroundSaveJob = KritaUtils::ExportFileJob();
// unlock at the very end
d->savingMutex.unlock();
KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3")
.arg(job.filePath)
.arg(QString::fromLatin1(job.mimeType))
.arg(!status.isOk() ? exportErrorToUserMessage(status, errorMessage) : "OK"));
emit sigCompleteBackgroundSaving(job, status, errorMessage);
}
void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument)
{
if (!d->modified || !d->modifiedAfterAutosave) return;
const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout);
const bool hadClonedDocument = bool(optionalClonedDocument);
bool started = false;
if (d->image->isIdle() || hadClonedDocument) {
started = initiateSavingInBackground(i18n("Autosaving..."),
this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)),
KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode),
0,
std::move(optionalClonedDocument));
} else {
emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout);
}
if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) {
KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this);
connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)),
this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)),
Qt::BlockingQueuedConnection);
KisStrokeId strokeId = d->image->startStroke(stroke);
d->image->endStroke(strokeId);
setInfiniteAutoSaveInterval();
} else if (!started) {
setEmergencyAutoSaveInterval();
} else {
d->modifiedAfterAutosave = false;
}
}
void KisDocument::slotAutoSave()
{
slotAutoSaveImpl(std::unique_ptr());
}
void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument)
{
slotAutoSaveImpl(std::unique_ptr(clonedDocument));
}
void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage)
{
Q_UNUSED(job);
const QString fileName = QFileInfo(job.filePath).fileName();
if (!status.isOk()) {
setEmergencyAutoSaveInterval();
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during autosaving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
} else {
KisConfig cfg(true);
d->autoSaveDelay = cfg.autoSaveInterval();
if (!d->modifiedWhileSaving) {
d->autoSaveTimer->stop(); // until the next change
d->autoSaveFailureCount = 0;
} else {
setNormalAutoSaveInterval();
}
emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout);
}
}
bool KisDocument::startExportInBackground(const QString &actionName,
const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration)
{
d->savingImage = d->image;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName);
d->importExportManager->setUpdater(d->savingUpdater);
}
}
KisImportExportErrorCode initializationStatus(ImportExportCodes::OK);
d->childSavingFuture =
d->importExportManager->exportDocumentAsyc(location,
realLocation,
mimeType,
initializationStatus,
showWarnings,
exportConfiguration);
if (!initializationStatus.isOk()) {
if (d->savingUpdater) {
d->savingUpdater->cancel();
}
d->savingImage.clear();
emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage());
return false;
}
typedef QFutureWatcher StatusWatcher;
StatusWatcher *watcher = new StatusWatcher();
watcher->setFuture(d->childSavingFuture);
connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground()));
connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
return true;
}
void KisDocument::finishExportInBackground()
{
KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) {
emit sigBackgroundSavingFinished(ImportExportCodes::InternalError, "");
return;
}
KisImportExportErrorCode status =
d->childSavingFuture.result();
const QString errorMessage = status.errorMessage();
d->savingImage.clear();
d->childSavingFuture = QFuture();
d->lastErrorMessage.clear();
if (d->savingUpdater) {
d->savingUpdater->setProgress(100);
}
emit sigBackgroundSavingFinished(status, errorMessage);
}
void KisDocument::setReadWrite(bool readwrite)
{
d->readwrite = readwrite;
setNormalAutoSaveInterval();
Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) {
mainWindow->setReadWrite(readwrite);
}
}
void KisDocument::setAutoSaveDelay(int delay)
{
if (isReadWrite() && delay > 0) {
d->autoSaveTimer->start(delay * 1000);
} else {
d->autoSaveTimer->stop();
}
}
void KisDocument::setNormalAutoSaveInterval()
{
setAutoSaveDelay(d->autoSaveDelay);
d->autoSaveFailureCount = 0;
}
void KisDocument::setEmergencyAutoSaveInterval()
{
const int emergencyAutoSaveInterval = 10; /* sec */
setAutoSaveDelay(emergencyAutoSaveInterval);
d->autoSaveFailureCount++;
}
void KisDocument::setInfiniteAutoSaveInterval()
{
setAutoSaveDelay(-1);
}
KoDocumentInfo *KisDocument::documentInfo() const
{
return d->docInfo;
}
bool KisDocument::isModified() const
{
return d->modified;
}
QPixmap KisDocument::generatePreview(const QSize& size)
{
KisImageSP image = d->image;
if (d->savingImage) image = d->savingImage;
if (image) {
QRect bounds = image->bounds();
QSize newSize = bounds.size();
newSize.scale(size, Qt::KeepAspectRatio);
QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
if (px.size() == QSize(0,0)) {
px = QPixmap(newSize);
QPainter gc(&px);
QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
gc.fillRect(px.rect(), checkBrush);
gc.end();
}
return px;
}
return QPixmap(size);
}
QString KisDocument::generateAutoSaveFileName(const QString & path) const
{
QString retval;
// Using the extension allows to avoid relying on the mime magic when opening
const QString extension (".kra");
QString prefix = KisConfig(true).readEntry("autosavefileshidden") ? QString(".") : QString();
QRegularExpression autosavePattern1("^\\..+-autosave.kra$");
QRegularExpression autosavePattern2("^.+-autosave.kra$");
QFileInfo fi(path);
QString dir = fi.absolutePath();
QString filename = fi.fileName();
if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) {
// Never saved?
#ifdef Q_OS_WIN
// On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921)
retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix);
#else
// On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file
retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix);
#endif
} else {
retval = QString("%1%2%5%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension).arg(prefix);
}
//qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
return retval;
}
bool KisDocument::importDocument(const QUrl &_url)
{
bool ret;
dbgUI << "url=" << _url.url();
// open...
ret = openUrl(_url);
// reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
// File --> Import
if (ret) {
dbgUI << "success, resetting url";
resetURL();
setTitleModified();
}
return ret;
}
bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags)
{
if (!_url.isLocalFile()) {
return false;
}
dbgUI << "url=" << _url.url();
d->lastErrorMessage.clear();
// Reimplemented, to add a check for autosave files and to improve error reporting
if (!_url.isValid()) {
d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ?
return false;
}
QUrl url(_url);
bool autosaveOpened = false;
if (url.isLocalFile() && !fileBatchMode()) {
QString file = url.toLocalFile();
QString asf = generateAutoSaveFileName(file);
if (QFile::exists(asf)) {
KisApplication *kisApp = static_cast(qApp);
kisApp->hideSplashScreen();
//dbgUI <<"asf=" << asf;
// ## TODO compare timestamps ?
int res = QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
switch (res) {
case QMessageBox::Yes :
url.setPath(asf);
autosaveOpened = true;
break;
case QMessageBox::No :
QFile::remove(asf);
break;
default: // Cancel
return false;
}
}
}
bool ret = openUrlInternal(url);
if (autosaveOpened || flags & RecoveryFile) {
setReadWrite(true); // enable save button
setModified(true);
setRecovered(true);
}
else {
if (ret) {
if (!(flags & DontAddToRecent)) {
KisPart::instance()->addRecentURLToAllMainWindows(_url);
}
// Detect readonly local-files; remote files are assumed to be writable
QFileInfo fi(url.toLocalFile());
setReadWrite(fi.isWritable());
}
setRecovered(false);
}
return ret;
}
class DlgLoadMessages : public KoDialog {
public:
DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) {
setWindowTitle(title);
setWindowIcon(KisIconUtils::loadIcon("warning"));
QWidget *page = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(page);
QHBoxLayout *hlayout = new QHBoxLayout();
QLabel *labelWarning= new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32));
hlayout->addWidget(labelWarning);
hlayout->addWidget(new QLabel(message));
layout->addLayout(hlayout);
QTextBrowser *browser = new QTextBrowser();
QString warning = "";
if (warnings.size() == 1) {
warning += " Reason:
";
}
else {
warning += " Reasons:
";
}
warning += "";
Q_FOREACH(const QString &w, warnings) {
warning += "\n- " + w + "
";
}
warning += "
";
browser->setHtml(warning);
browser->setMinimumHeight(200);
browser->setMinimumWidth(400);
layout->addWidget(browser);
setMainWidget(page);
setButtons(KoDialog::Ok);
resize(minimumSize());
}
};
bool KisDocument::openFile()
{
//dbgUI <<"for" << localFilePath();
if (!QFile::exists(localFilePath())) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
return false;
}
QString filename = localFilePath();
QString typeName = mimeType();
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(filename);
}
//qDebug() << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = filename;
while (path.length() > 0) {
path.chop(1);
typeName = KisMimeDatabase::mimeTypeForFile(path);
//qDebug() << "\t" << path << typeName;
if (!typeName.isEmpty()) {
break;
}
}
//qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
}
dbgUI << localFilePath() << "type:" << typeName;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
KoUpdaterPtr updater;
if (window && window->viewManager()) {
updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
d->importExportManager->setUpdater(updater);
}
KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName);
if (!status.isOk()) {
if (window && window->viewManager()) {
updater->cancel();
}
QString msg = status.errorMessage();
if (!msg.isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()),
errorMessage().split("\n") + warningMessage().split("\n"));
dlg.exec();
}
return false;
}
else if (!warningMessage().isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("There were problems opening %1.", prettyPathOrUrl()),
warningMessage().split("\n"));
dlg.exec();
setUrl(QUrl());
}
setMimeTypeAfterLoading(typeName);
emit sigLoadingFinished();
undoStack()->clear();
return true;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
d->mimeType = mimeType.toLatin1();
d->outputMimeType = d->mimeType;
}
bool KisDocument::loadNativeFormat(const QString & file_)
{
return openUrl(QUrl::fromLocalFile(file_));
}
void KisDocument::setModified(bool mod)
{
if (mod) {
updateEditingTime(false);
}
if (d->isAutosaving) // ignore setModified calls due to autosaving
return;
if ( !d->readwrite && d->modified ) {
errKrita << "Can't set a read-only document to 'modified' !" << endl;
return;
}
//dbgUI<<" url:" << url.path();
//dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
d->firstMod = now;
} else if (firstModDelta > 60 || forceStoreElapsed) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
d->firstMod = now;
}
d->lastMod = now;
}
QString KisDocument::prettyPathOrUrl() const
{
QString _url(url().toDisplayString());
#ifdef Q_OS_WIN
if (url().isLocalFile()) {
_url = QDir::toNativeSeparators(_url);
}
#endif
return _url;
}
// Get caption from document info (title(), in about page)
QString KisDocument::caption() const
{
QString c;
const QString _url(url().fileName());
// if URL is empty...it is probably an unsaved file
if (_url.isEmpty()) {
c = " [" + i18n("Not Saved") + "] ";
} else {
c = _url; // Fall back to document URL
}
return c;
}
void KisDocument::setTitleModified()
{
emit titleModified(caption(), isModified());
}
QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
{
return createDomDocument("krita", tagName, version);
}
//static
QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
{
QDomImplementation impl;
QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
QDomDocumentType dtype = impl.createDocumentType(tagName,
QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
url);
// The namespace URN doesn't need to include the version number.
QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
return doc;
}
bool KisDocument::isNativeFormat(const QByteArray& mimetype) const
{
if (mimetype == nativeFormatMimeType())
return true;
return extraNativeMimeTypes().contains(mimetype);
}
void KisDocument::setErrorMessage(const QString& errMsg)
{
d->lastErrorMessage = errMsg;
}
QString KisDocument::errorMessage() const
{
return d->lastErrorMessage;
}
void KisDocument::setWarningMessage(const QString& warningMsg)
{
d->lastWarningMessage = warningMsg;
}
QString KisDocument::warningMessage() const
{
return d->lastWarningMessage;
}
void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered)
{
//qDebug() << "removeAutoSaveFiles";
// Eliminate any auto-save file
QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir
//qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf);
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autosavefile" << asf;
QFile::remove(asf);
}
asf = generateAutoSaveFileName(QString()); // and the one in $HOME
//qDebug() << "Autsavefile in $home" << asf;
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autsavefile 2" << asf;
QFile::remove(asf);
}
QList expressions;
expressions << QRegularExpression("^\\..+-autosave.kra$")
<< QRegularExpression("^.+-autosave.kra$");
Q_FOREACH(const QRegularExpression &rex, expressions) {
if (wasRecovered &&
!autosaveBaseName.isEmpty() &&
rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() &&
QFile::exists(autosaveBaseName)) {
QFile::remove(autosaveBaseName);
}
}
}
KoUnit KisDocument::unit() const
{
return d->unit;
}
void KisDocument::setUnit(const KoUnit &unit)
{
if (d->unit != unit) {
d->unit = unit;
emit unitChanged(unit);
}
}
KUndo2Stack *KisDocument::undoStack()
{
return d->undoStack;
}
KisImportExportManager *KisDocument::importExportManager() const
{
return d->importExportManager;
}
void KisDocument::addCommand(KUndo2Command *command)
{
if (command)
d->undoStack->push(command);
}
void KisDocument::beginMacro(const KUndo2MagicString & text)
{
d->undoStack->beginMacro(text);
}
void KisDocument::endMacro()
{
d->undoStack->endMacro();
}
void KisDocument::slotUndoStackCleanChanged(bool value)
{
setModified(!value);
}
void KisDocument::slotConfigChanged()
{
KisConfig cfg(true);
if (d->undoStack->undoLimit() != cfg.undoStackLimit()) {
if (!d->undoStack->isClean()) {
d->undoStack->clear();
}
d->undoStack->setUndoLimit(cfg.undoStackLimit());
}
d->autoSaveDelay = cfg.autoSaveInterval();
setNormalAutoSaveInterval();
}
void KisDocument::clearUndoHistory()
{
d->undoStack->clear();
}
KisGridConfig KisDocument::gridConfig() const
{
return d->gridConfig;
}
void KisDocument::setGridConfig(const KisGridConfig &config)
{
if (d->gridConfig != config) {
d->gridConfig = config;
+ d->syncDecorationsWrapperLayerState();
emit sigGridConfigChanged(config);
}
}
QList &KisDocument::paletteList()
{
return d->paletteList;
}
void KisDocument::setPaletteList(const QList &paletteList, bool emitSignal)
{
if (d->paletteList != paletteList) {
QList oldPaletteList = d->paletteList;
d->paletteList = paletteList;
if (emitSignal) {
emit sigPaletteListChanged(oldPaletteList, paletteList);
}
}
}
const KisGuidesConfig& KisDocument::guidesConfig() const
{
return d->guidesConfig;
}
void KisDocument::setGuidesConfig(const KisGuidesConfig &data)
{
if (d->guidesConfig == data) return;
d->guidesConfig = data;
+ d->syncDecorationsWrapperLayerState();
emit sigGuidesConfigChanged(d->guidesConfig);
}
const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const
{
return d->mirrorAxisConfig;
}
void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config)
{
if (d->mirrorAxisConfig == config) {
return;
}
d->mirrorAxisConfig = config;
setModified(true);
emit sigMirrorAxisConfigChanged();
}
void KisDocument::resetURL() {
setUrl(QUrl());
setLocalFilePath(QString());
}
KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
{
return new KoDocumentInfoDlg(parent, docInfo);
}
bool KisDocument::isReadWrite() const
{
return d->readwrite;
}
QUrl KisDocument::url() const
{
return d->m_url;
}
bool KisDocument::closeUrl(bool promptToSave)
{
if (promptToSave) {
if ( isReadWrite() && isModified()) {
Q_FOREACH (KisView *view, KisPart::instance()->views()) {
if (view && view->document() == this) {
if (!view->queryClose()) {
return false;
}
}
}
}
}
// Not modified => ok and delete temp file.
d->mimeType = QByteArray();
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
void KisDocument::setUrl(const QUrl &url)
{
d->m_url = url;
}
QString KisDocument::localFilePath() const
{
return d->m_file;
}
void KisDocument::setLocalFilePath( const QString &localFilePath )
{
d->m_file = localFilePath;
}
bool KisDocument::openUrlInternal(const QUrl &url)
{
if ( !url.isValid() ) {
return false;
}
if (d->m_bAutoDetectedMime) {
d->mimeType = QByteArray();
d->m_bAutoDetectedMime = false;
}
QByteArray mimetype = d->mimeType;
if ( !closeUrl() ) {
return false;
}
d->mimeType = mimetype;
setUrl(url);
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
bool ret;
// set the mimetype only if it was not already set (for example, by the host application)
if (d->mimeType.isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile());
d->mimeType = mime.toLocal8Bit();
d->m_bAutoDetectedMime = true;
}
setUrl(d->m_url);
ret = openFile();
if (ret) {
emit completed();
} else {
emit canceled(QString());
}
return ret;
}
return false;
}
bool KisDocument::newImage(const QString& name,
qint32 width, qint32 height,
const KoColorSpace* cs,
const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle,
int numberOfLayers,
const QString &description, const double imageResolution)
{
Q_ASSERT(cs);
KisImageSP image;
if (!cs) return false;
QApplication::setOverrideCursor(Qt::BusyCursor);
image = new KisImage(createUndoStore(), width, height, cs, name);
Q_CHECK_PTR(image);
connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
image->setResolution(imageResolution, imageResolution);
image->assignImageProfile(cs->profile());
documentInfo()->setAboutInfo("title", name);
documentInfo()->setAboutInfo("abstract", description);
KisLayerSP layer;
if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) {
KoColor strippedAlpha = bgColor;
strippedAlpha.setOpacity(OPACITY_OPAQUE_U8);
if (bgStyle == KisConfig::RASTER_LAYER) {
layer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);;
layer->paintDevice()->setDefaultPixel(strippedAlpha);
} else if (bgStyle == KisConfig::FILL_LAYER) {
KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration();
filter_config->setProperty("color", strippedAlpha.toQColor());
layer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection());
}
layer->setOpacity(bgColor.opacityU8());
if (numberOfLayers > 1) {
//Lock bg layer if others are present.
layer->setUserLocked(true);
}
}
else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer).
image->setDefaultProjectionColor(bgColor);
layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
}
Q_CHECK_PTR(layer);
image->addNode(layer.data(), image->rootLayer().data());
layer->setDirty(QRect(0, 0, width, height));
setCurrentImage(image);
for(int i = 1; i < numberOfLayers; ++i) {
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
image->addNode(layer, image->root(), i);
layer->setDirty(QRect(0, 0, width, height));
}
KisConfig cfg(false);
cfg.defImageWidth(width);
cfg.defImageHeight(height);
cfg.defImageResolution(imageResolution);
cfg.defColorModel(image->colorSpace()->colorModelId().id());
cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id());
cfg.defColorProfile(image->colorSpace()->profile()->name());
KisUsageLogger::log(i18n("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8"
, name
, width, height
, imageResolution * 72.0
, image->colorSpace()->colorModelId().name(), image->colorSpace()->colorDepthId().name()
, image->colorSpace()->profile()->name()
, numberOfLayers));
QApplication::restoreOverrideCursor();
return true;
}
bool KisDocument::isSaving() const
{
const bool result = d->savingMutex.tryLock();
if (result) {
d->savingMutex.unlock();
}
return !result;
}
void KisDocument::waitForSavingToComplete()
{
if (isSaving()) {
KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0);
f.waitForMutex(&d->savingMutex);
}
}
KoShapeControllerBase *KisDocument::shapeController() const
{
return d->shapeController;
}
KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const
{
return d->shapeController->shapeForNode(layer);
}
QList KisDocument::assistants() const
{
return d->assistants;
}
void KisDocument::setAssistants(const QList &value)
{
if (d->assistants != value) {
d->assistants = value;
+ d->syncDecorationsWrapperLayerState();
emit sigAssistantsChanged();
}
}
KisSharedPtr KisDocument::referenceImagesLayer() const
{
return d->referenceImagesLayer.data();
}
void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage)
{
if (d->referenceImagesLayer == layer) {
return;
}
if (d->referenceImagesLayer) {
d->referenceImagesLayer->disconnect(this);
}
if (updateImage) {
if (layer) {
d->image->addNode(layer);
} else {
d->image->removeNode(d->referenceImagesLayer);
}
}
d->referenceImagesLayer = layer;
if (d->referenceImagesLayer) {
connect(d->referenceImagesLayer, SIGNAL(sigUpdateCanvas(QRectF)),
this, SIGNAL(sigReferenceImagesChanged()));
}
emit sigReferenceImagesLayerChanged(layer);
}
void KisDocument::setPreActivatedNode(KisNodeSP activatedNode)
{
d->preActivatedNode = activatedNode;
}
KisNodeSP KisDocument::preActivatedNode() const
{
return d->preActivatedNode;
}
KisImageWSP KisDocument::image() const
{
return d->image;
}
KisImageSP KisDocument::savingImage() const
{
return d->savingImage;
}
void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate)
{
if (d->image) {
// Disconnect existing sig/slot connections
d->image->setUndoStore(new KisDumbUndoStore());
d->image->disconnect(this);
d->shapeController->setImage(0);
d->image = 0;
}
if (!image) return;
d->setImageAndInitIdleWatcher(image);
d->image->setUndoStore(new KisDocumentUndoStore(this));
d->shapeController->setImage(image);
setModified(false);
connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
if (forceInitialUpdate) {
d->image->initialRefreshGraph();
}
}
void KisDocument::hackPreliminarySetImage(KisImageSP image)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image);
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
}
void KisDocument::setImageModified()
{
// we only set as modified if undo stack is not at clean state
setModified(!d->undoStack->isClean());
}
KisUndoStore* KisDocument::createUndoStore()
{
return new KisDocumentUndoStore(this);
}
bool KisDocument::isAutosaving() const
{
return d->isAutosaving;
}
QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage)
{
return errorMessage.isEmpty() ? status.errorMessage() : errorMessage;
}
void KisDocument::setAssistantsGlobalColor(QColor color)
{
d->globalAssistantsColor = color;
}
QColor KisDocument::assistantsGlobalColor()
{
return d->globalAssistantsColor;
}
QRectF KisDocument::documentBounds() const
{
QRectF bounds = d->image->bounds();
if (d->referenceImagesLayer) {
bounds |= d->referenceImagesLayer->boundingImageRect();
}
return bounds;
}
diff --git a/libs/ui/canvas/kis_grid_config.cpp b/libs/ui/canvas/kis_grid_config.cpp
index 07078c9043..98cb300061 100644
--- a/libs/ui/canvas/kis_grid_config.cpp
+++ b/libs/ui/canvas/kis_grid_config.cpp
@@ -1,97 +1,124 @@
/*
* Copyright (c) 2015 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_grid_config.h"
#include
#include "kis_config.h"
#include "kis_dom_utils.h"
+#include "kis_algebra_2d.h"
struct KisGridConfigStaticRegistrar {
KisGridConfigStaticRegistrar() {
qRegisterMetaType("KisGridConfig");
}
};
static KisGridConfigStaticRegistrar __registrar;
Q_GLOBAL_STATIC(KisGridConfig, staticDefaultObject)
const KisGridConfig& KisGridConfig::defaultGrid()
{
staticDefaultObject->loadStaticData();
return *staticDefaultObject;
}
+void KisGridConfig::transform(const QTransform &transform)
+{
+ if (transform.type() >= QTransform::TxShear) return;
+
+ KisAlgebra2D::DecomposedMatix m(transform);
+
+
+
+ if (m_gridType == 0) {
+ QTransform t = m.scaleTransform();
+
+ const qreal eps = 1e-3;
+ if (KisAlgebra2D::wrapValue(m.angle, 90.0) <= eps) {
+ t *= m.rotateTransform();
+ }
+
+ m_spacing = KisAlgebra2D::abs(t.map(m_spacing));
+
+ } else {
+ if (qFuzzyCompare(m.scaleX, m.scaleY)) {
+ m_cellSpacing = qRound(qAbs(m_cellSpacing * m.scaleX));
+ }
+ }
+ m_offset = KisAlgebra2D::wrapValue(transform.map(m_offset), m_spacing);
+}
+
void KisGridConfig::loadStaticData()
{
KisConfig cfg(true);
m_lineTypeMain = LineTypeInternal(cfg.getGridMainStyle());
m_lineTypeSubdivision = LineTypeInternal(cfg.getGridSubdivisionStyle());
m_colorMain = cfg.getGridMainColor();
m_colorSubdivision = cfg.getGridSubdivisionColor();
}
void KisGridConfig::saveStaticData() const
{
KisConfig cfg(false);
cfg.setGridMainStyle(m_lineTypeMain);
cfg.setGridSubdivisionStyle(m_lineTypeSubdivision);
cfg.setGridMainColor(m_colorMain);
cfg.setGridSubdivisionColor(m_colorSubdivision);
}
QDomElement KisGridConfig::saveDynamicDataToXml(QDomDocument& doc, const QString &tag) const
{
QDomElement gridElement = doc.createElement(tag);
KisDomUtils::saveValue(&gridElement, "showGrid", m_showGrid);
KisDomUtils::saveValue(&gridElement, "snapToGrid", m_snapToGrid);
KisDomUtils::saveValue(&gridElement, "offset", m_offset);
KisDomUtils::saveValue(&gridElement, "spacing", m_spacing);
KisDomUtils::saveValue(&gridElement, "offsetAspectLocked", m_offsetAspectLocked);
KisDomUtils::saveValue(&gridElement, "spacingAspectLocked", m_spacingAspectLocked);
KisDomUtils::saveValue(&gridElement, "subdivision", m_subdivision);
KisDomUtils::saveValue(&gridElement, "angleLeft", m_angleLeft);
KisDomUtils::saveValue(&gridElement, "angleRight", m_angleRight);
KisDomUtils::saveValue(&gridElement, "cellSpacing", m_cellSpacing);
KisDomUtils::saveValue(&gridElement, "gridType", m_gridType);
return gridElement;
}
bool KisGridConfig::loadDynamicDataFromXml(const QDomElement &gridElement)
{
bool result = true;
result &= KisDomUtils::loadValue(gridElement, "showGrid", &m_showGrid);
result &= KisDomUtils::loadValue(gridElement, "snapToGrid", &m_snapToGrid);
result &= KisDomUtils::loadValue(gridElement, "offset", &m_offset);
result &= KisDomUtils::loadValue(gridElement, "spacing", &m_spacing);
result &= KisDomUtils::loadValue(gridElement, "offsetAspectLocked", &m_offsetAspectLocked);
result &= KisDomUtils::loadValue(gridElement, "spacingAspectLocked", &m_spacingAspectLocked);
result &= KisDomUtils::loadValue(gridElement, "subdivision", &m_subdivision);
result &= KisDomUtils::loadValue(gridElement, "angleLeft", &m_angleLeft);
result &= KisDomUtils::loadValue(gridElement, "angleRight", &m_angleRight);
result &= KisDomUtils::loadValue(gridElement, "gridType", &m_gridType);
return result;
}
diff --git a/libs/ui/canvas/kis_grid_config.h b/libs/ui/canvas/kis_grid_config.h
index 6c86637834..5ffac4a22f 100644
--- a/libs/ui/canvas/kis_grid_config.h
+++ b/libs/ui/canvas/kis_grid_config.h
@@ -1,244 +1,246 @@
/*
* Copyright (c) 2015 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_GRID_CONFIG_H
#define __KIS_GRID_CONFIG_H
#include
#include
#include
#include
#include "kritaui_export.h"
class QDomElement;
class QDomDocument;
class KRITAUI_EXPORT KisGridConfig : boost::equality_comparable
{
public:
enum LineTypeInternal {
LINE_SOLID = 0,
LINE_DASHED,
LINE_DOTTED
};
public:
KisGridConfig()
: m_showGrid(false),
m_snapToGrid(false),
m_spacing(20,20),
m_offsetAspectLocked(true),
m_spacingAspectLocked(true),
m_angleLeft(45),
m_angleRight(45),
m_cellSpacing(30),
m_gridType(0),
m_subdivision(2),
m_lineTypeMain(LINE_SOLID),
m_lineTypeSubdivision(LINE_DOTTED),
m_colorMain(200, 200, 200, 200),
m_colorSubdivision(200, 200, 200, 150)
{
loadStaticData();
}
bool operator==(const KisGridConfig &rhs) const {
return
m_showGrid == rhs.m_showGrid &&
m_snapToGrid == rhs.m_snapToGrid &&
m_spacing == rhs.m_spacing &&
m_offset == rhs.m_offset &&
m_offsetAspectLocked == rhs.m_offsetAspectLocked &&
m_spacingAspectLocked == rhs.m_spacingAspectLocked &&
m_angleRight == rhs.m_angleRight &&
m_angleLeft == rhs.m_angleLeft &&
m_gridType == rhs.m_gridType &&
m_cellSpacing == rhs.m_cellSpacing &&
m_subdivision == rhs.m_subdivision &&
m_lineTypeMain == rhs.m_lineTypeMain &&
m_lineTypeSubdivision == rhs.m_lineTypeSubdivision &&
m_colorMain == rhs.m_colorMain &&
m_colorSubdivision == rhs.m_colorSubdivision;
}
bool showGrid() const {
return m_showGrid;
}
void setShowGrid(bool value) {
m_showGrid = value;
}
bool snapToGrid() const {
return m_snapToGrid;
}
void setSnapToGrid(bool value) {
m_snapToGrid = value;
}
QPoint offset() const {
return m_offset;
}
void setOffset(const QPoint &value) {
m_offset = value;
}
QPoint spacing() const {
return m_spacing;
}
void setSpacing(const QPoint &value) {
m_spacing = value;
}
int subdivision() const {
return m_subdivision;
}
void setSubdivision(int value) {
m_subdivision = value;
}
int angleLeft() const {
return m_angleLeft;
}
void setAngleLeft(int angle) {
m_angleLeft = angle;
}
int angleRight() const {
return m_angleRight;
}
void setAngleRight(int angle) {
m_angleRight = angle;
}
int cellSpacing() const {
return m_cellSpacing;
}
void setCellSpacing(int spacing) {
m_cellSpacing = spacing;
}
int gridType() const {
return m_gridType;
}
void setGridType(int type) {
m_gridType = type;
}
bool offsetAspectLocked() const {
return m_offsetAspectLocked;
}
void setOffsetAspectLocked(bool value) {
m_offsetAspectLocked = value;
}
bool spacingAspectLocked() const {
return m_spacingAspectLocked;
}
void setSpacingAspectLocked(bool value) {
m_spacingAspectLocked = value;
}
LineTypeInternal lineTypeMain() const {
return m_lineTypeMain;
}
void setLineTypeMain(LineTypeInternal value) {
m_lineTypeMain = value;
}
LineTypeInternal lineTypeSubdivision() const {
return m_lineTypeSubdivision;
}
void setLineTypeSubdivision(LineTypeInternal value) {
m_lineTypeSubdivision = value;
}
QColor colorMain() const {
return m_colorMain;
}
void setColorMain(const QColor &value) {
m_colorMain = value;
}
QColor colorSubdivision() const {
return m_colorSubdivision;
}
void setColorSubdivision(const QColor &value) {
m_colorSubdivision = value;
}
QPen penMain() const {
return QPen(m_colorMain, 0, toPenStyle(m_lineTypeMain));
}
QPen penSubdivision() const {
return QPen(m_colorSubdivision, 0, toPenStyle(m_lineTypeSubdivision));
}
void loadStaticData();
void saveStaticData() const;
QDomElement saveDynamicDataToXml(QDomDocument& doc, const QString &tag) const;
bool loadDynamicDataFromXml(const QDomElement &parent);
static const KisGridConfig& defaultGrid();
bool isDefault() const {
return *this == defaultGrid();
}
+ void transform(const QTransform &transform);
+
private:
static Qt::PenStyle toPenStyle(LineTypeInternal type) {
return type == LINE_SOLID ? Qt::SolidLine :
type == LINE_DASHED ? Qt::DashLine :
type == LINE_DOTTED ? Qt::DotLine :
Qt::DashDotDotLine;
}
private:
// Dynamic data. Stored in KisDocument.
bool m_showGrid;
bool m_snapToGrid;
QPoint m_spacing;
bool m_offsetAspectLocked;
bool m_spacingAspectLocked;
int m_angleLeft;
int m_angleRight;
int m_cellSpacing;
int m_gridType; // 0 is rectangle, 1 is isometric
int m_subdivision;
QPoint m_offset;
// Static data. Stored in the Krita config.
LineTypeInternal m_lineTypeMain;
LineTypeInternal m_lineTypeSubdivision;
QColor m_colorMain;
QColor m_colorSubdivision;
};
Q_DECLARE_METATYPE(KisGridConfig)
#endif /* __KIS_GRID_CONFIG_H */
diff --git a/plugins/dockers/griddocker/grid_config_widget.cpp b/plugins/dockers/griddocker/grid_config_widget.cpp
index 198cd0e385..8d5841889e 100644
--- a/plugins/dockers/griddocker/grid_config_widget.cpp
+++ b/plugins/dockers/griddocker/grid_config_widget.cpp
@@ -1,359 +1,362 @@
/*
* Copyright (c) 2016 Dmitry Kazakov
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "grid_config_widget.h"
#include "ui_grid_config_widget.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_debug.h"
#include "kis_aspect_ratio_locker.h"
#include "kis_int_parse_spin_box.h"
#include
#include
#include
#include
struct GridConfigWidget::Private
{
Private() : guiSignalsBlocked(false) {}
KisGridConfig gridConfig;
KisGuidesConfig guidesConfig;
bool guiSignalsBlocked {false};
};
GridConfigWidget::GridConfigWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::GridConfigWidget),
m_d(new Private)
{
ui->setupUi(this);
ui->colorMain->setAlphaChannelEnabled(true);
ui->colorSubdivision->setAlphaChannelEnabled(true);
ui->colorGuides->setAlphaChannelEnabled(true);
ui->angleLeftSpinbox->setSuffix(QChar(Qt::Key_degree));
ui->angleRightSpinbox->setSuffix(QChar(Qt::Key_degree));
ui->cellSpacingSpinbox->setSuffix(i18n(" px"));
ui->gridTypeCombobox->addItem(i18n("Rectangle"));
ui->gridTypeCombobox->addItem(i18n("Isometric"));
ui->gridTypeCombobox->setCurrentIndex(0); // set to rectangle by default
slotGridTypeChanged(); // update the UI to hide any elements we don't need
connect(ui->gridTypeCombobox, SIGNAL(currentIndexChanged(int)), SLOT(slotGridTypeChanged()));
setGridConfig(m_d->gridConfig);
setGuidesConfig(m_d->guidesConfig);
// hide offset UI elements if offset is disabled
connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->lblXOffset, SLOT(setVisible(bool)));
connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->lblYOffset, SLOT(setVisible(bool)));
connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->intXOffset, SLOT(setVisible(bool)));
connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->intYOffset, SLOT(setVisible(bool)));
connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->offsetAspectButton, SLOT(setVisible(bool)));
ui->lblXOffset->setVisible(false);
ui->lblYOffset->setVisible(false);
ui->intXOffset->setVisible(false);
ui->intYOffset->setVisible(false);
ui->offsetAspectButton->setVisible(false);
connect(ui->chkShowGrid, SIGNAL(stateChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->chkSnapToGrid, SIGNAL(stateChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->chkShowGuides, SIGNAL(stateChanged(int)), SLOT(slotGuidesGuiChanged()));
connect(ui->chkSnapToGuides, SIGNAL(stateChanged(int)), SLOT(slotGuidesGuiChanged()));
connect(ui->chkLockGuides, SIGNAL(stateChanged(int)), SLOT(slotGuidesGuiChanged()));
connect(ui->intSubdivision, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->angleLeftSpinbox, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->angleRightSpinbox, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->cellSpacingSpinbox, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->selectMainStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->colorMain, SIGNAL(changed(QColor)), SLOT(slotGridGuiChanged()));
connect(ui->selectSubdivisionStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->colorSubdivision, SIGNAL(changed(QColor)), SLOT(slotGridGuiChanged()));
connect(ui->selectGuidesStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotGuidesGuiChanged()));
connect(ui->colorGuides, SIGNAL(changed(QColor)), SLOT(slotGuidesGuiChanged()));
ui->chkOffset->setChecked(false);
KisAspectRatioLocker *offsetLocker = new KisAspectRatioLocker(this);
offsetLocker->connectSpinBoxes(ui->intXOffset, ui->intYOffset, ui->offsetAspectButton);
KisAspectRatioLocker *spacingLocker = new KisAspectRatioLocker(this);
spacingLocker->connectSpinBoxes(ui->intHSpacing, ui->intVSpacing, ui->spacingAspectButton);
connect(offsetLocker, SIGNAL(sliderValueChanged()), SLOT(slotGridGuiChanged()));
connect(offsetLocker, SIGNAL(aspectButtonChanged()), SLOT(slotGridGuiChanged()));
connect(spacingLocker, SIGNAL(sliderValueChanged()), SLOT(slotGridGuiChanged()));
connect(spacingLocker, SIGNAL(aspectButtonChanged()), SLOT(slotGridGuiChanged()));
connect(ui->chkShowRulers,SIGNAL(toggled(bool)),SIGNAL(showRulersChanged(bool)));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotPreferencesUpdated()));
}
GridConfigWidget::~GridConfigWidget()
{
delete ui;
}
void GridConfigWidget::setGridConfig(const KisGridConfig &value)
{
KisGridConfig currentConfig = fetchGuiGridConfig();
if (currentConfig == value) return;
setGridConfigImpl(value);
}
void GridConfigWidget::setGuidesConfig(const KisGuidesConfig &value)
{
KisGuidesConfig currentConfig = fetchGuiGuidesConfig();
if (currentConfig == value) return;
setGuidesConfigImpl(value);
}
void GridConfigWidget::setGridConfigImpl(const KisGridConfig &value)
{
m_d->gridConfig = value;
m_d->guiSignalsBlocked = true;
+ if (!m_d->gridConfig.offset().isNull()) {
+ ui->chkOffset->setChecked(true);
+ }
+
ui->offsetAspectButton->setKeepAspectRatio(m_d->gridConfig.offsetAspectLocked());
ui->spacingAspectButton->setKeepAspectRatio(m_d->gridConfig.spacingAspectLocked());
ui->chkShowGrid->setChecked(m_d->gridConfig.showGrid());
- ui->intHSpacing->setValue(m_d->gridConfig.spacing().x());
ui->intHSpacing->setMaximum(std::numeric_limits::max());
- ui->intVSpacing->setValue(m_d->gridConfig.spacing().y());
ui->intVSpacing->setMaximum(std::numeric_limits::max());
+ ui->intHSpacing->setValue(m_d->gridConfig.spacing().x());
+ ui->intVSpacing->setValue(m_d->gridConfig.spacing().y());
ui->intXOffset->setValue(m_d->gridConfig.offset().x());
ui->intYOffset->setValue(m_d->gridConfig.offset().y());
ui->intSubdivision->setValue(m_d->gridConfig.subdivision());
ui->chkSnapToGrid->setChecked(m_d->gridConfig.snapToGrid());
ui->angleLeftSpinbox->setValue(m_d->gridConfig.angleLeft());
ui->angleRightSpinbox->setValue(m_d->gridConfig.angleRight());
ui->cellSpacingSpinbox->setValue(m_d->gridConfig.cellSpacing());
-
ui->selectMainStyle->setCurrentIndex(int(m_d->gridConfig.lineTypeMain()));
ui->selectSubdivisionStyle->setCurrentIndex(int(m_d->gridConfig.lineTypeSubdivision()));
ui->gridTypeCombobox->setCurrentIndex(m_d->gridConfig.gridType());
ui->colorMain->setColor(m_d->gridConfig.colorMain());
ui->colorSubdivision->setColor(m_d->gridConfig.colorSubdivision());
m_d->guiSignalsBlocked = false;
emit gridValueChanged();
}
void GridConfigWidget::setGuidesConfigImpl(const KisGuidesConfig &value)
{
m_d->guidesConfig = value;
m_d->guiSignalsBlocked = true;
ui->chkShowGuides->setChecked(m_d->guidesConfig.showGuides());
ui->chkSnapToGuides->setChecked(m_d->guidesConfig.snapToGuides());
ui->chkLockGuides->setChecked(m_d->guidesConfig.lockGuides());
ui->selectGuidesStyle->setCurrentIndex(int(m_d->guidesConfig.guidesLineType()));
ui->colorGuides->setColor(m_d->guidesConfig.guidesColor());
m_d->guiSignalsBlocked = false;
emit guidesValueChanged();
}
KisGridConfig GridConfigWidget::gridConfig() const
{
return m_d->gridConfig;
}
KisGuidesConfig GridConfigWidget::guidesConfig() const
{
return m_d->guidesConfig;
}
KisGridConfig GridConfigWidget::fetchGuiGridConfig() const
{
KisGridConfig config;
config.setShowGrid(ui->chkShowGrid->isChecked());
config.setSnapToGrid(ui->chkSnapToGrid->isChecked());
QPoint pt;
pt.rx() = ui->intHSpacing->value();
pt.ry() = ui->intVSpacing->value();
config.setSpacing(pt);
pt.rx() = ui->intXOffset->value();
pt.ry() = ui->intYOffset->value();
config.setOffset(pt);
config.setSubdivision(ui->intSubdivision->value());
config.setAngleLeft(ui->angleLeftSpinbox->value());
config.setAngleRight(ui->angleRightSpinbox->value());
config.setCellSpacing(ui->cellSpacingSpinbox->value());
config.setGridType(ui->gridTypeCombobox->currentIndex());
config.setOffsetAspectLocked(ui->offsetAspectButton->keepAspectRatio());
config.setSpacingAspectLocked(ui->spacingAspectButton->keepAspectRatio());
config.setLineTypeMain(KisGridConfig::LineTypeInternal(ui->selectMainStyle->currentIndex()));
config.setLineTypeSubdivision(KisGridConfig::LineTypeInternal(ui->selectSubdivisionStyle->currentIndex()));
config.setColorMain(ui->colorMain->color());
config.setColorSubdivision(ui->colorSubdivision->color());
return config;
}
KisGuidesConfig GridConfigWidget::fetchGuiGuidesConfig() const
{
KisGuidesConfig config = m_d->guidesConfig;
config.setShowGuides(ui->chkShowGuides->isChecked());
config.setSnapToGuides(ui->chkSnapToGuides->isChecked());
config.setLockGuides(ui->chkLockGuides->isChecked());
config.setGuidesLineType(KisGuidesConfig::LineTypeInternal(ui->selectGuidesStyle->currentIndex()));
config.setGuidesColor(ui->colorGuides->color());
return config;
}
void GridConfigWidget::slotGridGuiChanged()
{
if (m_d->guiSignalsBlocked) return;
KisGridConfig currentConfig = fetchGuiGridConfig();
if (currentConfig == m_d->gridConfig) return;
setGridConfigImpl(currentConfig);
}
void GridConfigWidget::slotPreferencesUpdated()
{
KisConfig cfg(true);
enableIsometricGrid(cfg.useOpenGL()); // Isometric view needs OpenGL
}
void GridConfigWidget::slotGuidesGuiChanged()
{
if (m_d->guiSignalsBlocked) return;
KisGuidesConfig currentConfig = fetchGuiGuidesConfig();
if (currentConfig == m_d->guidesConfig) return;
setGuidesConfigImpl(currentConfig);
}
void GridConfigWidget::slotGridTypeChanged() {
bool showRectangleControls = ui->gridTypeCombobox->currentIndex() == 0;
// specific rectangle UI controls
ui->lblXSpacing->setVisible(showRectangleControls);
ui->lblYSpacing->setVisible(showRectangleControls);
ui->intHSpacing->setVisible(showRectangleControls);
ui->intVSpacing->setVisible(showRectangleControls);
ui->spacingAspectButton->setVisible(showRectangleControls);
ui->lblSubdivision->setVisible(showRectangleControls);
ui->intSubdivision->setVisible(showRectangleControls);
ui->lblSubdivisionStyle->setVisible(showRectangleControls);
ui->selectSubdivisionStyle->setVisible(showRectangleControls);
ui->colorSubdivision->setVisible(showRectangleControls);
// specific isometric UI controls
ui->leftAngleLabel->setVisible(!showRectangleControls);
ui->rightAngleLabel->setVisible(!showRectangleControls);
ui->angleLeftSpinbox->setVisible(!showRectangleControls);
ui->angleRightSpinbox->setVisible(!showRectangleControls);
ui->cellSpacingLabel->setVisible(!showRectangleControls);
ui->cellSpacingSpinbox->setVisible(!showRectangleControls);
// disable snapping for isometric grid type for now
// remember if we had snapping enabled if it was on the rectangule mode
if (!showRectangleControls) {
m_isGridEnabled = ui->chkSnapToGrid->isChecked();
ui->chkSnapToGrid->setEnabled(false);
ui->chkSnapToGrid->setChecked(false);
} else {
ui->chkSnapToGrid->setEnabled(true);
ui->chkSnapToGrid->setChecked(m_isGridEnabled);
}
slotGridGuiChanged();
}
bool GridConfigWidget::showRulers() const
{
return ui->chkShowRulers->isChecked();
}
void GridConfigWidget::enableIsometricGrid(bool value)
{
m_isIsometricGridEnabled = value;
// Isometric grids disabled if OpenGL is disabled
QStandardItemModel *model = qobject_cast(ui->gridTypeCombobox->model());
QStandardItem *item = model->item(1); // isometric option
// item->setFlags(m_isIsometricGridEnabled ? item->flags() & ~Qt::ItemIsEnabled:
// item->flags() | Qt::ItemIsEnabled);
item->setEnabled(m_isIsometricGridEnabled);
if (m_isIsometricGridEnabled) {
item->setText(i18n("Isometric"));
} else {
item->setText(i18n("Isometric (requires OpenGL)"));
// change drop down index to Rectangular in case it was previously set to isometric
ui->gridTypeCombobox->setCurrentIndex(0);
}
}
void GridConfigWidget::setShowRulers(bool value)
{
ui->chkShowRulers->setChecked(value);
}