Commit de5852d8 authored by Halla Rempt's avatar Halla Rempt

BUG:370566 Save a copy of the current image

This gets rid of both the delayed save dialog in KisMainWindow
(which offered a cancel button that didn't work to cancel the
runnin strokes), and the locking in KisDocument (which dead-locked
Krita and caused dataloss).

Instead, a shallow clone of the image is created that can be safely
used to save. The strokes will run on the original image, which means
that the result of still-running strokes is not saved. The user is
warned about that. (That means a new message has been added.)
parent ca63c0aa
......@@ -67,6 +67,10 @@ public:
virtual ~KisAnnotation() {}
virtual KisAnnotation* clone() const {
return new KisAnnotation(*this);
}
/**
* gets a non-localized string identifying the type of the
* annotation.
......@@ -102,6 +106,15 @@ public:
return QString::fromUtf8(m_annotation);
}
protected:
KisAnnotation(const KisAnnotation &rhs)
: KisShared(),
m_type(rhs.m_type),
m_description(rhs.m_description),
m_annotation(rhs.m_annotation)
{
}
protected:
QString m_type;
......
......@@ -137,10 +137,43 @@ public:
, postExecutionUndoAdapter(u, _q)
, recorder(_q)
, signalRouter(_q)
, animationInterface(0)
, animationInterface(new KisImageAnimationInterface(q))
, scheduler(_q)
, axesCenter(QPointF(0.5, 0.5))
{}
{
{
KisImageConfig cfg;
if (cfg.enableProgressReporting()) {
scheduler.setProgressProxy(&compositeProgressProxy);
}
// Each of these lambdas defines a new factory function.
scheduler.setLod0ToNStrokeStrategyFactory(
[=](bool forgettable) {
return KisLodSyncPair(
new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable),
KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q)));
});
scheduler.setSuspendUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true),
KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q)));
});
scheduler.setResumeUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false),
KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q)));
});
}
connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged()));
}
KisImage *q;
......@@ -158,7 +191,6 @@ public:
KisSelectionSP deselectedGlobalSelection;
KisGroupLayerSP rootLayer; // The layers are contained in here
QList<KisLayer*> dirtyLayers; // for thumbnails
QList<KisLayerCompositionSP> compositions;
KisNodeSP isolatedRootNode;
bool wrapAroundModePermitted = false;
......@@ -210,41 +242,7 @@ KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const K
}
m_d = new KisImagePrivate(this, width, height, c, undoStore);
{
KisImageConfig cfg;
if (cfg.enableProgressReporting()) {
m_d->scheduler.setProgressProxy(&m_d->compositeProgressProxy);
}
// Each of these lambdas defines a new factory function.
m_d->scheduler.setLod0ToNStrokeStrategyFactory(
[=](bool forgettable) {
return KisLodSyncPair(
new KisSyncLodCacheStrokeStrategy(KisImageWSP(this), forgettable),
KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(this)));
});
m_d->scheduler.setSuspendUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(this), true),
KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(this)));
});
m_d->scheduler.setResumeUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(this), false),
KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(this)));
});
}
setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8));
m_d->animationInterface = new KisImageAnimationInterface(this);
connect(this, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged()));
}
KisImage::~KisImage()
......@@ -272,6 +270,63 @@ KisImage::~KisImage()
disconnect(); // in case Qt gets confused
}
KisImage *KisImage::clone(bool exactCopy)
{
return new KisImage(*this, 0, exactCopy);
}
KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy)
: KisNodeFacade(),
KisNodeGraphListener(),
KisShared(),
m_d(new KisImagePrivate(this,
rhs.width(), rhs.height(),
rhs.colorSpace(),
undoStore ? undoStore : new KisDumbUndoStore()))
{
setObjectName(rhs.objectName());
m_d->xres = rhs.m_d->xres;
m_d->yres = rhs.m_d->yres;
if (rhs.m_d->proofingConfig) {
m_d->proofingConfig = toQShared(new KisProofingConfiguration(*rhs.m_d->proofingConfig));
}
KisNodeSP newRoot = rhs.root()->clone();
newRoot->setGraphListener(this);
newRoot->setImage(this);
m_d->rootLayer = dynamic_cast<KisGroupLayer*>(newRoot.data());
setRoot(newRoot);
if (exactCopy) {
QQueue<KisNodeSP> linearizedNodes;
KisLayerUtils::recursiveApplyNodes(rhs.root(),
[&linearizedNodes](KisNodeSP node) {
linearizedNodes.enqueue(node);
});
KisLayerUtils::recursiveApplyNodes(newRoot,
[&linearizedNodes](KisNodeSP node) {
KisNodeSP refNode = linearizedNodes.dequeue();
node->setUuid(refNode->uuid());
});
}
Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) {
m_d->compositions << toQShared(new KisLayerComposition(*comp, this));
}
rhs.m_d->nserver = KisNameServer(rhs.m_d->nserver);
vKisAnnotationSP newAnnotations;
Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) {
newAnnotations << annotation->clone();
}
m_d->annotations = newAnnotations;
m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail;
}
void KisImage::aboutToAddANode(KisNode *parent, int index)
{
KisNodeGraphListener::aboutToAddANode(parent, index);
......@@ -1484,12 +1539,16 @@ void KisImage::requestProjectionUpdate(KisNode *node, const QRect& rect)
void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect)
{
m_d->animationInterface->invalidateFrames(range, rect);
if (m_d->animationInterface) {
m_d->animationInterface->invalidateFrames(range, rect);
}
}
void KisImage::requestTimeSwitch(int time)
{
m_d->animationInterface->requestTimeSwitchNonGUI(time);
if (m_d->animationInterface) {
m_d->animationInterface->requestTimeSwitchNonGUI(time);
}
}
QList<KisLayerCompositionSP> KisImage::compositions()
......
......@@ -101,6 +101,18 @@ public: // KisProjectionUpdateListener implementation
public:
/**
* Makes a copy of the image with all the layers. If possible, shallow
* copies of the layers are made.
*
* \p exactCopy shows if the copied image should look *exactly* the same as
* the other one (according to it's .kra xml representation). It means that
* the layers will have the same UUID keys and, therefore, you are not
* expected to use the copied image anywhere except for saving. Don't use
* this option if you plan to work with the copied image later.
*/
KisImage* clone(bool exactCopy = false);
/**
* Render the projection onto a QImage.
*/
......@@ -912,7 +924,7 @@ public Q_SLOTS:
private:
KisImage(const KisImage& rhs);
KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy);
KisImage& operator=(const KisImage& rhs);
void emitSizeChanged();
......
......@@ -29,6 +29,8 @@
#include "kis_transparency_mask.h"
#include "kis_selection_mask.h"
#include "lazybrush/kis_colorize_mask.h"
#include "kis_layer_utils.h"
#include "kis_node_query_path.h"
#include <QDomDocument>
......@@ -98,6 +100,42 @@ KisLayerComposition::~KisLayerComposition()
}
KisLayerComposition::KisLayerComposition(const KisLayerComposition &rhs, KisImageWSP otherImage)
: m_image(otherImage ? otherImage : rhs.m_image),
m_name(rhs.m_name),
m_exportEnabled(rhs.m_exportEnabled)
{
{
auto it = rhs.m_visibilityMap.constBegin();
for (; it != rhs.m_visibilityMap.constEnd(); ++it) {
QUuid nodeUuid = it.key();
KisNodeSP node = KisLayerUtils::findNodeByUuid(rhs.m_image->root(), nodeUuid);
if (node) {
KisNodeQueryPath path = KisNodeQueryPath::absolutePath(node);
KisNodeSP newNode = path.queryUniqueNode(m_image);
KIS_ASSERT_RECOVER(newNode) { continue; }
m_visibilityMap.insert(newNode->uuid(), it.value());
}
}
}
{
auto it = rhs.m_collapsedMap.constBegin();
for (; it != rhs.m_collapsedMap.constEnd(); ++it) {
QUuid nodeUuid = it.key();
KisNodeSP node = KisLayerUtils::findNodeByUuid(rhs.m_image->root(), nodeUuid);
if (node) {
KisNodeQueryPath path = KisNodeQueryPath::absolutePath(node);
KisNodeSP newNode = path.queryUniqueNode(m_image);
KIS_ASSERT_RECOVER(newNode) { continue; }
m_collapsedMap.insert(newNode->uuid(), it.value());
}
}
}
}
void KisLayerComposition::setName(const QString& name)
{
m_name = name;
......
......@@ -38,6 +38,8 @@ public:
KisLayerComposition(KisImageWSP image, const QString& name);
~KisLayerComposition();
KisLayerComposition(const KisLayerComposition &rhs, KisImageWSP otherImage = 0);
/**
* Sets name of the composition
*/
......
......@@ -20,6 +20,7 @@
#include <algorithm>
#include <QUuid>
#include <KoColorSpaceConstants.h>
#include "kis_painter.h"
......@@ -1263,4 +1264,13 @@ namespace KisLayerUtils {
return 0;
}
KisNodeSP findNodeByUuid(KisNodeSP root, const QUuid &uuid)
{
return recursiveFindNode(root,
[uuid] (KisNodeSP node) {
return node->uuid() == uuid;
});
}
}
......@@ -28,6 +28,7 @@
class KoProperties;
class KoColor;
class QUuid;
namespace KisMetaData
{
......@@ -193,6 +194,11 @@ namespace KisLayerUtils
* node is returned to the caller.
*/
KisNodeSP KRITAIMAGE_EXPORT recursiveFindNode(KisNodeSP node, std::function<bool(KisNodeSP)> func);
/**
* Recursively searches for a node with specified Uuid
*/
KisNodeSP KRITAIMAGE_EXPORT findNodeByUuid(KisNodeSP root, const QUuid &uuid);
};
#endif /* __KIS_LAYER_UTILS_H */
......@@ -33,7 +33,6 @@ public:
private:
qint32 m_generator;
QString m_prefix;
};
#endif // KIS_NAMESERVER_H_
......
......@@ -39,7 +39,6 @@ KisNodeFacade::KisNodeFacade(KisNodeSP root)
KisNodeFacade::~KisNodeFacade()
{
delete m_d;
}
void KisNodeFacade::setRoot(KisNodeSP root)
......
......@@ -18,6 +18,8 @@
#ifndef _KIS_NODE_FACADE_H
#define _KIS_NODE_FACADE_H
#include <QScopedPointer>
#include "kis_types.h"
#include "kis_node.h"
#include "kritaimage_export.h"
......@@ -135,6 +137,6 @@ public:
private:
struct Private;
Private * const m_d;
QScopedPointer<Private> m_d;
};
#endif
......@@ -37,7 +37,6 @@ KisNodeGraphListener::KisNodeGraphListener()
KisNodeGraphListener::~KisNodeGraphListener()
{
delete m_d;
}
void KisNodeGraphListener::aboutToAddANode(KisNode */*parent*/, int /*index*/)
......
......@@ -19,6 +19,9 @@
#define KIS_NODE_GRAPH_LISTENER_H_
#include "kritaimage_export.h"
#include <QScopedPointer>
class KisTimeRange;
class KisNode;
class QRect;
......@@ -114,7 +117,7 @@ public:
private:
struct Private;
Private * const m_d;
QScopedPointer<Private> m_d;
};
#endif
......@@ -142,6 +142,14 @@ QList<KisNodeSP> KisNodeQueryPath::queryNodes(KisImageWSP image, KisNodeSP curre
return result;
}
KisNodeSP KisNodeQueryPath::queryUniqueNode(KisImageWSP image, KisNodeSP currentNode) const
{
QList<KisNodeSP> result = queryNodes(image, currentNode);
KIS_ASSERT_RECOVER_NOOP(result.size() <= 1);
return !result.isEmpty() ? result.first() : 0;
}
QString KisNodeQueryPath::toString() const
{
QString str;
......
......@@ -33,6 +33,7 @@ public:
KisNodeQueryPath(const KisNodeQueryPath&);
KisNodeQueryPath& operator=(const KisNodeQueryPath&);
QList<KisNodeSP> queryNodes(KisImageWSP image, KisNodeSP currentNode) const;
KisNodeSP queryUniqueNode(KisImageWSP image, KisNodeSP currentNode = 0) const;
bool isRelative() const;
// Use "///" style because of the needed "/*"
/// This function return a string representing this path. Which is a list separated by '\' of:
......
......@@ -38,6 +38,8 @@
#include "kis_keyframe_channel.h"
#include "kis_selection_mask.h"
#include "kis_layer_utils.h"
#include "kis_annotation.h"
#include "KisProofingConfiguration.h"
#include "kis_undo_stores.h"
......@@ -239,15 +241,84 @@ void KisImageTest::testGlobalSelection()
QCOMPARE(image->root()->childCount(), 1U);
}
void KisImageTest::testCloneImage()
{
KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests");
QVERIFY(image->rootLayer() != 0);
QVERIFY(image->rootLayer()->firstChild() == 0);
KisAnnotationSP annotation = new KisAnnotation("mytype", "mydescription", QByteArray());
image->addAnnotation(annotation);
QVERIFY(image->annotation("mytype"));
KisProofingConfigurationSP proofing = toQShared(new KisProofingConfiguration());
image->setProofingConfiguration(proofing);
QVERIFY(image->proofingConfiguration());
const KoColor defaultColor(Qt::green, image->colorSpace());
image->setDefaultProjectionColor(defaultColor);
QCOMPARE(image->defaultProjectionColor(), defaultColor);
KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8);
image->addNode(layer);
KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8);
image->addNode(layer2);
QVERIFY(layer->visible());
QVERIFY(layer2->visible());
QVERIFY(TestUtil::findNode(image->root(), "layer1"));
QVERIFY(TestUtil::findNode(image->root(), "layer2"));
QUuid uuid1 = layer->uuid();
QUuid uuid2 = layer2->uuid();
{
KisImageSP newImage = image->clone();
KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1");
KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2");
QVERIFY(newLayer1);
QVERIFY(newLayer2);
QVERIFY(newLayer1->uuid() != uuid1);
QVERIFY(newLayer2->uuid() != uuid2);
KisAnnotationSP newAnnotation = newImage->annotation("mytype");
QVERIFY(newAnnotation);
QVERIFY(newAnnotation != annotation);
KisProofingConfigurationSP newProofing = newImage->proofingConfiguration();
QVERIFY(newProofing);
QVERIFY(newProofing != proofing);
QCOMPARE(newImage->defaultProjectionColor(), defaultColor);
}
{
KisImageSP newImage = image->clone(true);
KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1");
KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2");
QVERIFY(newLayer1);
QVERIFY(newLayer2);
QVERIFY(newLayer1->uuid() == uuid1);
QVERIFY(newLayer2->uuid() == uuid2);
}
}
void KisImageTest::testLayerComposition()
{
KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests");
QVERIFY(image->rootLayer() != 0);
QVERIFY(image->rootLayer()->firstChild() == 0);
KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8);
KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8);
image->addNode(layer);
KisLayerSP layer2 = new KisPaintLayer(image, "layer 2", OPACITY_OPAQUE_U8);
KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8);
image->addNode(layer2);
QVERIFY(layer->visible());
......@@ -264,6 +335,10 @@ void KisImageTest::testLayerComposition()
KisLayerComposition comp2(image, "comp 2");
comp2.store();
KisLayerCompositionSP comp3 = toQShared(new KisLayerComposition(image, "comp 3"));
comp3->store();
image->addComposition(comp3);
comp.apply();
QVERIFY(layer->visible());
......@@ -273,6 +348,42 @@ void KisImageTest::testLayerComposition()
QVERIFY(layer->visible());
QVERIFY(!layer2->visible());
comp.apply();
QVERIFY(layer->visible());
QVERIFY(layer2->visible());
KisImageSP newImage = image->clone();
KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1");
KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2");
QVERIFY(newLayer1);
QVERIFY(newLayer2);
QVERIFY(newLayer1->visible());
QVERIFY(newLayer2->visible());
KisLayerComposition newComp1(comp, newImage);
newComp1.apply();
QVERIFY(newLayer1->visible());
QVERIFY(newLayer2->visible());
KisLayerComposition newComp2(comp2, newImage);
newComp2.apply();
QVERIFY(newLayer1->visible());
QVERIFY(!newLayer2->visible());
newComp1.apply();
QVERIFY(newLayer1->visible());
QVERIFY(newLayer2->visible());
QVERIFY(!newImage->compositions().isEmpty());
KisLayerCompositionSP newComp3 = newImage->compositions().first();
newComp3->apply();
QVERIFY(newLayer1->visible());
QVERIFY(!newLayer2->visible());
}
#include "testutil.h"
......
......@@ -33,6 +33,7 @@ private Q_SLOTS:
void testBlockLevelOfDetail();
void testConvertImageColorSpace();
void testGlobalSelection();
void testCloneImage();
void testLayerComposition();
void testFlattenLayer();
......
......@@ -59,7 +59,6 @@ set(kritaui_LIB_SRCS
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/kis_dlg_internal_color_selector.cpp
flake/kis_node_dummies_graph.cpp
flake/kis_dummies_facade_base.cpp
......@@ -459,7 +458,6 @@ ki18n_wrap_ui(kritaui_LIB_SRCS
forms/wdgstopgradienteditor.ui
brushhud/kis_dlg_brush_hud_config.ui
forms/wdgdlginternalcolorselector.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
......
......@@ -360,6 +360,8 @@ public:
qint32 macroNestDepth;