File indexing completed on 2024-05-19 04:29:12

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