File indexing completed on 2024-05-12 16:01:47

0001 /* This file is part of the Krita project
0002  *
0003  * SPDX-FileCopyrightText: 2014 Boudewijn Rempt <boud@valdyas.org>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "KisMainWindow.h" // XXX: remove
0009 #include <QMessageBox> // XXX: remove
0010 
0011 #include <KisMimeDatabase.h>
0012 
0013 #include <KoCanvasBase.h>
0014 #include <KoColor.h>
0015 #include <KoColorProfile.h>
0016 #include <KoColorSpaceEngine.h>
0017 #include <KoColorSpace.h>
0018 #include <KoColorSpaceRegistry.h>
0019 #include <KoDocumentInfoDlg.h>
0020 #include <KoDocumentInfo.h>
0021 #include <KoUnit.h>
0022 #include <KoID.h>
0023 #include <KoProgressProxy.h>
0024 #include <KoProgressUpdater.h>
0025 #include <KoSelection.h>
0026 #include <KoShape.h>
0027 #include <KoShapeController.h>
0028 #include <KoStore.h>
0029 #include <KoUpdater.h>
0030 #include <KoXmlWriter.h>
0031 #include <KoStoreDevice.h>
0032 #include <KoDialog.h>
0033 #include <KisImportExportErrorCode.h>
0034 #include <KoDocumentResourceManager.h>
0035 #include <KoMD5Generator.h>
0036 #include <KisResourceStorage.h>
0037 #include <KisResourceLocator.h>
0038 #include <KisResourceTypes.h>
0039 #include <KisGlobalResourcesInterface.h>
0040 #include <KisResourceLoaderRegistry.h>
0041 #include <KisResourceModelProvider.h>
0042 #include <KisResourceCacheDb.h>
0043 #include <KoEmbeddedResource.h>
0044 #include <KisUsageLogger.h>
0045 #include <klocalizedstring.h>
0046 #include "kis_scratch_pad.h"
0047 #include <kis_debug.h>
0048 #include <kis_generator_layer.h>
0049 #include <kis_generator_registry.h>
0050 #include <KisAutoSaveRecoveryDialog.h>
0051 #include <kdesktopfile.h>
0052 #include <kconfiggroup.h>
0053 #include <KisBackup.h>
0054 #include <KisView.h>
0055 
0056 #include <QTextBrowser>
0057 #include <QApplication>
0058 #include <QBuffer>
0059 #include <QStandardPaths>
0060 #include <QDir>
0061 #include <QDomDocument>
0062 #include <QDomElement>
0063 #include <QFileInfo>
0064 #include <QImage>
0065 #include <QList>
0066 #include <QPainter>
0067 #include <QRect>
0068 #include <QScopedPointer>
0069 #include <QSize>
0070 #include <QStringList>
0071 #include <QtGlobal>
0072 #include <QTimer>
0073 #include <QWidget>
0074 #include <QFuture>
0075 #include <QFutureWatcher>
0076 #include <QUuid>
0077 
0078 // Krita Image
0079 #include <kis_image_animation_interface.h>
0080 #include <kis_config.h>
0081 #include <flake/kis_shape_layer.h>
0082 #include <kis_group_layer.h>
0083 #include <kis_image.h>
0084 #include <kis_layer.h>
0085 #include <kis_name_server.h>
0086 #include <kis_paint_layer.h>
0087 #include <kis_painter.h>
0088 #include <kis_selection.h>
0089 #include <kis_fill_painter.h>
0090 #include <kis_document_undo_store.h>
0091 #include <kis_idle_watcher.h>
0092 #include <kis_signal_auto_connection.h>
0093 #include <kis_canvas_widget_base.h>
0094 #include "kis_layer_utils.h"
0095 #include "kis_selection_mask.h"
0096 
0097 // Local
0098 #include "KisViewManager.h"
0099 #include "kis_clipboard.h"
0100 #include "widgets/kis_custom_image_widget.h"
0101 #include "canvas/kis_canvas2.h"
0102 #include "flake/kis_shape_controller.h"
0103 #include "widgets/kis_progress_widget.h"
0104 #include "kis_canvas_resource_provider.h"
0105 #include "KisResourceServerProvider.h"
0106 #include "kis_node_manager.h"
0107 #include "KisPart.h"
0108 #include "KisApplication.h"
0109 #include "KisDocument.h"
0110 #include "KisImportExportManager.h"
0111 #include "KisView.h"
0112 #include "kis_grid_config.h"
0113 #include "kis_guides_config.h"
0114 #include "kis_image_barrier_lock_adapter.h"
0115 #include "KisReferenceImagesLayer.h"
0116 #include "dialogs/KisRecoverNamedAutosaveDialog.h"
0117 
0118 
0119 #include <mutex>
0120 #include "kis_config_notifier.h"
0121 #include "kis_async_action_feedback.h"
0122 #include "KisCloneDocumentStroke.h"
0123 
0124 #include <kis_algebra_2d.h>
0125 #include <KisMirrorAxisConfig.h>
0126 #include <KisDecorationsWrapperLayer.h>
0127 #include "kis_simple_stroke_strategy.h"
0128 
0129 // Define the protocol used here for embedded documents' URL
0130 // This used to "store" but QUrl didn't like it,
0131 // so let's simply make it "tar" !
0132 #define STORE_PROTOCOL "tar"
0133 // The internal path is a hack to make QUrl happy and for document children
0134 #define INTERNAL_PROTOCOL "intern"
0135 #define INTERNAL_PREFIX "intern:/"
0136 // Warning, keep it sync in koStore.cc
0137 
0138 #include <unistd.h>
0139 
0140 using namespace std;
0141 
0142 namespace {
0143 constexpr int errorMessageTimeout = 5000;
0144 constexpr int successMessageTimeout = 1000;
0145 }
0146 
0147 
0148 /**********************************************************
0149  *
0150  * KisDocument
0151  *
0152  **********************************************************/
0153 
0154 //static
0155 QString KisDocument::newObjectName()
0156 {
0157     static int s_docIFNumber = 0;
0158     QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
0159     return name;
0160 }
0161 
0162 
0163 class UndoStack : public KUndo2Stack
0164 {
0165 public:
0166     UndoStack(KisDocument *doc)
0167         : KUndo2Stack(doc),
0168           m_doc(doc)
0169     {
0170     }
0171 
0172     void setIndex(int idx) override {
0173         m_postponedJobs.append({PostponedJob::SetIndex, idx});
0174         processPostponedJobs();
0175     }
0176 
0177     void notifySetIndexChangedOneCommand() override {
0178         KisImageWSP image = this->image();
0179         image->unlock();
0180 
0181         /**
0182          * Some very weird commands may emit blocking signals to
0183          * the GUI (e.g. KisGuiContextCommand). Here is the best thing
0184          * we can do to avoid the deadlock
0185          */
0186         while(!image->tryBarrierLock()) {
0187             QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
0188         }
0189     }
0190 
0191     void undo() override {
0192         m_postponedJobs.append({PostponedJob::Undo, 0});
0193         processPostponedJobs();
0194     }
0195 
0196 
0197     void redo() override {
0198         m_postponedJobs.append({PostponedJob::Redo, 0});
0199         processPostponedJobs();
0200     }
0201 
0202 private:
0203     KisImageWSP image() {
0204         KisImageWSP currentImage = m_doc->image();
0205         Q_ASSERT(currentImage);
0206         return currentImage;
0207     }
0208 
0209     void setIndexImpl(int idx) {
0210         KisImageWSP image = this->image();
0211         image->requestStrokeCancellation();
0212         if(image->tryBarrierLock()) {
0213             KUndo2Stack::setIndex(idx);
0214             image->unlock();
0215         }
0216     }
0217 
0218     void undoImpl() {
0219         KisImageWSP image = this->image();
0220         image->requestUndoDuringStroke();
0221 
0222         if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) {
0223             return;
0224         }
0225 
0226         if(image->tryBarrierLock()) {
0227             KUndo2Stack::undo();
0228             image->unlock();
0229         }
0230     }
0231 
0232     void redoImpl() {
0233         KisImageWSP image = this->image();
0234         image->requestRedoDuringStroke();
0235 
0236         if(image->tryBarrierLock()) {
0237             KUndo2Stack::redo();
0238             image->unlock();
0239         }
0240     }
0241 
0242     void processPostponedJobs() {
0243         /**
0244          * Some undo commands may call QApplication::processEvents(),
0245          * see notifySetIndexChangedOneCommand(). That may cause
0246          * recursive calls to the undo stack methods when used from
0247          * the Undo History docker. Here we try to handle that gracefully
0248          * by accumulating all the requests and executing them at the
0249          * topmost level of recursion.
0250          */
0251         if (m_recursionCounter > 0) return;
0252 
0253         m_recursionCounter++;
0254 
0255         while (!m_postponedJobs.isEmpty()) {
0256             PostponedJob job = m_postponedJobs.dequeue();
0257             switch (job.type) {
0258             case PostponedJob::SetIndex:
0259                 setIndexImpl(job.index);
0260                 break;
0261             case PostponedJob::Redo:
0262                 redoImpl();
0263                 break;
0264             case PostponedJob::Undo:
0265                 undoImpl();
0266                 break;
0267             }
0268         }
0269 
0270         m_recursionCounter--;
0271     }
0272 
0273 private:
0274     int m_recursionCounter = 0;
0275 
0276     struct PostponedJob {
0277         enum Type {
0278             Undo = 0,
0279             Redo,
0280             SetIndex
0281         };
0282         Type type = Undo;
0283         int index = 0;
0284     };
0285     QQueue<PostponedJob> m_postponedJobs;
0286 
0287     KisDocument *m_doc;
0288 };
0289 
0290 class Q_DECL_HIDDEN KisDocument::Private
0291 {
0292 public:
0293     Private(KisDocument *_q)
0294         : q(_q)
0295         , docInfo(new KoDocumentInfo(_q)) // deleted by QObject
0296         , importExportManager(new KisImportExportManager(_q)) // deleted manually
0297         , autoSaveTimer(new QTimer(_q))
0298         , undoStack(new UndoStack(_q)) // deleted by QObject
0299         , m_bAutoDetectedMime(false)
0300         , modified(false)
0301         , readwrite(true)
0302         , firstMod(QDateTime::currentDateTime())
0303         , lastMod(firstMod)
0304         , nserver(new KisNameServer(1))
0305         , imageIdleWatcher(2000 /*ms*/)
0306         , globalAssistantsColor(KisConfig(true).defaultAssistantsColor())
0307         , batchMode(false)
0308     {
0309         if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
0310             unit = KoUnit::Inch;
0311         } else {
0312             unit = KoUnit::Centimeter;
0313         }
0314         connect(&imageIdleWatcher, SIGNAL(startedIdleMode()), q, SLOT(slotPerformIdleRoutines()));
0315     }
0316 
0317     Private(const Private &rhs, KisDocument *_q)
0318         : q(_q)
0319         , docInfo(new KoDocumentInfo(*rhs.docInfo, _q))
0320         , importExportManager(new KisImportExportManager(_q))
0321         , autoSaveTimer(new QTimer(_q))
0322         , undoStack(new UndoStack(_q))
0323         , nserver(new KisNameServer(*rhs.nserver))
0324         , preActivatedNode(0) // the node is from another hierarchy!
0325         , imageIdleWatcher(2000 /*ms*/)
0326     {
0327         copyFromImpl(rhs, _q, CONSTRUCT);
0328         connect(&imageIdleWatcher, SIGNAL(startedIdleMode()), q, SLOT(slotPerformIdleRoutines()));
0329     }
0330 
0331     ~Private() {
0332         // Don't delete m_d->shapeController because it's in a QObject hierarchy.
0333         delete nserver;
0334     }
0335 
0336     KisDocument *q = 0;
0337     KoDocumentInfo *docInfo = 0;
0338 
0339     KoUnit unit;
0340 
0341     KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options]
0342 
0343     QByteArray mimeType; // The actual mimeType of the document
0344     QByteArray outputMimeType; // The mimeType to use when saving
0345 
0346     QTimer *autoSaveTimer;
0347     QString lastErrorMessage; // see openFile()
0348     QString lastWarningMessage;
0349     int autoSaveDelay = 300; // in seconds, 0 to disable.
0350     bool modifiedAfterAutosave = false;
0351     bool isAutosaving = false;
0352     bool disregardAutosaveFailure = false;
0353     int autoSaveFailureCount = 0;
0354 
0355     KUndo2Stack *undoStack = 0;
0356 
0357     KisGuidesConfig guidesConfig;
0358     KisMirrorAxisConfig mirrorAxisConfig;
0359 
0360     bool m_bAutoDetectedMime = false; // whether the mimeType in the arguments was detected by the part itself
0361     QString m_path; // local url - the one displayed to the user.
0362     QString m_file; // Local file - the only one the part implementation should deal with.
0363 
0364     QMutex savingMutex;
0365 
0366     bool modified = false;
0367     bool readwrite = false;
0368 
0369     QDateTime firstMod;
0370     QDateTime lastMod;
0371 
0372     KisNameServer *nserver;
0373 
0374     KisImageSP image;
0375     KisImageSP savingImage;
0376 
0377     KisNodeWSP preActivatedNode;
0378     KisShapeController* shapeController = 0;
0379     KoShapeController* koShapeController = 0;
0380     KisIdleWatcher imageIdleWatcher;
0381     QScopedPointer<KisSignalAutoConnection> imageIdleConnection;
0382 
0383     QList<KisPaintingAssistantSP> assistants;
0384 
0385     StoryboardItemList m_storyboardItemList;
0386     QVector<StoryboardComment> m_storyboardCommentList;
0387 
0388     QColor globalAssistantsColor;
0389 
0390     KisGridConfig gridConfig;
0391 
0392     bool imageModifiedWithoutUndo = false;
0393     bool modifiedWhileSaving = false;
0394     QScopedPointer<KisDocument> backgroundSaveDocument;
0395     QPointer<KoUpdater> savingUpdater;
0396     QFuture<KisImportExportErrorCode> childSavingFuture;
0397     KritaUtils::ExportFileJob backgroundSaveJob;
0398     KisSignalAutoConnectionsStore referenceLayerConnections;
0399 
0400     bool isRecovered = false;
0401 
0402     bool batchMode { false };
0403     bool decorationsSyncingDisabled = false;
0404     bool wasStorageAdded = false;
0405 
0406 
0407     // Resources saved in the .kra document
0408     QString linkedResourcesStorageID;
0409     KisResourceStorageSP linkedResourceStorage;
0410 
0411     // Resources saved into other components of the kra file
0412     QString embeddedResourcesStorageID;
0413     KisResourceStorageSP embeddedResourceStorage;
0414 
0415     void syncDecorationsWrapperLayerState();
0416 
0417     void setImageAndInitIdleWatcher(KisImageSP _image) {
0418         image = _image;
0419 
0420         imageIdleWatcher.setTrackedImage(image);
0421     }
0422 
0423     void copyFrom(const Private &rhs, KisDocument *q);
0424     void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy);
0425 
0426     void uploadLinkedResourcesFromLayersToStorage();
0427     KisDocument* lockAndCloneImpl(bool fetchResourcesFromLayers);
0428 
0429     void updateDocumentMetadataOnSaving(const QString &filePath, const QByteArray &mimeType);
0430 
0431     /// clones the palette list oldList
0432     /// the ownership of the returned KoColorSet * belongs to the caller
0433     class StrippedSafeSavingLocker;
0434 };
0435 
0436 
0437 void KisDocument::Private::syncDecorationsWrapperLayerState()
0438 {
0439     if (!this->image || this->decorationsSyncingDisabled) return;
0440 
0441     KisImageSP image = this->image;
0442     KisDecorationsWrapperLayerSP decorationsLayer =
0443             KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(image->root());
0444 
0445     const bool needsDecorationsWrapper =
0446             gridConfig.showGrid() || (guidesConfig.showGuides() && guidesConfig.hasGuides()) || !assistants.isEmpty();
0447 
0448     struct SyncDecorationsWrapperStroke : public KisSimpleStrokeStrategy {
0449         SyncDecorationsWrapperStroke(KisDocument *document, bool needsDecorationsWrapper)
0450             : KisSimpleStrokeStrategy(QLatin1String("sync-decorations-wrapper"),
0451                                       kundo2_noi18n("start-isolated-mode")),
0452               m_document(document),
0453               m_needsDecorationsWrapper(needsDecorationsWrapper)
0454         {
0455             this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
0456             setClearsRedoOnStart(false);
0457             setRequestsOtherStrokesToEnd(false);
0458         }
0459 
0460         void initStrokeCallback() override {
0461             KisDecorationsWrapperLayerSP decorationsLayer =
0462                     KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(m_document->image()->root());
0463 
0464             if (m_needsDecorationsWrapper && !decorationsLayer) {
0465                 m_document->image()->addNode(new KisDecorationsWrapperLayer(m_document));
0466             } else if (!m_needsDecorationsWrapper && decorationsLayer) {
0467                 m_document->image()->removeNode(decorationsLayer);
0468             }
0469         }
0470 
0471     private:
0472         KisDocument *m_document = 0;
0473         bool m_needsDecorationsWrapper = false;
0474     };
0475 
0476     KisStrokeId id = image->startStroke(new SyncDecorationsWrapperStroke(q, needsDecorationsWrapper));
0477     image->endStroke(id);
0478 }
0479 
0480 void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q)
0481 {
0482     copyFromImpl(rhs, q, KisDocument::REPLACE);
0483 }
0484 
0485 void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy)
0486 {
0487     if (policy == REPLACE) {
0488         delete docInfo;
0489     }
0490     docInfo = (new KoDocumentInfo(*rhs.docInfo, q));
0491     unit = rhs.unit;
0492     mimeType = rhs.mimeType;
0493     outputMimeType = rhs.outputMimeType;
0494 
0495     if (policy == REPLACE) {
0496         q->setGuidesConfig(rhs.guidesConfig);
0497         q->setMirrorAxisConfig(rhs.mirrorAxisConfig);
0498         q->setModified(rhs.modified);
0499         q->setAssistants(KisPaintingAssistant::cloneAssistantList(rhs.assistants));
0500         q->setStoryboardItemList(StoryboardItem::cloneStoryboardItemList(rhs.m_storyboardItemList));
0501         q->setStoryboardCommentList(rhs.m_storyboardCommentList);
0502         q->setGridConfig(rhs.gridConfig);
0503     } else {
0504         // in CONSTRUCT mode, we cannot use the functions of KisDocument
0505         // because KisDocument does not yet have a pointer to us.
0506         guidesConfig = rhs.guidesConfig;
0507         mirrorAxisConfig = rhs.mirrorAxisConfig;
0508         modified = rhs.modified;
0509         assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants);
0510         m_storyboardItemList = StoryboardItem::cloneStoryboardItemList(rhs.m_storyboardItemList);
0511         m_storyboardCommentList = rhs.m_storyboardCommentList;
0512         gridConfig = rhs.gridConfig;
0513     }
0514     imageModifiedWithoutUndo = rhs.imageModifiedWithoutUndo;
0515     m_bAutoDetectedMime = rhs.m_bAutoDetectedMime;
0516     m_path = rhs.m_path;
0517     m_file = rhs.m_file;
0518     readwrite = rhs.readwrite;
0519     firstMod = rhs.firstMod;
0520     lastMod = rhs.lastMod;
0521     // XXX: the display properties will be shared between different snapshots
0522     globalAssistantsColor = rhs.globalAssistantsColor;
0523     batchMode = rhs.batchMode;
0524 
0525 
0526     if (rhs.linkedResourceStorage) {
0527         linkedResourceStorage = rhs.linkedResourceStorage->clone();
0528     }
0529 
0530     if (rhs.embeddedResourceStorage) {
0531         embeddedResourceStorage = rhs.embeddedResourceStorage->clone();
0532     }
0533 
0534 }
0535 
0536 class KisDocument::Private::StrippedSafeSavingLocker {
0537 public:
0538     StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
0539         : m_locked(false)
0540         , m_image(image)
0541         , m_savingLock(savingMutex)
0542         , m_imageLock(image, true)
0543 
0544     {
0545         /**
0546          * Initial try to lock both objects. Locking the image guards
0547          * us from any image composition threads running in the
0548          * background, while savingMutex guards us from entering the
0549          * saving code twice by autosave and main threads.
0550          *
0551          * Since we are trying to lock multiple objects, so we should
0552          * do it in a safe manner.
0553          */
0554         m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
0555 
0556         if (!m_locked) {
0557             m_image->requestStrokeEnd();
0558             QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
0559 
0560             // one more try...
0561             m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
0562         }
0563     }
0564 
0565     ~StrippedSafeSavingLocker() {
0566         if (m_locked) {
0567             m_imageLock.unlock();
0568             m_savingLock.unlock();
0569         }
0570     }
0571 
0572     bool successfullyLocked() const {
0573         return m_locked;
0574     }
0575 
0576 private:
0577     bool m_locked;
0578     KisImageSP m_image;
0579     StdLockableWrapper<QMutex> m_savingLock;
0580     KisImageBarrierLockAdapter m_imageLock;
0581 };
0582 
0583 KisDocument::KisDocument(bool addStorage)
0584     : d(new Private(this))
0585 {
0586     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
0587     connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
0588     connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
0589     setObjectName(newObjectName());
0590 
0591 
0592     if (addStorage) {
0593         d->linkedResourcesStorageID = QUuid::createUuid().toString();
0594         d->linkedResourceStorage.reset(new KisResourceStorage(d->linkedResourcesStorageID));
0595         KisResourceLocator::instance()->addStorage(d->linkedResourcesStorageID, d->linkedResourceStorage);
0596 
0597         d->embeddedResourcesStorageID = QUuid::createUuid().toString();
0598         d->embeddedResourceStorage.reset(new KisResourceStorage(d->embeddedResourcesStorageID));
0599         KisResourceLocator::instance()->addStorage(d->embeddedResourcesStorageID, d->embeddedResourceStorage);
0600 
0601         d->wasStorageAdded = true;
0602     }
0603 
0604     // preload the krita resources
0605     KisResourceServerProvider::instance();
0606 
0607     d->shapeController = new KisShapeController(d->nserver, d->undoStack, this);
0608     d->koShapeController = new KoShapeController(0, d->shapeController);
0609     d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController);
0610 
0611     slotConfigChanged();
0612 }
0613 
0614 KisDocument::KisDocument(const KisDocument &rhs, bool addStorage)
0615     : QObject(),
0616       d(new Private(*rhs.d, this))
0617 {
0618     copyFromDocumentImpl(rhs, CONSTRUCT);
0619 
0620     if (addStorage) {
0621         KisResourceLocator::instance()->addStorage(d->linkedResourcesStorageID, d->linkedResourceStorage);
0622         KisResourceLocator::instance()->addStorage(d->embeddedResourcesStorageID, d->embeddedResourceStorage);
0623         d->wasStorageAdded = true;
0624     }
0625 }
0626 
0627 KisDocument::~KisDocument()
0628 {
0629     // wait until all the pending operations are in progress
0630     waitForSavingToComplete();
0631     d->imageIdleWatcher.setTrackedImage(0);
0632 
0633     /**
0634      * Push a timebomb, which will try to release the memory after
0635      * the document has been deleted
0636      */
0637     KisPaintDevice::createMemoryReleaseObject()->deleteLater();
0638 
0639     d->autoSaveTimer->disconnect(this);
0640     d->autoSaveTimer->stop();
0641 
0642     delete d->importExportManager;
0643 
0644     // Despite being QObject they needs to be deleted before the image
0645     delete d->shapeController;
0646 
0647     delete d->koShapeController;
0648 
0649     if (d->image) {
0650         d->image->notifyAboutToBeDeleted();
0651 
0652         /**
0653          * WARNING: We should wait for all the internal image jobs to
0654          * finish before entering KisImage's destructor. The problem is,
0655          * while execution of KisImage::~KisImage, all the weak shared
0656          * pointers pointing to the image enter an inconsistent
0657          * state(!). The shared counter is already zero and destruction
0658          * has started, but the weak reference doesn't know about it,
0659          * because KisShared::~KisShared hasn't been executed yet. So all
0660          * the threads running in background and having weak pointers will
0661          * enter the KisImage's destructor as well.
0662          */
0663 
0664         d->image->requestStrokeCancellation();
0665         d->image->waitForDone();
0666 
0667         // clear undo commands that can still point to the image
0668         d->undoStack->clear();
0669         d->image->waitForDone();
0670 
0671         KisImageWSP sanityCheckPointer = d->image;
0672         Q_UNUSED(sanityCheckPointer);
0673 
0674         // The following line trigger the deletion of the image
0675         d->image.clear();
0676 
0677         // check if the image has actually been deleted
0678         KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
0679     }
0680     if (d->wasStorageAdded) {
0681         if (KisResourceLocator::instance()->hasStorage(d->linkedResourcesStorageID)) {
0682             KisResourceLocator::instance()->removeStorage(d->linkedResourcesStorageID);
0683         }
0684         if (KisResourceLocator::instance()->hasStorage(d->embeddedResourcesStorageID)) {
0685             KisResourceLocator::instance()->removeStorage(d->embeddedResourcesStorageID);
0686         }
0687     }
0688 
0689     delete d;
0690 }
0691 
0692 QString KisDocument::embeddedResourcesStorageId() const
0693 {
0694     return d->embeddedResourcesStorageID;
0695 }
0696 
0697 QString KisDocument::linkedResourcesStorageId() const
0698 {
0699     return d->linkedResourcesStorageID;
0700 }
0701 
0702 KisDocument *KisDocument::clone(bool addStorage)
0703 {
0704     return new KisDocument(*this, addStorage);
0705 }
0706 
0707 bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting)
0708 {
0709     QFileInfo filePathInfo(job.filePath);
0710 
0711     if (filePathInfo.exists() && !filePathInfo.isWritable()) {
0712         slotCompleteSavingDocument(job, ImportExportCodes::NoAccessToWrite,
0713                                    i18n("%1 cannot be written to. Please save under a different name.", job.filePath),
0714                                    "");
0715         return false;
0716     }
0717 
0718     KisConfig cfg(true);
0719     if (cfg.backupFile() && filePathInfo.exists()) {
0720 
0721         QString backupDir;
0722 
0723         switch(cfg.readEntry<int>("backupfilelocation", 0)) {
0724         case 1:
0725             backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
0726             break;
0727         case 2:
0728             backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
0729             break;
0730         default:
0731 #ifdef Q_OS_ANDROID
0732             // We deal with URIs, there may or may not be a "directory"
0733             backupDir = KisAutoSaveRecoveryDialog::autoSaveLocation();
0734             QDir().mkpath(backupDir);
0735 #endif
0736 
0737             // Do nothing: the empty string is user file location
0738             break;
0739         }
0740 
0741         int numOfBackupsKept = cfg.readEntry<int>("numberofbackupfiles", 1);
0742         QString suffix = cfg.readEntry<QString>("backupfilesuffix", "~");
0743 
0744         if (numOfBackupsKept == 1) {
0745             if (!KisBackup::simpleBackupFile(job.filePath, backupDir, suffix)) {
0746                 qWarning() << "Failed to create simple backup file!" << job.filePath << backupDir << suffix;
0747                 KisUsageLogger::log(QString("Failed to create a simple backup for %1 in %2.")
0748                                         .arg(job.filePath, backupDir.isEmpty()
0749                                                                ? "the same location as the file"
0750                                                                : backupDir));
0751                 return false;
0752             }
0753             else {
0754                 KisUsageLogger::log(QString("Create a simple backup for %1 in %2.")
0755                                         .arg(job.filePath, backupDir.isEmpty()
0756                                                                ? "the same location as the file"
0757                                                                : backupDir));
0758             }
0759         }
0760         else if (numOfBackupsKept > 1) {
0761             if (!KisBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept)) {
0762                 qWarning() << "Failed to create numbered backup file!" << job.filePath << backupDir << suffix;
0763                 KisUsageLogger::log(QString("Failed to create a numbered backup for %2.")
0764                                         .arg(job.filePath, backupDir.isEmpty()
0765                                                                ? "the same location as the file"
0766                                                                : backupDir));
0767                 return false;
0768             }
0769             else {
0770                 KisUsageLogger::log(QString("Create a simple backup for %1 in %2.")
0771                                         .arg(job.filePath, backupDir.isEmpty()
0772                                                                ? "the same location as the file"
0773                                                                : backupDir));
0774             }
0775         }
0776     }
0777 
0778     //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
0779     if (job.mimeType.isEmpty()) {
0780         KisImportExportErrorCode error = ImportExportCodes::FileFormatNotSupported;
0781         slotCompleteSavingDocument(job, error, error.errorMessage(), "");
0782         return false;
0783 
0784     }
0785 
0786     const QString actionName =
0787             job.flags & KritaUtils::SaveIsExporting ?
0788                 i18n("Exporting Document...") :
0789                 i18n("Saving Document...");
0790 
0791     KritaUtils::JobResult result =
0792             initiateSavingInBackground(actionName,
0793                                        this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString, QString)),
0794                                        job, exportConfiguration, isAdvancedExporting);
0795 
0796     if (result == KritaUtils::JobResult::Busy) {
0797         KisUsageLogger::log(QString("Failed to initiate saving %1 in background.").arg(job.filePath));
0798         slotCompleteSavingDocument(job, ImportExportCodes::Busy,
0799                                    i18n("Could not start saving %1. Wait until the current save operation has finished.", job.filePath),
0800                                    "");
0801         return false;
0802     }
0803 
0804     return (result == KritaUtils::JobResult::Success);
0805 }
0806 
0807 bool KisDocument::exportDocument(const QString &path, const QByteArray &mimeType, bool isAdvancedExporting, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
0808 {
0809     using namespace KritaUtils;
0810 
0811     SaveFlags flags = SaveIsExporting;
0812     if (showWarnings) {
0813         flags |= SaveShowWarnings;
0814     }
0815 
0816     KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 "
0817                                 "framerate. Export configuration: %8")
0818                             .arg(path, QString::fromLatin1(mimeType), QString::number(d->image->width()),
0819                                  QString::number(d->image->height()), QString::number(d->image->nlayers()),
0820                                  QString::number(d->image->animationInterface()->totalLength()),
0821                                  QString::number(d->image->animationInterface()->framerate()),
0822                                  (exportConfiguration ? exportConfiguration->toXML() : "No configuration")));
0823 
0824     return exportDocumentImpl(KritaUtils::ExportFileJob(path,
0825                                                         mimeType,
0826                                                         flags),
0827                               exportConfiguration, isAdvancedExporting);
0828 }
0829 
0830 bool KisDocument::saveAs(const QString &_path, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
0831 {
0832     using namespace KritaUtils;
0833 
0834     KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers.  %6 frames, "
0835                                 "%7 framerate. Export configuration: %8")
0836                             .arg(_path, QString::fromLatin1(mimeType), QString::number(d->image->width()),
0837                                  QString::number(d->image->height()), QString::number(d->image->nlayers()),
0838                                  QString::number(d->image->animationInterface()->totalLength()),
0839                                  QString::number(d->image->animationInterface()->framerate()),
0840                                  (exportConfiguration ? exportConfiguration->toXML() : "No configuration"),
0841                                  path()));
0842 
0843     // Check whether it's an existing resource were are saving to
0844     if (resourceSavingFilter(_path, mimeType, exportConfiguration)) {
0845         return true;
0846     }
0847 
0848     return exportDocumentImpl(ExportFileJob(_path,
0849                                             mimeType,
0850                                             showWarnings ? SaveShowWarnings : SaveNone),
0851                               exportConfiguration);
0852 }
0853 
0854 bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
0855 {
0856     return saveAs(path(), mimeType(), showWarnings, exportConfiguration);
0857 }
0858 
0859 QByteArray KisDocument::serializeToNativeByteArray()
0860 {
0861     QBuffer buffer;
0862 
0863     QScopedPointer<KisImportExportFilter> filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export));
0864     filter->setBatchMode(true);
0865     filter->setMimeType(nativeFormatMimeType());
0866 
0867     Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
0868     if (!locker.successfullyLocked()) {
0869         return buffer.data();
0870     }
0871 
0872     d->savingImage = d->image;
0873 
0874     if (!filter->convert(this, &buffer).isOk()) {
0875         qWarning() << "serializeToByteArray():: Could not export to our native format";
0876     }
0877 
0878     return buffer.data();
0879 }
0880 
0881 class DlgLoadMessages : public KoDialog
0882 {
0883 public:
0884     DlgLoadMessages(const QString &title,
0885                     const QString &message,
0886                     const QStringList &warnings)
0887         : KoDialog(qApp->activeWindow())
0888     {
0889         setWindowTitle(title);
0890         QWidget *page = new QWidget();
0891         QVBoxLayout *layout = new QVBoxLayout(page);
0892         QHBoxLayout *hlayout = new QHBoxLayout();
0893         QLabel *labelWarning= new QLabel();
0894         labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32));
0895         labelWarning->setAlignment(Qt::AlignTop);
0896         hlayout->addWidget(labelWarning);
0897         hlayout->addWidget(new QLabel(message), 1);
0898         layout->addLayout(hlayout);
0899         if (!warnings.isEmpty()) {
0900             QTextBrowser *browser = new QTextBrowser();
0901             QString warning = "<html><body><ul>";
0902             Q_FOREACH (const QString &w, warnings) {
0903                 warning += "\n<li>" + w + "</li>";
0904             }
0905             warning += "</ul>";
0906             browser->setHtml(warning);
0907             browser->setMinimumHeight(200);
0908             browser->setMinimumWidth(400);
0909             layout->addWidget(browser);
0910         }
0911         setMainWidget(page);
0912         page->setParent(this);
0913         setButtons(KoDialog::Ok);
0914     }
0915 };
0916 
0917 void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
0918 {
0919     if (status.isCancelled())
0920         return;
0921 
0922     const QString fileName = QFileInfo(job.filePath).fileName();
0923 
0924     if (!status.isOk()) {
0925         emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
0926                                     "Error during saving %1: %2",
0927                                     fileName,
0928                                     errorMessage), errorMessageTimeout);
0929 
0930         if (!fileBatchMode()) {
0931             DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
0932                                 i18n("Could not save %1.\nReason: %2", job.filePath, status.errorMessage()),
0933                                 errorMessage.split("\n") + warningMessage.split("\n"));
0934             dlg.exec();
0935         }
0936     }
0937     else {
0938         if (!fileBatchMode() && !warningMessage.isEmpty()) {
0939             DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
0940                                 i18nc("dialog box shown to the user if there were warnings while saving the document, %1 is the file path",
0941                                       "%1 has been saved but is incomplete.\nThe following problems were encountered when saving:", job.filePath),
0942                                 warningMessage.split("\n"));
0943             dlg.exec();
0944         }
0945 
0946 
0947         if (!(job.flags & KritaUtils::SaveIsExporting)) {
0948             const QString existingAutoSaveBaseName = localFilePath();
0949             const bool wasRecovered = isRecovered();
0950 
0951             d->updateDocumentMetadataOnSaving(job.filePath, job.mimeType);
0952 
0953             removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered);
0954         }
0955 
0956         emit completed();
0957         emit sigSavingFinished(job.filePath);
0958 
0959         emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
0960     }
0961 }
0962 
0963 void KisDocument::Private::updateDocumentMetadataOnSaving(const QString &filePath, const QByteArray &mimeType)
0964 {
0965     q->setPath(filePath);
0966     q->setLocalFilePath(filePath);
0967     q->setMimeType(mimeType);
0968     q->updateEditingTime(true);
0969 
0970     if (!modifiedWhileSaving) {
0971         /**
0972          * If undo stack is already clean/empty, it doesn't emit any
0973          * signals, so we might forget update document modified state
0974          * (which was set, e.g. while recovering an autosave file)
0975          */
0976 
0977         if (undoStack->isClean()) {
0978             q->setModified(false);
0979         } else {
0980             imageModifiedWithoutUndo = false;
0981             undoStack->setClean();
0982         }
0983     }
0984     q->setRecovered(false);
0985 }
0986 
0987 QByteArray KisDocument::mimeType() const
0988 {
0989     return d->mimeType;
0990 }
0991 
0992 void KisDocument::setMimeType(const QByteArray & mimeType)
0993 {
0994     d->mimeType = mimeType;
0995 }
0996 
0997 bool KisDocument::fileBatchMode() const
0998 {
0999     return d->batchMode;
1000 }
1001 
1002 void KisDocument::setFileBatchMode(const bool batchMode)
1003 {
1004     d->batchMode = batchMode;
1005 }
1006 
1007 void KisDocument::Private::uploadLinkedResourcesFromLayersToStorage()
1008 {
1009     /// Fetch resources from KisAdjustmentLayer, KisFilterMask and
1010     /// KisGeneratorLayer and put them into the cloned storage. This must be
1011     /// done in the context of the GUI thread, otherwise we will not be able to
1012     /// access resources database
1013 
1014     KisDocument *doc = q;
1015 
1016     KisLayerUtils::recursiveApplyNodes(doc->image()->root(),
1017         [doc] (KisNodeSP node) {
1018             if (KisNodeFilterInterface *layer = dynamic_cast<KisNodeFilterInterface*>(node.data())) {
1019                 KisFilterConfigurationSP filterConfig = layer->filter();
1020                 if (!filterConfig) return;
1021 
1022                 QList<KoResourceLoadResult> linkedResources = filterConfig->linkedResources(KisGlobalResourcesInterface::instance());
1023 
1024                 Q_FOREACH (const KoResourceLoadResult &result, linkedResources) {
1025                     KIS_SAFE_ASSERT_RECOVER(result.type() != KoResourceLoadResult::EmbeddedResource) { continue; }
1026 
1027                     KoResourceSP resource = result.resource();
1028 
1029                     if (!resource) {
1030                         qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to fetch a resource" << result.signature();
1031                         continue;
1032                     }
1033 
1034                     QBuffer buf;
1035                     buf.open(QBuffer::WriteOnly);
1036 
1037                     KisResourceModel model(resource->resourceType().first);
1038                     bool res = model.exportResource(resource, &buf);
1039 
1040                     buf.close();
1041 
1042                     if (!res) {
1043                         qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to export resource" << result.signature();
1044                         continue;
1045                     }
1046 
1047                     buf.open(QBuffer::ReadOnly);
1048 
1049                     res = doc->d->linkedResourceStorage->importResource(resource->resourceType().first + "/" + resource->filename(), &buf);
1050 
1051                     buf.close();
1052 
1053                     if (!res) {
1054                         qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to import resource" << result.signature();
1055                         continue;
1056                     }
1057                 }
1058 
1059             }
1060     });
1061 }
1062 
1063 KisDocument *KisDocument::Private::lockAndCloneImpl(bool fetchResourcesFromLayers)
1064 {
1065     // force update of all the asynchronous nodes before cloning
1066     QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1067     KisLayerUtils::forceAllDelayedNodesUpdate(image->root());
1068 
1069     KisMainWindow *window = KisPart::instance()->currentMainwindow();
1070     if (window) {
1071         if (window->viewManager()) {
1072             if (!window->viewManager()->blockUntilOperationsFinished(image)) {
1073                 return 0;
1074             }
1075         }
1076     }
1077 
1078     Private::StrippedSafeSavingLocker locker(&savingMutex, image);
1079     if (!locker.successfullyLocked()) {
1080         return 0;
1081     }
1082 
1083     KisDocument *doc = new KisDocument(*this->q, false);
1084 
1085     if (fetchResourcesFromLayers) {
1086         doc->d->uploadLinkedResourcesFromLayersToStorage();
1087     }
1088 
1089     return doc;
1090 }
1091 
1092 KisDocument* KisDocument::lockAndCloneForSaving()
1093 {
1094     return d->lockAndCloneImpl(true);
1095 }
1096 
1097 KisDocument *KisDocument::lockAndCreateSnapshot()
1098 {
1099     return d->lockAndCloneImpl(false);
1100 }
1101 
1102 void KisDocument::copyFromDocument(const KisDocument &rhs)
1103 {
1104     copyFromDocumentImpl(rhs, REPLACE);
1105 }
1106 
1107 void KisDocument::copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy)
1108 {
1109     if (policy == REPLACE) {
1110         d->decorationsSyncingDisabled = true;
1111         d->copyFrom(*(rhs.d), this);
1112         d->decorationsSyncingDisabled = false;
1113 
1114         d->undoStack->clear();
1115     } else {
1116         // in CONSTRUCT mode, d should be already initialized
1117         connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
1118         connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
1119         connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
1120 
1121         d->shapeController = new KisShapeController(d->nserver, d->undoStack, this);
1122         d->koShapeController = new KoShapeController(0, d->shapeController);
1123         d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController);
1124     }
1125 
1126     setObjectName(rhs.objectName());
1127 
1128     slotConfigChanged();
1129 
1130     if (rhs.d->image) {
1131         if (policy == REPLACE) {
1132             d->image->barrierLock(/* readOnly = */ false);
1133             rhs.d->image->barrierLock(/* readOnly = */ true);
1134             d->image->copyFromImage(*(rhs.d->image));
1135             d->image->unlock();
1136             rhs.d->image->unlock();
1137 
1138             setCurrentImage(d->image, /* forceInitialUpdate = */ true);
1139         } else {
1140             // clone the image with keeping the GUIDs of the layers intact
1141             // NOTE: we expect the image to be locked!
1142             setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false);
1143         }
1144     }
1145 
1146     if (policy == REPLACE) {
1147         d->syncDecorationsWrapperLayerState();
1148     }
1149 
1150     if (rhs.d->preActivatedNode) {
1151         QQueue<KisNodeSP> linearizedNodes;
1152         KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(),
1153                                            [&linearizedNodes](KisNodeSP node) {
1154             linearizedNodes.enqueue(node);
1155         });
1156         KisLayerUtils::recursiveApplyNodes(d->image->root(),
1157                                            [&linearizedNodes, &rhs, this](KisNodeSP node) {
1158             KisNodeSP refNode = linearizedNodes.dequeue();
1159             if (rhs.d->preActivatedNode.data() == refNode.data()) {
1160                 d->preActivatedNode = node;
1161             }
1162         });
1163     }
1164 
1165     // reinitialize references' signal connection
1166     KisReferenceImagesLayerSP referencesLayer = this->referenceImagesLayer();
1167     if (referencesLayer) {
1168         d->referenceLayerConnections.clear();
1169         d->referenceLayerConnections.addConnection(
1170                     referencesLayer, SIGNAL(sigUpdateCanvas(QRectF)),
1171                     this, SIGNAL(sigReferenceImagesChanged()));
1172 
1173         emit sigReferenceImagesLayerChanged(referencesLayer);
1174     }
1175 
1176     KisDecorationsWrapperLayerSP decorationsLayer =
1177             KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(d->image->root());
1178     if (decorationsLayer) {
1179         decorationsLayer->setDocument(this);
1180     }
1181 
1182 
1183     if (policy == REPLACE) {
1184         setModified(true);
1185     }
1186 }
1187 
1188 bool KisDocument::exportDocumentSync(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
1189 {
1190     {
1191         /**
1192          * The caller guarantees that no one else uses the document (usually,
1193          * it is a temporary document created specifically for exporting), so
1194          * we don't need to copy or lock the document. Instead we should just
1195          * ensure the barrier lock is synced and then released.
1196          */
1197         Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
1198         if (!locker.successfullyLocked()) {
1199             return false;
1200         }
1201     }
1202 
1203     d->savingImage = d->image;
1204 
1205     KisImportExportErrorCode status =
1206             d->importExportManager->
1207             exportDocument(path, path, mimeType, false, exportConfiguration);
1208 
1209     d->savingImage = 0;
1210 
1211     return status.isOk();
1212 }
1213 
1214 
1215 KritaUtils::JobResult KisDocument::initiateSavingInBackground(const QString actionName,
1216                                              const QObject *receiverObject, const char *receiverMethod,
1217                                              const KritaUtils::ExportFileJob &job,
1218                                              KisPropertiesConfigurationSP exportConfiguration,bool isAdvancedExporting)
1219 {
1220     return initiateSavingInBackground(actionName, receiverObject, receiverMethod,
1221                                       job, exportConfiguration, std::unique_ptr<KisDocument>(), isAdvancedExporting);
1222 }
1223 
1224 KritaUtils::JobResult KisDocument::initiateSavingInBackground(const QString actionName,
1225                                              const QObject *receiverObject, const char *receiverMethod,
1226                                              const KritaUtils::ExportFileJob &job,
1227                                              KisPropertiesConfigurationSP exportConfiguration,
1228                                              std::unique_ptr<KisDocument> &&optionalClonedDocument,bool isAdvancedExporting)
1229 {
1230     KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), KritaUtils::JobResult::Failure);
1231 
1232     QScopedPointer<KisDocument> clonedDocument;
1233 
1234     if (!optionalClonedDocument) {
1235         clonedDocument.reset(lockAndCloneForSaving());
1236     } else {
1237         clonedDocument.reset(optionalClonedDocument.release());
1238     }
1239 
1240     if (!d->savingMutex.tryLock()){
1241         return KritaUtils::JobResult::Busy;
1242     }
1243 
1244     if (!clonedDocument) {
1245         return KritaUtils::JobResult::Failure;
1246     }
1247 
1248     auto waitForImage = [] (KisImageSP image) {
1249         KisMainWindow *window = KisPart::instance()->currentMainwindow();
1250         if (window) {
1251             if (window->viewManager()) {
1252                 window->viewManager()->blockUntilOperationsFinishedForced(image);
1253             }
1254         }
1255     };
1256 
1257     {
1258         KisNodeSP newRoot = clonedDocument->image()->root();
1259         KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) {
1260             KisLayerUtils::forceAllDelayedNodesUpdate(newRoot);
1261             waitForImage(clonedDocument->image());
1262         }
1263     }
1264 
1265     if (clonedDocument->image()->hasOverlaySelectionMask()) {
1266         clonedDocument->image()->setOverlaySelectionMask(0);
1267         waitForImage(clonedDocument->image());
1268     }
1269 
1270     KisConfig cfg(true);
1271     if (cfg.trimKra()) {
1272         clonedDocument->image()->cropImage(clonedDocument->image()->bounds());
1273         clonedDocument->image()->purgeUnusedData(false);
1274         waitForImage(clonedDocument->image());
1275     }
1276 
1277     KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) {
1278         waitForImage(clonedDocument->image());
1279     }
1280 
1281     KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, KritaUtils::JobResult::Failure);
1282     KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), KritaUtils::JobResult::Failure);
1283     d->backgroundSaveDocument.reset(clonedDocument.take());
1284     d->backgroundSaveJob = job;
1285     d->modifiedWhileSaving = false;
1286 
1287     if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
1288         d->backgroundSaveDocument->d->isAutosaving = true;
1289     }
1290 
1291     connect(d->backgroundSaveDocument.data(),
1292             SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString, QString)),
1293             this,
1294             SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString, QString)));
1295 
1296 
1297     connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString, QString)),
1298             receiverObject, receiverMethod, Qt::UniqueConnection);
1299 
1300     bool started =
1301             d->backgroundSaveDocument->startExportInBackground(actionName,
1302                                                                job.filePath,
1303                                                                job.filePath,
1304                                                                job.mimeType,
1305                                                                job.flags & KritaUtils::SaveShowWarnings,
1306                                                                exportConfiguration, isAdvancedExporting);
1307 
1308     if (!started) {
1309         // the state should have been deinitialized in slotChildCompletedSavingInBackground()
1310         KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) {
1311             d->backgroundSaveDocument.take()->deleteLater();
1312             d->savingMutex.unlock();
1313             d->backgroundSaveJob = KritaUtils::ExportFileJob();
1314         }
1315         return KritaUtils::JobResult::Failure;
1316     }
1317 
1318     return KritaUtils::JobResult::Success;
1319 }
1320 
1321 
1322 void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
1323 {
1324     KIS_ASSERT_RECOVER_RETURN(isSaving());
1325 
1326     KIS_ASSERT_RECOVER(d->backgroundSaveDocument) {
1327         d->savingMutex.unlock();
1328         return;
1329     }
1330 
1331     if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
1332         d->backgroundSaveDocument->d->isAutosaving = false;
1333     }
1334 
1335     d->backgroundSaveDocument.take()->deleteLater();
1336 
1337     KIS_ASSERT_RECOVER(d->backgroundSaveJob.isValid()) {
1338         d->savingMutex.unlock();
1339         return;
1340     }
1341 
1342     const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
1343     d->backgroundSaveJob = KritaUtils::ExportFileJob();
1344 
1345     // unlock at the very end
1346     d->savingMutex.unlock();
1347 
1348     QFileInfo fi(job.filePath);
1349     KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3. Warning: %4. Size: %5")
1350                             .arg(job.filePath, QString::fromLatin1(job.mimeType),
1351                                  (!status.isOk() ? errorMessage : "OK"), warningMessage,
1352                                  QString::number(fi.size())));
1353 
1354     emit sigCompleteBackgroundSaving(job, status, errorMessage, warningMessage);
1355 }
1356 
1357 void KisDocument::slotAutoSaveImpl(std::unique_ptr<KisDocument> &&optionalClonedDocument)
1358 {
1359     if (!d->modified || !d->modifiedAfterAutosave) return;
1360     const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
1361 
1362     emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout);
1363 
1364     KisUsageLogger::log(QString("Autosaving: %1").arg(autoSaveFileName));
1365 
1366     const bool hadClonedDocument = bool(optionalClonedDocument);
1367     KritaUtils::JobResult result = KritaUtils::JobResult::Failure;
1368 
1369     if (d->image->isIdle() || hadClonedDocument) {
1370         result = initiateSavingInBackground(i18n("Autosaving..."),
1371                                              this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString, QString)),
1372                                              KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode),
1373                                              0,
1374                                              std::move(optionalClonedDocument));
1375     } else {
1376         emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout);
1377     }
1378 
1379     if (result != KritaUtils::JobResult::Success && !hadClonedDocument && d->autoSaveFailureCount >= 3) {
1380         KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this);
1381         connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)),
1382                 this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)),
1383                 Qt::BlockingQueuedConnection);
1384         connect(stroke, SIGNAL(sigCloningCancelled()),
1385                 this, SLOT(slotDocumentCloningCancelled()),
1386                 Qt::BlockingQueuedConnection);
1387 
1388         KisStrokeId strokeId = d->image->startStroke(stroke);
1389         d->image->endStroke(strokeId);
1390 
1391         setInfiniteAutoSaveInterval();
1392 
1393     } else if (result != KritaUtils::JobResult::Success) {
1394         setEmergencyAutoSaveInterval();
1395     } else {
1396         d->modifiedAfterAutosave = false;
1397     }
1398 }
1399 
1400 bool KisDocument::resourceSavingFilter(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
1401 {
1402     if (QFileInfo(path).absolutePath().startsWith(KisResourceLocator::instance()->resourceLocationBase())) {
1403 
1404         QStringList pathParts = QFileInfo(path).absolutePath().split('/');
1405         if (pathParts.size() > 0) {
1406             QString resourceType = pathParts.last();
1407             if (KisResourceLoaderRegistry::instance()->resourceTypes().contains(resourceType)) {
1408 
1409                 KisResourceModel model(resourceType);
1410                 model.setResourceFilter(KisResourceModel::ShowAllResources);
1411 
1412                 QString tempFileName = QDir::tempPath() + "/" + QFileInfo(path).fileName();
1413 
1414                 if (QFileInfo(path).exists()) {
1415 
1416                     int outResourceId;
1417                     KoResourceSP res;
1418                     if (KisResourceCacheDb::getResourceIdFromVersionedFilename(QFileInfo(path).fileName(), resourceType, "", outResourceId)) {
1419                         res = model.resourceForId(outResourceId);
1420                     }
1421 
1422                     if (res) {
1423                         d->modifiedWhileSaving = false;
1424 
1425                         if (!exportConfiguration) {
1426                             QScopedPointer<KisImportExportFilter> filter(
1427                                 KisImportExportManager::filterForMimeType(mimeType, KisImportExportManager::Export));
1428                             if (filter) {
1429                                 exportConfiguration = filter->defaultConfiguration(nativeFormatMimeType(), mimeType);
1430                             }
1431                         }
1432 
1433                         if (exportConfiguration) {
1434                             // make sure the the name of the resource doesn't change
1435                             exportConfiguration->setProperty("name", res->name());
1436                         }
1437 
1438                         if (exportDocumentSync(tempFileName, mimeType, exportConfiguration)) {
1439                             QFile f2(tempFileName);
1440                             f2.open(QFile::ReadOnly);
1441 
1442                             QByteArray ba = f2.readAll();
1443 
1444                             QBuffer buf(&ba);
1445                             buf.open(QBuffer::ReadOnly);
1446 
1447 
1448 
1449                             if (res->loadFromDevice(&buf, KisGlobalResourcesInterface::instance())) {
1450                                 if (model.updateResource(res)) {
1451                                     const QString filePath =
1452                                         KisResourceLocator::instance()->filePathForResource(res);
1453 
1454                                     d->updateDocumentMetadataOnSaving(filePath, mimeType);
1455 
1456                                     return true;
1457                                 }
1458                             }
1459                         }
1460                     }
1461                 }
1462                 else {
1463                     d->modifiedWhileSaving = false;
1464                     if (exportDocumentSync(tempFileName, mimeType, exportConfiguration)) {
1465                         KoResourceSP res = model.importResourceFile(tempFileName, false);
1466                         if (res) {
1467                             const QString filePath =
1468                                 KisResourceLocator::instance()->filePathForResource(res);
1469 
1470                             d->updateDocumentMetadataOnSaving(filePath, mimeType);
1471 
1472                             return true;
1473                         }
1474                     }
1475                 }
1476             }
1477         }
1478     }
1479     return false;
1480 }
1481 
1482 void KisDocument::slotAutoSave()
1483 {
1484     slotAutoSaveImpl(std::unique_ptr<KisDocument>());
1485 }
1486 
1487 void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument)
1488 {
1489     slotAutoSaveImpl(std::unique_ptr<KisDocument>(clonedDocument));
1490 }
1491 
1492 void KisDocument::slotDocumentCloningCancelled()
1493 {
1494     setEmergencyAutoSaveInterval();
1495 }
1496 
1497 void KisDocument::slotPerformIdleRoutines()
1498 {
1499     d->image->explicitRegenerateLevelOfDetail();
1500 
1501 
1502     /// TODO: automatic purging is disabled for now: it modifies
1503     ///       data managers without creating a transaction, which breaks
1504     ///       undo.
1505 
1506     // d->image->purgeUnusedData(true);
1507 }
1508 
1509 void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
1510 {
1511     Q_UNUSED(job);
1512     Q_UNUSED(warningMessage);
1513 
1514     const QString fileName = QFileInfo(job.filePath).fileName();
1515 
1516     if (!status.isOk()) {
1517         setEmergencyAutoSaveInterval();
1518         emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
1519                                     "Error during autosaving %1: %2",
1520                                     fileName,
1521                                     exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
1522     } else {
1523         KisConfig cfg(true);
1524         d->autoSaveDelay = cfg.autoSaveInterval();
1525 
1526         if (!d->modifiedWhileSaving) {
1527             d->autoSaveTimer->stop(); // until the next change
1528             d->autoSaveFailureCount = 0;
1529         } else {
1530             setNormalAutoSaveInterval();
1531         }
1532 
1533         emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout);
1534     }
1535 }
1536 
1537 bool KisDocument::startExportInBackground(const QString &actionName,
1538                                           const QString &location,
1539                                           const QString &realLocation,
1540                                           const QByteArray &mimeType,
1541                                           bool showWarnings,
1542                                           KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting)
1543 {
1544     d->savingImage = d->image;
1545 
1546     KisMainWindow *window = KisPart::instance()->currentMainwindow();
1547     if (window) {
1548         if (window->viewManager()) {
1549             d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName);
1550             d->importExportManager->setUpdater(d->savingUpdater);
1551         }
1552     }
1553 
1554     KisImportExportErrorCode initializationStatus(ImportExportCodes::OK);
1555     d->childSavingFuture =
1556             d->importExportManager->exportDocumentAsyc(location,
1557                                                        realLocation,
1558                                                        mimeType,
1559                                                        initializationStatus,
1560                                                        showWarnings,
1561                                                        exportConfiguration,
1562                                                        isAdvancedExporting);
1563 
1564     if (!initializationStatus.isOk()) {
1565         if (d->savingUpdater) {
1566             d->savingUpdater->cancel();
1567         }
1568         d->savingImage.clear();
1569         emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage(), "");
1570         return false;
1571     }
1572 
1573     typedef QFutureWatcher<KisImportExportErrorCode> StatusWatcher;
1574     StatusWatcher *watcher = new StatusWatcher();
1575     watcher->setFuture(d->childSavingFuture);
1576 
1577     connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground()));
1578     connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
1579 
1580     return true;
1581 }
1582 
1583 void KisDocument::finishExportInBackground()
1584 {
1585     KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) {
1586         emit sigBackgroundSavingFinished(ImportExportCodes::InternalError, "", "");
1587         return;
1588     }
1589 
1590     KisImportExportErrorCode status = d->childSavingFuture.result();
1591     QString errorMessage = status.errorMessage();
1592     QString warningMessage = d->lastWarningMessage;
1593 
1594     if (!d->lastErrorMessage.isEmpty()) {
1595         if (status == ImportExportCodes::InternalError || status == ImportExportCodes::Failure) {
1596             errorMessage = d->lastErrorMessage;
1597         } else {
1598             errorMessage += "\n" + d->lastErrorMessage;
1599         }
1600     }
1601 
1602     d->savingImage.clear();
1603     d->childSavingFuture = QFuture<KisImportExportErrorCode>();
1604     d->lastErrorMessage.clear();
1605     d->lastWarningMessage.clear();
1606 
1607     if (d->savingUpdater) {
1608         d->savingUpdater->setProgress(100);
1609     }
1610 
1611     emit sigBackgroundSavingFinished(status, errorMessage, warningMessage);
1612 }
1613 
1614 void KisDocument::setReadWrite(bool readwrite)
1615 {
1616     d->readwrite = readwrite;
1617     setNormalAutoSaveInterval();
1618 
1619     Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) {
1620         mainWindow->setReadWrite(readwrite);
1621     }
1622 }
1623 
1624 void KisDocument::setAutoSaveDelay(int delay)
1625 {
1626     if (isReadWrite() && delay > 0) {
1627         d->autoSaveTimer->start(delay * 1000);
1628     } else {
1629         d->autoSaveTimer->stop();
1630     }
1631 }
1632 
1633 void KisDocument::setNormalAutoSaveInterval()
1634 {
1635     setAutoSaveDelay(d->autoSaveDelay);
1636     d->autoSaveFailureCount = 0;
1637 }
1638 
1639 void KisDocument::setEmergencyAutoSaveInterval()
1640 {
1641     const int emergencyAutoSaveInterval = 10; /* sec */
1642     setAutoSaveDelay(emergencyAutoSaveInterval);
1643     d->autoSaveFailureCount++;
1644 }
1645 
1646 void KisDocument::setInfiniteAutoSaveInterval()
1647 {
1648     setAutoSaveDelay(-1);
1649 }
1650 
1651 KoDocumentInfo *KisDocument::documentInfo() const
1652 {
1653     return d->docInfo;
1654 }
1655 
1656 bool KisDocument::isModified() const
1657 {
1658     return d->modified;
1659 }
1660 
1661 QPixmap KisDocument::generatePreview(const QSize& size)
1662 {
1663     KisImageSP image = d->image;
1664     if (d->savingImage) image = d->savingImage;
1665 
1666     if (image) {
1667         QRect bounds = image->bounds();
1668         QSize originalSize = bounds.size();
1669         // QSize may round down one dimension to zero on extreme aspect rations, so ensure 1px minimum
1670         QSize newSize = originalSize.scaled(size, Qt::KeepAspectRatio).expandedTo({1, 1});
1671 
1672         bool pixelArt = false;
1673         // determine if the image is pixel art or not
1674         if (originalSize.width() < size.width() && originalSize.height() < size.height()) {
1675             // the image must be smaller than the requested preview
1676             // the scale must be integer
1677             if (newSize.height()%originalSize.height() == 0 && newSize.width()%originalSize.width() == 0) {
1678                 pixelArt = true;
1679             }
1680         }
1681 
1682         QPixmap px;
1683         if (pixelArt) {
1684             // do not scale while converting (because it uses Bicubic)
1685             QImage original = image->convertToQImage(originalSize, 0);
1686             // scale using FastTransformation, which is probably Nearest neighbour, suitable for pixel art
1687             QImage scaled = original.scaled(newSize, Qt::KeepAspectRatio, Qt::FastTransformation);
1688             px = QPixmap::fromImage(scaled);
1689         } else {
1690             px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
1691         }
1692         if (px.size() == QSize(0,0)) {
1693             px = QPixmap(newSize);
1694             QPainter gc(&px);
1695             QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
1696             gc.fillRect(px.rect(), checkBrush);
1697             gc.end();
1698         }
1699         return px;
1700     }
1701     return QPixmap(size);
1702 }
1703 
1704 QString KisDocument::generateAutoSaveFileName(const QString & path) const
1705 {
1706     QString retval;
1707 
1708     // Using the extension allows to avoid relying on the mime magic when opening
1709     const QString extension (".kra");
1710     QString prefix = KisConfig(true).readEntry<bool>("autosavefileshidden") ? QString(".") : QString();
1711     QRegularExpression autosavePattern1("^\\..+-autosave.kra$");
1712     QRegularExpression autosavePattern2("^.+-autosave.kra$");
1713 
1714     QFileInfo fi(path);
1715     QString dir = fi.absolutePath();
1716 
1717 #ifdef Q_OS_ANDROID
1718     // URIs may or may not have a directory backing them, so we save to our default autosave location
1719     if (path.startsWith("content://")) {
1720         dir = KisAutoSaveRecoveryDialog::autoSaveLocation();
1721         QDir().mkpath(dir);
1722     }
1723 #endif
1724 
1725     QString filename = fi.fileName();
1726 
1727     if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) {
1728         // Never saved?
1729         retval = QString("%1%2%3%4-%5-%6-autosave%7")
1730                      .arg(KisAutoSaveRecoveryDialog::autoSaveLocation())
1731                      .arg('/')
1732                      .arg(prefix)
1733                      .arg("krita")
1734                      .arg(qApp->applicationPid())
1735                      .arg(objectName())
1736                      .arg(extension);
1737     } else {
1738         // Beware: don't reorder arguments
1739         //   otherwise in case of filename = '1-file.kra' it will become '.-file.kra-autosave.kra' instead of '.1-file.kra-autosave.kra'
1740         retval = QString("%1%2%3%4-autosave%5").arg(dir).arg('/').arg(prefix).arg(filename).arg(extension);
1741     }
1742 
1743     //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
1744     return retval;
1745 }
1746 
1747 bool KisDocument::importDocument(const QString &_path)
1748 {
1749     bool ret;
1750 
1751     dbgUI << "path=" << _path;
1752 
1753     // open...
1754     ret = openPath(_path);
1755 
1756     // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
1757     // File --> Import
1758     if (ret) {
1759         dbgUI << "success, resetting url";
1760         resetPath();
1761         setTitleModified();
1762     }
1763 
1764     return ret;
1765 }
1766 
1767 
1768 bool KisDocument::openPath(const QString &_path, OpenFlags flags)
1769 {
1770     dbgUI << "path=" << _path;
1771     d->lastErrorMessage.clear();
1772 
1773     // Reimplemented, to add a check for autosave files and to improve error reporting
1774     if (_path.isEmpty()) {
1775         d->lastErrorMessage = i18n("Malformed Path\n%1", _path);  // ## used anywhere ?
1776         return false;
1777     }
1778 
1779     QString path = _path;
1780     QString original  = "";
1781     bool autosaveOpened = false;
1782     if (!fileBatchMode()) {
1783         QString file = path;
1784         QString asf = generateAutoSaveFileName(file);
1785         if (QFile::exists(asf)) {
1786             KisApplication *kisApp = static_cast<KisApplication*>(qApp);
1787             kisApp->hideSplashScreen();
1788             //qDebug() <<"asf=" << asf;
1789             // ## TODO compare timestamps ?
1790             KisRecoverNamedAutosaveDialog dlg(0, file, asf);
1791             dlg.exec();
1792             int res = dlg.result();
1793 
1794             switch (res) {
1795             case KisRecoverNamedAutosaveDialog::OpenAutosave :
1796                 original = file;
1797                 path = asf;
1798                 autosaveOpened = true;
1799                 break;
1800             case KisRecoverNamedAutosaveDialog::OpenMainFile :
1801                 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
1802                 QFile::remove(asf);
1803                 break;
1804             default: // Cancel
1805                 return false;
1806             }
1807         }
1808     }
1809 
1810     bool ret = openPathInternal(path);
1811 
1812     if (autosaveOpened || flags & RecoveryFile) {
1813         setReadWrite(true); // enable save button
1814         setModified(true);
1815         setRecovered(true);
1816 
1817         setPath(original); // since it was an autosave, it will be a local file
1818         setLocalFilePath(original);
1819     }
1820     else {
1821         if (ret) {
1822 
1823             if (!(flags & DontAddToRecent)) {
1824                 KisPart::instance()->addRecentURLToAllMainWindows(QUrl::fromLocalFile(_path));
1825             }
1826 
1827             QFileInfo fi(_path);
1828             setReadWrite(fi.isWritable());
1829         }
1830 
1831         setRecovered(false);
1832     }
1833 
1834     return ret;
1835 }
1836 
1837 bool KisDocument::openFile()
1838 {
1839     //dbgUI <<"for" << localFilePath();
1840     if (!QFile::exists(localFilePath()) && !fileBatchMode()) {
1841         QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
1842         return false;
1843     }
1844 
1845     QString filename = localFilePath();
1846     QString typeName = mimeType();
1847 
1848     if (typeName.isEmpty()) {
1849         typeName = KisMimeDatabase::mimeTypeForFile(filename);
1850     }
1851 
1852     // Allow to open backup files, don't keep the mimeType application/x-trash.
1853     if (typeName == "application/x-trash") {
1854         QString path = filename;
1855         while (path.length() > 0) {
1856             path.chop(1);
1857             typeName = KisMimeDatabase::mimeTypeForFile(path);
1858             //qDebug() << "\t" << path << typeName;
1859             if (!typeName.isEmpty()) {
1860                 break;
1861             }
1862         }
1863         //qDebug() << "chopped" << filename  << "to" << path << "Was trash, is" << typeName;
1864     }
1865     dbgUI << localFilePath() << "type:" << typeName;
1866 
1867     KisMainWindow *window = KisPart::instance()->currentMainwindow();
1868     KoUpdaterPtr updater;
1869     if (window && window->viewManager()) {
1870         updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
1871         d->importExportManager->setUpdater(updater);
1872     }
1873 
1874     KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName);
1875 
1876     if (!status.isOk()) {
1877         if (window && window->viewManager()) {
1878             updater->cancel();
1879         }
1880         QString msg = status.errorMessage();
1881         KisUsageLogger::log(QString("Loading %1 failed: %2").arg(prettyPath(), msg));
1882 
1883         if (!msg.isEmpty() && !fileBatchMode()) {
1884             DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
1885                                 i18n("Could not open %2.\nReason: %1", msg, prettyPath()),
1886                                 errorMessage().split("\n") + warningMessage().split("\n"));
1887             dlg.exec();
1888         }
1889         return false;
1890     }
1891     else if (!warningMessage().isEmpty() && !fileBatchMode()) {
1892         DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
1893                             i18n("There were problems opening %1", prettyPath()),
1894                             warningMessage().split("\n"));
1895         dlg.exec();
1896         setPath(QString());
1897     }
1898 
1899     setMimeTypeAfterLoading(typeName);
1900     d->syncDecorationsWrapperLayerState();
1901     emit sigLoadingFinished();
1902 
1903     undoStack()->clear();
1904 
1905     return true;
1906 }
1907 
1908 void KisDocument::autoSaveOnPause()
1909 {
1910     if (!d->modified || !d->modifiedAfterAutosave)
1911         return;
1912 
1913     const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
1914 
1915     bool started = exportDocumentSync(autoSaveFileName, nativeFormatMimeType());
1916 
1917     if (started)
1918     {
1919         d->modifiedAfterAutosave = false;
1920         dbgAndroid << "autoSaveOnPause successful";
1921     }
1922     else
1923     {
1924         qWarning() << "Could not auto-save when paused";
1925     }
1926 }
1927 
1928 // shared between openFile and koMainWindow's "create new empty document" code
1929 void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
1930 {
1931     d->mimeType = mimeType.toLatin1();
1932     d->outputMimeType = d->mimeType;
1933 }
1934 
1935 
1936 bool KisDocument::loadNativeFormat(const QString & file_)
1937 {
1938     return openPath(file_);
1939 }
1940 
1941 void KisDocument::setModified(bool mod)
1942 {
1943     if (mod) {
1944         updateEditingTime(false);
1945     }
1946 
1947     if (d->isAutosaving)   // ignore setModified calls due to autosaving
1948         return;
1949 
1950     //dbgUI<<" url:" << url.path();
1951     //dbgUI<<" mod="<<mod<<" MParts mod="<<KisParts::ReadWritePart::isModified()<<" isModified="<<isModified();
1952 
1953     if (mod && !d->autoSaveTimer->isActive()) {
1954         // First change since last autosave -> start the autosave timer
1955         setNormalAutoSaveInterval();
1956     }
1957     d->modifiedAfterAutosave = mod;
1958     d->modifiedWhileSaving = mod;
1959 
1960     if (!mod) {
1961         d->imageModifiedWithoutUndo = mod;
1962     }
1963 
1964     if (mod == isModified())
1965         return;
1966 
1967     d->modified = mod;
1968 
1969     if (mod) {
1970         documentInfo()->updateParameters();
1971     }
1972 
1973     // This influences the title
1974     setTitleModified();
1975     emit modified(mod);
1976 }
1977 
1978 void KisDocument::setRecovered(bool value)
1979 {
1980     d->isRecovered = value;
1981 }
1982 
1983 bool KisDocument::isRecovered() const
1984 {
1985     return d->isRecovered;
1986 }
1987 
1988 void KisDocument::updateEditingTime(bool forceStoreElapsed)
1989 {
1990     QDateTime now = QDateTime::currentDateTime();
1991     int firstModDelta = d->firstMod.secsTo(now);
1992     int lastModDelta = d->lastMod.secsTo(now);
1993 
1994     if (lastModDelta > 30) {
1995         d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
1996         d->firstMod = now;
1997     } else if (firstModDelta > 60 || forceStoreElapsed) {
1998         d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
1999         d->firstMod = now;
2000     }
2001 
2002     d->lastMod = now;
2003 }
2004 
2005 QString KisDocument::prettyPath() const
2006 {
2007     QString _url(path());
2008 #ifdef Q_OS_WIN
2009     _url = QDir::toNativeSeparators(_url);
2010 #endif
2011     return _url;
2012 }
2013 
2014 // Get caption from document info (title(), in about page)
2015 QString KisDocument::caption() const
2016 {
2017     QString c;
2018     const QString _url(QFileInfo(path()).fileName());
2019 
2020     // if URL is empty...it is probably an unsaved file
2021     if (_url.isEmpty()) {
2022         c = " [" + i18n("Not Saved") + "] ";
2023     } else {
2024         c = _url; // Fall back to document URL
2025     }
2026 
2027     return c;
2028 }
2029 
2030 void KisDocument::setTitleModified()
2031 {
2032     emit titleModified(caption(), isModified());
2033 }
2034 
2035 QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
2036 {
2037     return createDomDocument("krita", tagName, version);
2038 }
2039 
2040 //static
2041 QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
2042 {
2043     QDomImplementation impl;
2044     QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
2045     QDomDocumentType dtype = impl.createDocumentType(tagName,
2046                                                      QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
2047                                                      url);
2048     // The namespace URN doesn't need to include the version number.
2049     QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
2050     QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
2051     doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
2052     return doc;
2053 }
2054 
2055 bool KisDocument::isNativeFormat(const QByteArray& mimeType) const
2056 {
2057     if (mimeType == nativeFormatMimeType())
2058         return true;
2059     return extraNativeMimeTypes().contains(mimeType);
2060 }
2061 
2062 void KisDocument::setErrorMessage(const QString& errMsg)
2063 {
2064     d->lastErrorMessage = errMsg;
2065 }
2066 
2067 QString KisDocument::errorMessage() const
2068 {
2069     return d->lastErrorMessage;
2070 }
2071 
2072 void KisDocument::setWarningMessage(const QString& warningMsg)
2073 {
2074     d->lastWarningMessage = warningMsg;
2075 }
2076 
2077 QString KisDocument::warningMessage() const
2078 {
2079     return d->lastWarningMessage;
2080 }
2081 
2082 
2083 void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered)
2084 {
2085     // Eliminate any auto-save file
2086     QString asf = generateAutoSaveFileName(autosaveBaseName);   // the one in the current dir
2087     if (QFile::exists(asf)) {
2088         KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
2089         QFile::remove(asf);
2090     }
2091     asf = generateAutoSaveFileName(QString());   // and the one in $HOME
2092 
2093     if (QFile::exists(asf)) {
2094         KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
2095         QFile::remove(asf);
2096     }
2097 
2098     QList<QRegularExpression> expressions;
2099 
2100     expressions << QRegularExpression("^\\..+-autosave.kra$")
2101                 << QRegularExpression("^.+-autosave.kra$");
2102 
2103     Q_FOREACH(const QRegularExpression &rex, expressions) {
2104         if (wasRecovered &&
2105                 !autosaveBaseName.isEmpty() &&
2106                 rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() &&
2107                 QFile::exists(autosaveBaseName)) {
2108 
2109             KisUsageLogger::log(QString("Removing autosave file: %1").arg(autosaveBaseName));
2110             QFile::remove(autosaveBaseName);
2111         }
2112     }
2113 }
2114 
2115 KoUnit KisDocument::unit() const
2116 {
2117     return d->unit;
2118 }
2119 
2120 void KisDocument::setUnit(const KoUnit &unit)
2121 {
2122     if (d->unit != unit) {
2123         d->unit = unit;
2124         emit unitChanged(unit);
2125     }
2126 }
2127 
2128 KUndo2Stack *KisDocument::undoStack()
2129 {
2130     return d->undoStack;
2131 }
2132 
2133 KisImportExportManager *KisDocument::importExportManager() const
2134 {
2135     return d->importExportManager;
2136 }
2137 
2138 void KisDocument::slotUndoStackCleanChanged(bool value)
2139 {
2140     setModified(!value || d->imageModifiedWithoutUndo);
2141 }
2142 
2143 void KisDocument::slotConfigChanged()
2144 {
2145     KisConfig cfg(true);
2146 
2147     if (d->undoStack->undoLimit() != cfg.undoStackLimit()) {
2148         if (!d->undoStack->isClean()) {
2149             d->undoStack->clear();
2150             // we set this because the document *has* changed, even though the
2151             // undo history was purged.
2152             setImageModifiedWithoutUndo();
2153         }
2154         d->undoStack->setUndoLimit(cfg.undoStackLimit());
2155     }
2156 
2157     d->autoSaveDelay = cfg.autoSaveInterval();
2158     setNormalAutoSaveInterval();
2159 }
2160 
2161 void KisDocument::slotImageRootChanged()
2162 {
2163     d->syncDecorationsWrapperLayerState();
2164 }
2165 
2166 void KisDocument::clearUndoHistory()
2167 {
2168     d->undoStack->clear();
2169 }
2170 
2171 KisGridConfig KisDocument::gridConfig() const
2172 {
2173     return d->gridConfig;
2174 }
2175 
2176 void KisDocument::setGridConfig(const KisGridConfig &config)
2177 {
2178     if (d->gridConfig != config) {
2179         d->gridConfig = config;
2180         d->syncDecorationsWrapperLayerState();
2181         emit sigGridConfigChanged(config);
2182     }
2183 }
2184 
2185 QList<KoResourceLoadResult> KisDocument::linkedDocumentResources()
2186 {
2187     QList<KoResourceLoadResult> result;
2188     if (!d->linkedResourceStorage) {
2189         return result;
2190     }
2191 
2192     Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) {
2193         QSharedPointer<KisResourceStorage::ResourceIterator> iter = d->linkedResourceStorage->resources(resourceType);
2194         while (iter->hasNext()) {
2195             iter->next();
2196 
2197             QBuffer buf;
2198             buf.open(QBuffer::WriteOnly);
2199             bool exportSuccessfull =
2200                 d->linkedResourceStorage->exportResource(iter->url(), &buf);
2201 
2202             KoResourceSP resource = d->linkedResourceStorage->resource(iter->url());
2203             exportSuccessfull &= bool(resource);
2204 
2205             const QString name = resource ? resource->name() : QString();
2206             const QString fileName = QFileInfo(iter->url()).fileName();
2207             const KoResourceSignature signature(resourceType,
2208                                                 KoMD5Generator::generateHash(buf.data()),
2209                                                 fileName, name);
2210 
2211             if (exportSuccessfull) {
2212                 result << KoEmbeddedResource(signature, buf.data());
2213             } else {
2214                 result << signature;
2215             }
2216         }
2217     }
2218 
2219     return result;
2220 }
2221 
2222 void KisDocument::setPaletteList(const QList<KoColorSetSP > &paletteList, bool emitSignal)
2223 {
2224     QList<KoColorSetSP> oldPaletteList;
2225     if (d->linkedResourceStorage) {
2226         QSharedPointer<KisResourceStorage::ResourceIterator> iter = d->linkedResourceStorage->resources(ResourceType::Palettes);
2227         while (iter->hasNext()) {
2228             iter->next();
2229             KoResourceSP resource = iter->resource();
2230             if (resource && resource->valid()) {
2231                 oldPaletteList << resource.dynamicCast<KoColorSet>();
2232             }
2233         }
2234         if (oldPaletteList != paletteList) {
2235             KisResourceModel resourceModel(ResourceType::Palettes);
2236             Q_FOREACH(KoColorSetSP palette, oldPaletteList) {
2237                 if (!paletteList.contains(palette)) {
2238                     resourceModel.setResourceInactive(resourceModel.indexForResource(palette));
2239                 }
2240             }
2241             Q_FOREACH(KoColorSetSP palette, paletteList) {
2242                 if (!oldPaletteList.contains(palette)) {
2243                     resourceModel.addResource(palette, d->linkedResourcesStorageID);
2244                 }
2245                 else {
2246                     palette->setStorageLocation(d->linkedResourcesStorageID);
2247                     resourceModel.updateResource(palette);
2248                 }
2249             }
2250             if (emitSignal) {
2251                 emit sigPaletteListChanged(oldPaletteList, paletteList);
2252             }
2253         }
2254     }
2255 }
2256 
2257 StoryboardItemList KisDocument::getStoryboardItemList()
2258 {
2259     return d->m_storyboardItemList;
2260 }
2261 
2262 void KisDocument::setStoryboardItemList(const StoryboardItemList &storyboardItemList, bool emitSignal)
2263 {
2264     d->m_storyboardItemList = storyboardItemList;
2265     if (emitSignal) {
2266         emit sigStoryboardItemListChanged();
2267     }
2268 }
2269 
2270 QVector<StoryboardComment> KisDocument::getStoryboardCommentsList()
2271 {
2272     return d->m_storyboardCommentList;
2273 }
2274 
2275 void KisDocument::setStoryboardCommentList(const QVector<StoryboardComment> &storyboardCommentList, bool emitSignal)
2276 {
2277     d->m_storyboardCommentList = storyboardCommentList;
2278     if (emitSignal) {
2279         emit sigStoryboardCommentListChanged();
2280     }
2281 }
2282 
2283 const KisGuidesConfig& KisDocument::guidesConfig() const
2284 {
2285     return d->guidesConfig;
2286 }
2287 
2288 void KisDocument::setGuidesConfig(const KisGuidesConfig &data)
2289 {
2290     if (d->guidesConfig == data) return;
2291 
2292     d->guidesConfig = data;
2293     d->syncDecorationsWrapperLayerState();
2294     emit sigGuidesConfigChanged(d->guidesConfig);
2295 }
2296 
2297 
2298 const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const
2299 {
2300     return d->mirrorAxisConfig;
2301 }
2302 
2303 void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config)
2304 {
2305     if (d->mirrorAxisConfig == config) {
2306         return;
2307     }
2308 
2309     d->mirrorAxisConfig = config;
2310     if (d->image) {
2311         d->image->setMirrorAxesCenter(KisAlgebra2D::absoluteToRelative(d->mirrorAxisConfig.axisPosition(),
2312                                                                        d->image->bounds()));
2313     }
2314     setModified(true);
2315 
2316     emit sigMirrorAxisConfigChanged();
2317 }
2318 
2319 void KisDocument::resetPath() {
2320     setPath(QString());
2321     setLocalFilePath(QString());
2322 }
2323 
2324 KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
2325 {
2326     return new KoDocumentInfoDlg(parent, docInfo);
2327 }
2328 
2329 bool KisDocument::isReadWrite() const
2330 {
2331     return d->readwrite;
2332 }
2333 
2334 QString KisDocument::path() const
2335 {
2336     return d->m_path;
2337 }
2338 
2339 bool KisDocument::closePath(bool promptToSave)
2340 {
2341     if (promptToSave) {
2342         if ( isReadWrite() && isModified()) {
2343             Q_FOREACH (KisView *view, KisPart::instance()->views()) {
2344                 if (view && view->document() == this) {
2345                     if (!view->queryClose()) {
2346                         return false;
2347                     }
2348                 }
2349             }
2350         }
2351     }
2352     // Not modified => ok and delete temp file.
2353     d->mimeType = QByteArray();
2354 
2355     // It always succeeds for a read-only part,
2356     // but the return value exists for reimplementations
2357     // (e.g. pressing cancel for a modified read-write part)
2358     return true;
2359 }
2360 
2361 
2362 
2363 void KisDocument::setPath(const QString &path)
2364 {
2365     d->m_path = path;
2366 }
2367 
2368 QString KisDocument::localFilePath() const
2369 {
2370     return d->m_file;
2371 }
2372 
2373 
2374 void KisDocument::setLocalFilePath( const QString &localFilePath )
2375 {
2376     d->m_file = localFilePath;
2377 }
2378 
2379 bool KisDocument::openPathInternal(const QString &path)
2380 {
2381     if ( path.isEmpty() ) {
2382         return false;
2383     }
2384 
2385     if (d->m_bAutoDetectedMime) {
2386         d->mimeType = QByteArray();
2387         d->m_bAutoDetectedMime = false;
2388     }
2389 
2390     QByteArray mimeType = d->mimeType;
2391 
2392     if ( !closePath() ) {
2393         return false;
2394     }
2395 
2396     d->mimeType = mimeType;
2397     setPath(path);
2398 
2399     d->m_file.clear();
2400 
2401     d->m_file = d->m_path;
2402 
2403     bool ret = false;
2404     // set the mimeType only if it was not already set (for example, by the host application)
2405     if (d->mimeType.isEmpty()) {
2406         // get the mimeType of the file
2407         // using findByUrl() to avoid another string -> url conversion
2408         QString mime = KisMimeDatabase::mimeTypeForFile(d->m_path);
2409         d->mimeType = mime.toLocal8Bit();
2410         d->m_bAutoDetectedMime = true;
2411     }
2412 
2413     setPath(d->m_path);
2414     ret = openFile();
2415 
2416     if (ret) {
2417         emit completed();
2418     }
2419     else {
2420         emit canceled(QString());
2421     }
2422     return ret;
2423 }
2424 
2425 bool KisDocument::newImage(const QString& name,
2426                            qint32 width, qint32 height,
2427                            const KoColorSpace* cs,
2428                            const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle,
2429                            int numberOfLayers,
2430                            const QString &description, const double imageResolution)
2431 {
2432     Q_ASSERT(cs);
2433 
2434     KisImageSP image;
2435 
2436     if (!cs) return false;
2437 
2438     QApplication::setOverrideCursor(Qt::BusyCursor);
2439 
2440     image = new KisImage(createUndoStore(), width, height, cs, name);
2441 
2442     Q_CHECK_PTR(image);
2443 
2444     connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
2445     connect(image, SIGNAL(sigImageModifiedWithoutUndo()), this, SLOT(setImageModifiedWithoutUndo()), Qt::UniqueConnection);
2446     image->setResolution(imageResolution, imageResolution);
2447 
2448     image->assignImageProfile(cs->profile());
2449     image->waitForDone();
2450 
2451     documentInfo()->setAboutInfo("title", name);
2452     documentInfo()->setAboutInfo("abstract", description);
2453 
2454     KisConfig cfg(false);
2455     cfg.defImageWidth(width);
2456     cfg.defImageHeight(height);
2457     cfg.defImageResolution(imageResolution);
2458     if (!cfg.useDefaultColorSpace())
2459     {
2460         cfg.defColorModel(image->colorSpace()->colorModelId().id());
2461         cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id());
2462         cfg.defColorProfile(image->colorSpace()->profile()->name());
2463     }
2464 
2465     bool autopin = cfg.autoPinLayersToTimeline();
2466 
2467     KisLayerSP bgLayer;
2468     if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) {
2469         KoColor strippedAlpha = bgColor;
2470         strippedAlpha.setOpacity(OPACITY_OPAQUE_U8);
2471 
2472         if (bgStyle == KisConfig::RASTER_LAYER) {
2473             bgLayer = new KisPaintLayer(image.data(), i18n("Background"), OPACITY_OPAQUE_U8, cs);
2474             bgLayer->paintDevice()->setDefaultPixel(strippedAlpha);
2475             bgLayer->setPinnedToTimeline(autopin);
2476         } else if (bgStyle == KisConfig::FILL_LAYER) {
2477             KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration(KisGlobalResourcesInterface::instance());
2478             filter_config->setProperty("color", strippedAlpha.toQColor());
2479             filter_config->createLocalResourcesSnapshot();
2480             bgLayer = new KisGeneratorLayer(image.data(), i18nc("Name of automatically created background color fill layer", "Background Fill"), filter_config, image->globalSelection());
2481         }
2482 
2483         bgLayer->setOpacity(bgColor.opacityU8());
2484 
2485         if (numberOfLayers > 1) {
2486             //Lock bg layer if others are present.
2487             bgLayer->setUserLocked(true);
2488         }
2489     }
2490     else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer).
2491         image->setDefaultProjectionColor(bgColor);
2492         bgLayer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
2493     }
2494 
2495     Q_CHECK_PTR(bgLayer);
2496     image->addNode(bgLayer.data(), image->rootLayer().data());
2497     bgLayer->setDirty(QRect(0, 0, width, height));
2498 
2499     // reset mirror axis to default:
2500     d->mirrorAxisConfig.setAxisPosition(QRectF(image->bounds()).center());
2501     setCurrentImage(image);
2502 
2503     for(int i = 1; i < numberOfLayers; ++i) {
2504         KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
2505         layer->setPinnedToTimeline(autopin);
2506         image->addNode(layer, image->root(), i);
2507         layer->setDirty(QRect(0, 0, width, height));
2508     }
2509 
2510     KisUsageLogger::log(
2511         QString("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8")
2512             .arg(name, QString::number(width), QString::number(height),
2513                  QString::number(imageResolution * 72.0), image->colorSpace()->colorModelId().name(),
2514                  image->colorSpace()->colorDepthId().name(), image->colorSpace()->profile()->name(),
2515                  QString::number(numberOfLayers)));
2516 
2517     QApplication::restoreOverrideCursor();
2518 
2519     return true;
2520 }
2521 
2522 bool KisDocument::isSaving() const
2523 {
2524     const bool result = d->savingMutex.tryLock();
2525     if (result) {
2526         d->savingMutex.unlock();
2527     }
2528     return !result;
2529 }
2530 
2531 void KisDocument::waitForSavingToComplete()
2532 {
2533     if (isSaving()) {
2534         KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0);
2535         f.waitForMutex(&d->savingMutex);
2536     }
2537 }
2538 
2539 KoShapeControllerBase *KisDocument::shapeController() const
2540 {
2541     return d->shapeController;
2542 }
2543 
2544 KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const
2545 {
2546     return d->shapeController->shapeForNode(layer);
2547 }
2548 
2549 QList<KisPaintingAssistantSP> KisDocument::assistants() const
2550 {
2551     return d->assistants;
2552 }
2553 
2554 void KisDocument::setAssistants(const QList<KisPaintingAssistantSP> &value)
2555 {
2556     if (d->assistants != value) {
2557         d->assistants = value;
2558         d->syncDecorationsWrapperLayerState();
2559         emit sigAssistantsChanged();
2560     }
2561 }
2562 
2563 KisReferenceImagesLayerSP KisDocument::referenceImagesLayer() const
2564 {
2565     if (!d->image) return KisReferenceImagesLayerSP();
2566 
2567     KisReferenceImagesLayerSP referencesLayer =
2568             KisLayerUtils::findNodeByType<KisReferenceImagesLayer>(d->image->root());
2569 
2570     return referencesLayer;
2571 }
2572 
2573 void KisDocument::setReferenceImagesLayer(KisSharedPtr<KisReferenceImagesLayer> layer, bool updateImage)
2574 {
2575     KisReferenceImagesLayerSP currentReferenceLayer = referenceImagesLayer();
2576 
2577     // updateImage=false inherently means we are not changing the
2578     // reference images layer, but just would like to update its signals.
2579     if (currentReferenceLayer == layer && updateImage) {
2580         return;
2581     }
2582 
2583     d->referenceLayerConnections.clear();
2584 
2585     if (updateImage) {
2586         if (currentReferenceLayer) {
2587             d->image->removeNode(currentReferenceLayer);
2588         }
2589 
2590         if (layer) {
2591             d->image->addNode(layer);
2592         }
2593     }
2594 
2595     currentReferenceLayer = layer;
2596 
2597     if (currentReferenceLayer) {
2598         d->referenceLayerConnections.addConnection(
2599                     currentReferenceLayer, SIGNAL(sigUpdateCanvas(QRectF)),
2600                     this, SIGNAL(sigReferenceImagesChanged()));
2601     }
2602 
2603     emit sigReferenceImagesLayerChanged(layer);
2604 }
2605 
2606 void KisDocument::setPreActivatedNode(KisNodeSP activatedNode)
2607 {
2608     d->preActivatedNode = activatedNode;
2609 }
2610 
2611 KisNodeSP KisDocument::preActivatedNode() const
2612 {
2613     return d->preActivatedNode;
2614 }
2615 
2616 KisImageWSP KisDocument::image() const
2617 {
2618     return d->image;
2619 }
2620 
2621 KisImageSP KisDocument::savingImage() const
2622 {
2623     return d->savingImage;
2624 }
2625 
2626 
2627 void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate)
2628 {
2629     if (d->image) {
2630         // Disconnect existing sig/slot connections
2631         d->image->setUndoStore(new KisDumbUndoStore());
2632         d->image->disconnect(this);
2633         d->shapeController->setImage(0);
2634         d->image = 0;
2635     }
2636 
2637     if (!image) return;
2638 
2639     if (d->linkedResourceStorage){
2640         d->linkedResourceStorage->setMetaData(KisResourceStorage::s_meta_name, image->objectName());
2641     }
2642 
2643     d->setImageAndInitIdleWatcher(image);
2644     d->image->setUndoStore(new KisDocumentUndoStore(this));
2645     d->shapeController->setImage(image);
2646     d->image->setMirrorAxesCenter(KisAlgebra2D::absoluteToRelative(d->mirrorAxisConfig.axisPosition(), image->bounds()));
2647     setModified(false);
2648     connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
2649     connect(d->image, SIGNAL(sigImageModifiedWithoutUndo()), this, SLOT(setImageModifiedWithoutUndo()), Qt::UniqueConnection);
2650     connect(d->image, SIGNAL(sigLayersChangedAsync()), this, SLOT(slotImageRootChanged()));
2651 
2652     if (forceInitialUpdate) {
2653         d->image->initialRefreshGraph();
2654     }
2655 }
2656 
2657 void KisDocument::hackPreliminarySetImage(KisImageSP image)
2658 {
2659     KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image);
2660 
2661     // we set image without connecting idle-watcher, because loading
2662     // hasn't been finished yet
2663     d->image = image;
2664     d->shapeController->setImage(image);
2665 }
2666 
2667 void KisDocument::setImageModified()
2668 {
2669     // we only set as modified if undo stack is not at clean state
2670     setModified(d->imageModifiedWithoutUndo || !d->undoStack->isClean());
2671 }
2672 
2673 void KisDocument::setImageModifiedWithoutUndo()
2674 {
2675     d->imageModifiedWithoutUndo = true;
2676     setImageModified();
2677 }
2678 
2679 
2680 KisUndoStore* KisDocument::createUndoStore()
2681 {
2682     return new KisDocumentUndoStore(this);
2683 }
2684 
2685 bool KisDocument::isAutosaving() const
2686 {
2687     return d->isAutosaving;
2688 }
2689 
2690 QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage)
2691 {
2692     return errorMessage.isEmpty() ? status.errorMessage() : errorMessage;
2693 }
2694 
2695 void KisDocument::setAssistantsGlobalColor(QColor color)
2696 {
2697     d->globalAssistantsColor = color;
2698 }
2699 
2700 QColor KisDocument::assistantsGlobalColor()
2701 {
2702     return d->globalAssistantsColor;
2703 }
2704 
2705 QRectF KisDocument::documentBounds() const
2706 {
2707     QRectF bounds = d->image->bounds();
2708 
2709     KisReferenceImagesLayerSP referenceImagesLayer = this->referenceImagesLayer();
2710 
2711     if (referenceImagesLayer) {
2712         bounds |= referenceImagesLayer->boundingImageRect();
2713     }
2714 
2715     return bounds;
2716 }