KisDocument.cpp 80.7 KB
Newer Older
1
/* This file is part of the Krita project
2
 *
3
 * SPDX-FileCopyrightText: 2014 Boudewijn Rempt <boud@valdyas.org>
4
 *
Samuel Gaist's avatar
Samuel Gaist committed
5
 * SPDX-License-Identifier: LGPL-2.0-or-later
6
7
8
 */

#include "KisMainWindow.h" // XXX: remove
Halla Rempt's avatar
Halla Rempt committed
9
#include <QMessageBox> // XXX: remove
10

11
#include <KisMimeDatabase.h>
12
13
14
15
16
17
18
19
20
21

#include <KoCanvasBase.h>
#include <KoColor.h>
#include <KoColorProfile.h>
#include <KoColorSpaceEngine.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoDocumentInfoDlg.h>
#include <KoDocumentInfo.h>
#include <KoDpi.h>
22
#include <KoUnit.h>
23
24
25
26
27
#include <KoID.h>
#include <KoProgressProxy.h>
#include <KoProgressUpdater.h>
#include <KoSelection.h>
#include <KoShape.h>
28
#include <KoShapeController.h>
29
30
31
#include <KoStore.h>
#include <KoUpdater.h>
#include <KoXmlWriter.h>
32
#include <KoXmlReader.h>
33
#include <KoStoreDevice.h>
34
#include <KoDialog.h>
35
#include <KisImportExportErrorCode.h>
36
#include <KoDocumentResourceManager.h>
37
#include <KoMD5Generator.h>
38
39
#include <KisResourceStorage.h>
#include <KisResourceLocator.h>
40
#include <KisResourceTypes.h>
41
#include <KisGlobalResourcesInterface.h>
42
43
#include <KisResourceLoaderRegistry.h>
#include <KisResourceModelProvider.h>
44
#include <KisUsageLogger.h>
45
#include <klocalizedstring.h>
Scott Petrovic's avatar
Scott Petrovic committed
46
#include "kis_scratch_pad.h"
Halla Rempt's avatar
Halla Rempt committed
47
#include <kis_debug.h>
48
49
#include <kis_generator_layer.h>
#include <kis_generator_registry.h>
50
51
#include <kdesktopfile.h>
#include <kconfiggroup.h>
52
#include <kbackup.h>
Scott Petrovic's avatar
Scott Petrovic committed
53
#include <KisView.h>
54

55
#include <QTextBrowser>
56
57
#include <QApplication>
#include <QBuffer>
58
#include <QStandardPaths>
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <QDir>
#include <QDomDocument>
#include <QDomElement>
#include <QFileInfo>
#include <QImage>
#include <QList>
#include <QPainter>
#include <QRect>
#include <QScopedPointer>
#include <QSize>
#include <QStringList>
#include <QtGlobal>
#include <QTimer>
#include <QWidget>
73
74
#include <QFuture>
#include <QFutureWatcher>
Halla Rempt's avatar
Halla Rempt committed
75
#include <QUuid>
76
77

// Krita Image
78
#include <kis_image_animation_interface.h>
79
80
81
82
83
84
85
86
87
88
89
#include <kis_config.h>
#include <flake/kis_shape_layer.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_name_server.h>
#include <kis_paint_layer.h>
#include <kis_painter.h>
#include <kis_selection.h>
#include <kis_fill_painter.h>
#include <kis_document_undo_store.h>
90
91
#include <kis_idle_watcher.h>
#include <kis_signal_auto_connection.h>
92
#include <kis_canvas_widget_base.h>
93
#include "kis_layer_utils.h"
94
#include "kis_selection_mask.h"
95
96
97
98
99
100
101
102
103
104

// Local
#include "KisViewManager.h"
#include "kis_clipboard.h"
#include "widgets/kis_custom_image_widget.h"
#include "canvas/kis_canvas2.h"
#include "flake/kis_shape_controller.h"
#include "kis_statusbar.h"
#include "widgets/kis_progress_widget.h"
#include "kis_canvas_resource_provider.h"
105
#include "KisResourceServerProvider.h"
106
107
#include "kis_node_manager.h"
#include "KisPart.h"
108
109
110
111
#include "KisApplication.h"
#include "KisDocument.h"
#include "KisImportExportManager.h"
#include "KisView.h"
112
#include "kis_grid_config.h"
113
#include "kis_guides_config.h"
114
#include "kis_image_barrier_lock_adapter.h"
115
#include "KisReferenceImagesLayer.h"
116
#include "dialogs/KisRecoverNamedAutosaveDialog.h"
117

118
#include <mutex>
119
#include "kis_config_notifier.h"
120
#include "kis_async_action_feedback.h"
121
#include "KisCloneDocumentStroke.h"
122

123
#include <KisMirrorAxisConfig.h>
124
125
#include <KisDecorationsWrapperLayer.h>
#include "kis_simple_stroke_strategy.h"
126

127
// Define the protocol used here for embedded documents' URL
Halla Rempt's avatar
Halla Rempt committed
128
// This used to "store" but QUrl didn't like it,
129
130
// so let's simply make it "tar" !
#define STORE_PROTOCOL "tar"
Halla Rempt's avatar
Halla Rempt committed
131
// The internal path is a hack to make QUrl happy and for document children
132
133
134
135
136
137
138
139
#define INTERNAL_PROTOCOL "intern"
#define INTERNAL_PREFIX "intern:/"
// Warning, keep it sync in koStore.cc

#include <unistd.h>

using namespace std;

140
141
142
143
144
145
namespace {
constexpr int errorMessageTimeout = 5000;
constexpr int successMessageTimeout = 1000;
}


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**********************************************************
 *
 * KisDocument
 *
 **********************************************************/

//static
QString KisDocument::newObjectName()
{
    static int s_docIFNumber = 0;
    QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
    return name;
}


class UndoStack : public KUndo2Stack
{
public:
    UndoStack(KisDocument *doc)
165
166
        : KUndo2Stack(doc),
          m_doc(doc)
167
168
169
    {
    }

170
    void setIndex(int idx) override {
171
172
        m_postponedJobs.append({PostponedJob::SetIndex, idx});
        processPostponedJobs();
173
174
    }

175
    void notifySetIndexChangedOneCommand() override {
176
177
        KisImageWSP image = this->image();
        image->unlock();
178
179
180
181
182
183
184
185
186

        /**
         * Some very weird commands may emit blocking signals to
         * the GUI (e.g. KisGuiContextCommand). Here is the best thing
         * we can do to avoid the deadlock
         */
        while(!image->tryBarrierLock()) {
            QApplication::processEvents();
        }
187
188
    }

189
    void undo() override {
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
        m_postponedJobs.append({PostponedJob::Undo, 0});
        processPostponedJobs();
    }


    void redo() override {
        m_postponedJobs.append({PostponedJob::Redo, 0});
        processPostponedJobs();
    }

private:
    KisImageWSP image() {
        KisImageWSP currentImage = m_doc->image();
        Q_ASSERT(currentImage);
        return currentImage;
    }

    void setIndexImpl(int idx) {
        KisImageWSP image = this->image();
        image->requestStrokeCancellation();
        if(image->tryBarrierLock()) {
            KUndo2Stack::setIndex(idx);
            image->unlock();
        }
    }

    void undoImpl() {
217
218
        KisImageWSP image = this->image();
        image->requestUndoDuringStroke();
219
220
221
222
223

        if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) {
            return;
        }

224
225
226
227
228
229
        if(image->tryBarrierLock()) {
            KUndo2Stack::undo();
            image->unlock();
        }
    }

230
    void redoImpl() {
231
        KisImageWSP image = this->image();
232
233
        image->requestRedoDuringStroke();

234
235
236
237
238
239
        if(image->tryBarrierLock()) {
            KUndo2Stack::redo();
            image->unlock();
        }
    }

240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
    void processPostponedJobs() {
        /**
         * Some undo commands may call QApplication::processEvents(),
         * see notifySetIndexChangedOneCommand(). That may cause
         * recursive calls to the undo stack methods when used from
         * the Undo History docker. Here we try to handle that gracefully
         * by accumulating all the requests and executing them at the
         * topmost level of recursion.
         */
        if (m_recursionCounter > 0) return;

        m_recursionCounter++;

        while (!m_postponedJobs.isEmpty()) {
            PostponedJob job = m_postponedJobs.dequeue();
            switch (job.type) {
            case PostponedJob::SetIndex:
                setIndexImpl(job.index);
                break;
            case PostponedJob::Redo:
                redoImpl();
                break;
            case PostponedJob::Undo:
                undoImpl();
                break;
            }
        }

        m_recursionCounter--;
269
270
271
    }

private:
272
273
274
275
276
277
278
279
280
281
282
283
284
    int m_recursionCounter = 0;

    struct PostponedJob {
        enum Type {
            Undo = 0,
            Redo,
            SetIndex
        };
        Type type = Undo;
        int index = 0;
    };
    QQueue<PostponedJob> m_postponedJobs;

285
286
287
    KisDocument *m_doc;
};

288
class Q_DECL_HIDDEN KisDocument::Private
289
290
{
public:
291
292
293
294
295
296
    Private(KisDocument *_q)
        : q(_q)
        , docInfo(new KoDocumentInfo(_q)) // deleted by QObject
        , importExportManager(new KisImportExportManager(_q)) // deleted manually
        , autoSaveTimer(new QTimer(_q))
        , undoStack(new UndoStack(_q)) // deleted by QObject
297
298
299
300
301
302
303
        , m_bAutoDetectedMime(false)
        , modified(false)
        , readwrite(true)
        , firstMod(QDateTime::currentDateTime())
        , lastMod(firstMod)
        , nserver(new KisNameServer(1))
        , imageIdleWatcher(2000 /*ms*/)
304
        , globalAssistantsColor(KisConfig(true).defaultAssistantsColor())
305
306
        , savingLock(&savingMutex)
        , batchMode(false)
307
    {
308
        if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
309
310
311
312
            unit = KoUnit::Inch;
        } else {
            unit = KoUnit::Centimeter;
        }
313
        connect(&imageIdleWatcher, SIGNAL(startedIdleMode()), q, SLOT(slotPerformIdleRoutines()));
314
315
    }

316
317
318
319
320
321
    Private(const Private &rhs, KisDocument *_q)
        : q(_q)
        , docInfo(new KoDocumentInfo(*rhs.docInfo, _q))
        , importExportManager(new KisImportExportManager(_q))
        , autoSaveTimer(new QTimer(_q))
        , undoStack(new UndoStack(_q))
322
323
324
325
        , nserver(new KisNameServer(*rhs.nserver))
        , preActivatedNode(0) // the node is from another hierarchy!
        , imageIdleWatcher(2000 /*ms*/)
        , savingLock(&savingMutex)
326
    {
327
        copyFromImpl(rhs, _q, CONSTRUCT);
328
        connect(&imageIdleWatcher, SIGNAL(startedIdleMode()), q, SLOT(slotPerformIdleRoutines()));
329
330
    }

331
332
333
334
335
    ~Private() {
        // Don't delete m_d->shapeController because it's in a QObject hierarchy.
        delete nserver;
    }

336
    KisDocument *q = 0;
337
    KoDocumentInfo *docInfo = 0;
338
339
340

    KoUnit unit;

341
    KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options]
342

343
344
    QByteArray mimeType; // The actual mimeType of the document
    QByteArray outputMimeType; // The mimeType to use when saving
Halla Rempt's avatar
Halla Rempt committed
345

346
    QTimer *autoSaveTimer;
347
    QString lastErrorMessage; // see openFile()
348
    QString lastWarningMessage;
349
350
351
352
    int autoSaveDelay = 300; // in seconds, 0 to disable.
    bool modifiedAfterAutosave = false;
    bool isAutosaving = false;
    bool disregardAutosaveFailure = false;
353
    int autoSaveFailureCount = 0;
354

355
    KUndo2Stack *undoStack = 0;
356

357
    KisGuidesConfig guidesConfig;
358
    KisMirrorAxisConfig mirrorAxisConfig;
359

360
    bool m_bAutoDetectedMime = false; // whether the mimeType in the arguments was detected by the part itself
361
    QString m_path; // local url - the one displayed to the user.
362
    QString m_file; // Local file - the only one the part implementation should deal with.
363

364
    QMutex savingMutex;
365

366
367
    bool modified = false;
    bool readwrite = false;
368

369
370
371
    QDateTime firstMod;
    QDateTime lastMod;

372
373
374
    KisNameServer *nserver;

    KisImageSP image;
375
376
    KisImageSP savingImage;

377
    KisNodeWSP preActivatedNode;
378
379
    KisShapeController* shapeController = 0;
    KoShapeController* koShapeController = 0;
380
381
382
    KisIdleWatcher imageIdleWatcher;
    QScopedPointer<KisSignalAutoConnection> imageIdleConnection;

383
    QList<KisPaintingAssistantSP> assistants;
384

385
    StoryboardItemList m_storyboardItemList;
386
    QVector<StoryboardComment> m_storyboardCommentList;
387

Dmitry Kazakov's avatar
Dmitry Kazakov committed
388
389
    QColor globalAssistantsColor;

390
    KisGridConfig gridConfig;
391

392
393
    StdLockableWrapper<QMutex> savingLock;

394
    bool imageModifiedWithoutUndo = false;
395
    bool modifiedWhileSaving = false;
396
    QScopedPointer<KisDocument> backgroundSaveDocument;
397
    QPointer<KoUpdater> savingUpdater;
398
    QFuture<KisImportExportErrorCode> childSavingFuture;
399
    KritaUtils::ExportFileJob backgroundSaveJob;
400
    KisSignalAutoConnectionsStore referenceLayerConnections;
401
402
403

    bool isRecovered = false;

404
    bool batchMode { false };
405
    bool decorationsSyncingDisabled = false;
406

407
    QString documentStorageID {QUuid::createUuid().toString()};
408
    KisResourceStorageSP documentResourceStorage;
Halla Rempt's avatar
Halla Rempt committed
409

410
411
    void syncDecorationsWrapperLayerState();

412
413
    void setImageAndInitIdleWatcher(KisImageSP _image) {
        image = _image;
414

415
416
        imageIdleWatcher.setTrackedImage(image);
    }
417

418
    void copyFrom(const Private &rhs, KisDocument *q);
419
420
421
422
    void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy);

    /// clones the palette list oldList
    /// the ownership of the returned KoColorSet * belongs to the caller
423
    class StrippedSafeSavingLocker;
424
425
};

426
427
428

void KisDocument::Private::syncDecorationsWrapperLayerState()
{
429
    if (!this->image || this->decorationsSyncingDisabled) return;
430

431
432
    KisImageSP image = this->image;
    KisDecorationsWrapperLayerSP decorationsLayer =
433
            KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(image->root());
434
435
436
437
438
439

    const bool needsDecorationsWrapper =
            gridConfig.showGrid() || (guidesConfig.showGuides() && guidesConfig.hasGuides()) || !assistants.isEmpty();

    struct SyncDecorationsWrapperStroke : public KisSimpleStrokeStrategy {
        SyncDecorationsWrapperStroke(KisDocument *document, bool needsDecorationsWrapper)
440
441
            : KisSimpleStrokeStrategy(QLatin1String("sync-decorations-wrapper"),
                                      kundo2_noi18n("start-isolated-mode")),
442
443
444
445
446
              m_document(document),
              m_needsDecorationsWrapper(needsDecorationsWrapper)
        {
            this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
            setClearsRedoOnStart(false);
447
            setRequestsOtherStrokesToEnd(false);
448
449
        }

Halla Rempt's avatar
Halla Rempt committed
450
        void initStrokeCallback() override {
451
            KisDecorationsWrapperLayerSP decorationsLayer =
452
                    KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(m_document->image()->root());
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469

            if (m_needsDecorationsWrapper && !decorationsLayer) {
                m_document->image()->addNode(new KisDecorationsWrapperLayer(m_document));
            } else if (!m_needsDecorationsWrapper && decorationsLayer) {
                m_document->image()->removeNode(decorationsLayer);
            }
        }

    private:
        KisDocument *m_document = 0;
        bool m_needsDecorationsWrapper = false;
    };

    KisStrokeId id = image->startStroke(new SyncDecorationsWrapperStroke(q, needsDecorationsWrapper));
    image->endStroke(id);
}

470
471
void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q)
{
472
473
474
475
476
477
478
479
    copyFromImpl(rhs, q, KisDocument::REPLACE);
}

void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy)
{
    if (policy == REPLACE) {
        delete docInfo;
    }
480
481
482
483
    docInfo = (new KoDocumentInfo(*rhs.docInfo, q));
    unit = rhs.unit;
    mimeType = rhs.mimeType;
    outputMimeType = rhs.outputMimeType;
484
485
486
487
488
489

    if (policy == REPLACE) {
        q->setGuidesConfig(rhs.guidesConfig);
        q->setMirrorAxisConfig(rhs.mirrorAxisConfig);
        q->setModified(rhs.modified);
        q->setAssistants(KisPaintingAssistant::cloneAssistantList(rhs.assistants));
490
        q->setStoryboardItemList(StoryboardItem::cloneStoryboardItemList(rhs.m_storyboardItemList));
491
        q->setStoryboardCommentList(rhs.m_storyboardCommentList);
492
493
494
495
496
497
498
499
        q->setGridConfig(rhs.gridConfig);
    } else {
        // in CONSTRUCT mode, we cannot use the functions of KisDocument
        // because KisDocument does not yet have a pointer to us.
        guidesConfig = rhs.guidesConfig;
        mirrorAxisConfig = rhs.mirrorAxisConfig;
        modified = rhs.modified;
        assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants);
500
        m_storyboardItemList = StoryboardItem::cloneStoryboardItemList(rhs.m_storyboardItemList);
501
        m_storyboardCommentList = rhs.m_storyboardCommentList;
502
503
        gridConfig = rhs.gridConfig;
    }
504
    imageModifiedWithoutUndo = rhs.imageModifiedWithoutUndo;
505
    m_bAutoDetectedMime = rhs.m_bAutoDetectedMime;
506
    m_path = rhs.m_path;
507
508
509
510
    m_file = rhs.m_file;
    readwrite = rhs.readwrite;
    firstMod = rhs.firstMod;
    lastMod = rhs.lastMod;
511
    // XXX: the display properties will be shared between different snapshots
512
    globalAssistantsColor = rhs.globalAssistantsColor;
513
    batchMode = rhs.batchMode;
514

515
516
517
518
519
520
521
522
    // CHECK THIS! This is what happened to the palette list -- but is it correct here as well? Ask Dmitry!!!
    //    if (policy == REPLACE) {
    //        QList<KoColorSetSP> newPaletteList = clonePaletteList(rhs.paletteList);
    //        q->setPaletteList(newPaletteList, /* emitSignal = */ true);
    //        // we still do not own palettes if we did not
    //    } else {
    //        paletteList = rhs.paletteList;
    //    }
523
524

    if (rhs.documentResourceStorage) {
525
526
527
528
529
530
531
532
        if (policy == REPLACE) {
            // Clone the resources, but don't add them to the database, only the editable
            // version of the document should have those resources in the database.
            documentResourceStorage = rhs.documentResourceStorage->clone();
        }
        else {
            documentResourceStorage = rhs.documentResourceStorage;
        }
533
    }
534

535
536
}

537
class KisDocument::Private::StrippedSafeSavingLocker {
538
public:
539
540
541
542
543
    StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
        : m_locked(false)
        , m_image(image)
        , m_savingLock(savingMutex)
        , m_imageLock(image, true)
544

545
    {
546
547
        /**
         * Initial try to lock both objects. Locking the image guards
548
         * us from any image composition threads running in the
549
550
551
552
553
554
         * background, while savingMutex guards us from entering the
         * saving code twice by autosave and main threads.
         *
         * Since we are trying to lock multiple objects, so we should
         * do it in a safe manner.
         */
555
        m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
556
557

        if (!m_locked) {
558
            m_image->requestStrokeEnd();
559
            QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
560

561
562
            // one more try...
            m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
563
564
565
        }
    }

566
    ~StrippedSafeSavingLocker() {
567
568
569
570
571
        if (m_locked) {
            m_imageLock.unlock();
            m_savingLock.unlock();
        }
    }
572
573
574
575
576
577
578

    bool successfullyLocked() const {
        return m_locked;
    }

private:
    bool m_locked;
579
580
    KisImageSP m_image;
    StdLockableWrapper<QMutex> m_savingLock;
581
    KisImageBarrierLockAdapter m_imageLock;
582
583
};

584
KisDocument::KisDocument(bool addStorage)
585
    : d(new Private(this))
586
{
587
    connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
588
    connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
589
    connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
590
591
    setObjectName(newObjectName());

592
593
594
595
596

    if (addStorage) {
        d->documentResourceStorage.reset(new KisResourceStorage(d->documentStorageID));
        KisResourceLocator::instance()->addStorage(d->documentStorageID, d->documentResourceStorage);
    }
597

598
599
600
    // preload the krita resources
    KisResourceServerProvider::instance();

601
    d->shapeController = new KisShapeController(d->nserver, d->undoStack, this);
602
603
    d->koShapeController = new KoShapeController(0, d->shapeController);
    d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController);
604

605
    slotConfigChanged();
606
}
607

608
609
610
611
KisDocument::KisDocument(const KisDocument &rhs)
    : QObject(),
      d(new Private(*rhs.d, this))
{
612
    copyFromDocumentImpl(rhs, CONSTRUCT);
613
614
615
616
}

KisDocument::~KisDocument()
{
617
618
    // wait until all the pending operations are in progress
    waitForSavingToComplete();
619
    d->imageIdleWatcher.setTrackedImage(0);
620

621
622
623
624
625
626
    /**
     * Push a timebomb, which will try to release the memory after
     * the document has been deleted
     */
    KisPaintDevice::createMemoryReleaseObject()->deleteLater();

627
628
    d->autoSaveTimer->disconnect(this);
    d->autoSaveTimer->stop();
629

Halla Rempt's avatar
Halla Rempt committed
630
    delete d->importExportManager;
631
632
633

    // Despite being QObject they needs to be deleted before the image
    delete d->shapeController;
634

635
    delete d->koShapeController;
636
637
638

    if (d->image) {
        d->image->notifyAboutToBeDeleted();
639

640
641
642
643
644
645
646
647
648
649
650
651
652
653
        /**
         * WARNING: We should wait for all the internal image jobs to
         * finish before entering KisImage's destructor. The problem is,
         * while execution of KisImage::~KisImage, all the weak shared
         * pointers pointing to the image enter an inconsistent
         * state(!). The shared counter is already zero and destruction
         * has started, but the weak reference doesn't know about it,
         * because KisShared::~KisShared hasn't been executed yet. So all
         * the threads running in background and having weak pointers will
         * enter the KisImage's destructor as well.
         */

        d->image->requestStrokeCancellation();
        d->image->waitForDone();
654

655
656
657
        // clear undo commands that can still point to the image
        d->undoStack->clear();
        d->image->waitForDone();
658

659
660
        KisImageWSP sanityCheckPointer = d->image;
        Q_UNUSED(sanityCheckPointer);
661

662
663
        // The following line trigger the deletion of the image
        d->image.clear();
664

665
666
667
        // check if the image has actually been deleted
        KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
    }
668
669
    if (KisResourceLocator::instance()->hasStorage(d->documentStorageID)) {
        KisResourceLocator::instance()->removeStorage(d->documentStorageID);
670
    }
671
672
673
674

    delete d;
}

Halla Rempt's avatar
Halla Rempt committed
675
676
QString KisDocument::uniqueID() const
{
677
    return d->documentStorageID;
Halla Rempt's avatar
Halla Rempt committed
678
679
}

680
KisDocument *KisDocument::clone()
681
{
682
    return new KisDocument(*this);
683
684
}

Sachin Jindal's avatar
Sachin Jindal committed
685
bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting)
686
{
687
    QFileInfo filePathInfo(job.filePath);
Halla Rempt's avatar
Halla Rempt committed
688

689
    if (filePathInfo.exists() && !filePathInfo.isWritable()) {
690
        slotCompleteSavingDocument(job, ImportExportCodes::NoAccessToWrite,
691
                                   i18n("%1 cannot be written to. Please save under a different name.", job.filePath));
Agata Cacko's avatar
Agata Cacko committed
692
        //return ImportExportCodes::NoAccessToWrite;
693
694
695
        return false;
    }

696
    KisConfig cfg(true);
697
    if (cfg.backupFile() && filePathInfo.exists()) {
698
699
700
701
702
703
704
705
706
707
708

        QString backupDir;

        switch(cfg.readEntry<int>("backupfilelocation", 0)) {
        case 1:
            backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
            break;
        case 2:
            backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
            break;
        default:
Sharaf Zaman's avatar
Sharaf Zaman committed
709
710
711
712
713
714
#ifdef Q_OS_ANDROID
            // We deal with URIs, there may or may not be a "directory"
            backupDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).append("/krita-backup");
            QDir().mkpath(backupDir);
#endif

715
716
717
718
719
720
721
722
            // Do nothing: the empty string is user file location
            break;
        }

        int numOfBackupsKept = cfg.readEntry<int>("numberofbackupfiles", 1);
        QString suffix = cfg.readEntry<QString>("backupfilesuffix", "~");

        if (numOfBackupsKept == 1) {
723
724
725
726
727
728
729
730
            if (!KBackup::simpleBackupFile(job.filePath, backupDir, suffix)) {
                qWarning() << "Failed to create simple backup file!" << job.filePath << backupDir << suffix;
                KisUsageLogger::log(QString("Failed to create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir));
                return false;
            }
            else {
                KisUsageLogger::log(QString("Create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir));
            }
731
        }
732
        else if (numOfBackupsKept > 1) {
733
734
735
736
737
738
739
740
            if (!KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept)) {
                qWarning() << "Failed to create numbered backup file!" << job.filePath << backupDir << suffix;
                KisUsageLogger::log(QString("Failed to create a numbered backup for %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir));
                return false;
            }
            else {
                KisUsageLogger::log(QString("Create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir));
            }
741
        }
742
    }
743

Agata Cacko's avatar
Agata Cacko committed
744
745
    //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
    if (job.mimeType.isEmpty()) {
746
        KisImportExportErrorCode error = ImportExportCodes::FileFormatNotSupported;
Agata Cacko's avatar
Agata Cacko committed
747
748
749
750
        slotCompleteSavingDocument(job, error, error.errorMessage());
        return false;

    }
751

752
    const QString actionName =
753
754
755
            job.flags & KritaUtils::SaveIsExporting ?
                i18n("Exporting Document...") :
                i18n("Saving Document...");
Halla Rempt's avatar
Halla Rempt committed
756

757
    bool started =
758
759
760
            initiateSavingInBackground(actionName,
                                       this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,QString)),
                                       job, exportConfiguration, isAdvancedExporting);
761
762
    if (!started) {
        emit canceled(QString());
763
764
    }

765
    return started;
766
767
}

Sachin Jindal's avatar
Sachin Jindal committed
768
bool KisDocument::exportDocument(const QString &path, const QByteArray &mimeType, bool isAdvancedExporting, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
769
{
770
    using namespace KritaUtils;
771

772
773
774
    SaveFlags flags = SaveIsExporting;
    if (showWarnings) {
        flags |= SaveShowWarnings;
775
776
    }

777
    KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8")
778
                        .arg(path)
779
780
781
782
783
784
785
786
                        .arg(QString::fromLatin1(mimeType))
                        .arg(d->image->width())
                        .arg(d->image->height())
                        .arg(d->image->nlayers())
                        .arg(d->image->animationInterface()->totalLength())
                        .arg(d->image->animationInterface()->framerate())
                        .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration"));

Sharaf Zaman's avatar
Sharaf Zaman committed
787

788
    return exportDocumentImpl(KritaUtils::ExportFileJob(path,
789
790
                                                        mimeType,
                                                        flags),
Sachin Jindal's avatar
Sachin Jindal committed
791
                              exportConfiguration, isAdvancedExporting);
792
}
793

794
bool KisDocument::saveAs(const QString &_path, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
795
796
{
    using namespace KritaUtils;
797

798
    KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers.  %6 frames, %7 framerate. Export configuration: %8")
799
                        .arg(_path)
800
801
802
803
804
805
806
                        .arg(QString::fromLatin1(mimeType))
                        .arg(d->image->width())
                        .arg(d->image->height())
                        .arg(d->image->nlayers())
                        .arg(d->image->animationInterface()->totalLength())
                        .arg(d->image->animationInterface()->framerate())
                        .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")
807
                        .arg(path()));
808
809


810
811
812
813
814
815
    // Check whether it's an existing resource were are saving to
    if (resourceSavingFilter(_path, mimeType, exportConfiguration)) {
        return true;
    }


816
    return exportDocumentImpl(ExportFileJob(_path,
817
818
819
                                            mimeType,
                                            showWarnings ? SaveShowWarnings : SaveNone),
                              exportConfiguration);
820
821
}

822
bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
823
{
824
    return saveAs(path(), mimeType(), showWarnings, exportConfiguration);
825
826
}

827
828
829
830
831
832
833
834
835
QByteArray KisDocument::serializeToNativeByteArray()
{
    QByteArray byteArray;
    QBuffer buffer(&byteArray);

    QScopedPointer<KisImportExportFilter> filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export));
    filter->setBatchMode(true);
    filter->setMimeType(nativeFormatMimeType());

836
837
    Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
    if (!locker.successfullyLocked()) {
838
839
840
        return byteArray;
    }

841
842
    d->savingImage = d->image;

843
    if (!filter->convert(this, &buffer).isOk()) {
844
845
846
847
848
849
        qWarning() << "serializeToByteArray():: Could not export to our native format";
    }

    return byteArray;
}

850
void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage)
851
{
852
    if (status.isCancelled())
Igor Wect's avatar
Igor Wect committed
853
854
        return;

855
856
    const QString fileName = QFileInfo(job.filePath).fileName();

857
    if (!status.isOk()) {
luz paz's avatar
luz paz committed
858
        emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
859
860
861
                                    "Error during saving %1: %2",
                                    fileName,
                                    exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
862

863
864
        if (!fileBatchMode()) {
            const QString filePath = job.filePath;
865
            QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage)));
866
        }
867
868
    } else {
        if (!(job.flags & KritaUtils::SaveIsExporting)) {
869
870
871
            const QString existingAutoSaveBaseName = localFilePath();
            const bool wasRecovered = isRecovered();

872
            setPath(job.filePath);
873
874
875
876
            setLocalFilePath(job.filePath);
            setMimeType(job.mimeType);
            updateEditingTime(true);

877
            if (!d->modifiedWhileSaving) {
878
                /**
Yuri Chornoivan's avatar
Yuri Chornoivan committed
879
                 * If undo stack is already clean/empty, it doesn't emit any
880
881
882
883
884
885
886
                 * signals, so we might forget update document modified state
                 * (which was set, e.g. while recovering an autosave file)
                 */

                if (d->undoStack->isClean()) {
                    setModified(false);
                } else {
887
                    d->imageModifiedWithoutUndo = false;
888
889
                    d->undoStack->setClean();
                }
890
            }
891
            setRecovered(false);
892
            removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered);
893
894
        }

895
896
        emit completed();
        emit sigSavingFinished();
897
898

        emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
899
    }
900
901
}

902
903
904
905
QByteArray KisDocument::mimeType() const
{
    return d->mimeType;
}
906

907
908
909
910
void KisDocument::setMimeType(const QByteArray & mimeType)
{
    d->mimeType = mimeType;
}
911

912
bool KisDocument::fileBatchMode() const
913
{
914
    return d->batchMode;
915
916
}

917
void KisDocument::setFileBatchMode(const bool batchMode)
918
{
919
    d->batchMode = batchMode;
920
}
921

922
KisDocument* KisDocument::lockAndCloneForSaving()
923
{
924
    // force update of all the asynchronous nodes before cloning
925
    QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
926
927
928
929
930
931
932
933
934
935
936
    KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root());

    KisMainWindow *window = KisPart::instance()->currentMainwindow();
    if (window) {
        if (window->viewManager()) {
            if (!window->viewManager()->blockUntilOperationsFinished(d->image)) {
                return 0;
            }
        }
    }

937
938
939
    Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
    if (!locker.successfullyLocked()) {
        return 0;
940
    }
941

942
    return new KisDocument(*this);
943
}
944

945
946
947
948
KisDocument *KisDocument::lockAndCreateSnapshot()
{
    KisDocument *doc = lockAndCloneForSaving();
    if (doc) {
949
        // clone the local resource storage and its contents -- that is, the old palette list
950
951
952
        if (doc->d->documentResourceStorage) {
            doc->d->documentResourceStorage = doc->d->documentResourceStorage->clone();
        }
953
954
955
956
    }
    return doc;
}

957
958
void KisDocument::copyFromDocument(const KisDocument &rhs)
{
959
960
961
962
963
964
    copyFromDocumentImpl(rhs, REPLACE);
}

void KisDocument::copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy)
{
    if (policy == REPLACE) {
965
        d->decorationsSyncingDisabled = true;
966
        d->copyFrom(*(rhs.d), this);
967
        d->decorationsSyncingDisabled = false;
968
969
970
971
972
973
974
975

        d->undoStack->clear();
    } else {
        // in CONSTRUCT mode, d should be already initialized
        connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
        connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
        connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));

976
        d->shapeController = new KisShapeController(d->nserver, d->undoStack, this);
977
978
979
        d->koShapeController = new KoShapeController(0, d->shapeController);
        d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController);
    }
980

981
982
983
984
985
    setObjectName(rhs.objectName());

    slotConfigChanged();

    if (rhs.d->image) {
986
987
988
989
990
991
        if (policy == REPLACE) {
            d->image->barrierLock(/* readOnly = */ false);
            rhs.d->image->barrierLock(/* readOnly = */ true);
            d->image->copyFromImage(*(rhs.d->image));
            d->image->unlock();
            rhs.d->image->unlock();
992

993
994
995
996
997
998
            setCurrentImage(d->image, /* forceInitialUpdate = */ true);
        } else {
            // clone the image with keeping the GUIDs of the layers intact
            // NOTE: we expect the image to be locked!
            setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false);
        }
999
1000
    }

1001
1002
1003
1004
    if (policy == REPLACE) {
        d->syncDecorationsWrapperLayerState();
    }

1005
1006
1007
1008
    if (rhs.d->preActivatedNode) {
        QQueue<KisNodeSP> linearizedNodes;
        KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(),
                                           [&linearizedNodes](KisNodeSP node) {
1009
1010
            linearizedNodes.enqueue(node);
        });
1011
1012
        KisLayerUtils::recursiveApplyNodes(d->image->root(),
                                           [&linearizedNodes, &rhs, this](KisNodeSP node) {
1013
1014
1015
1016
1017
            KisNodeSP refNode = linearizedNodes.dequeue();
            if (rhs.d->preActivatedNode.data() == refNode.data()) {
                d->preActivatedNode = node;
            }
        });
1018
    }
1019

1020
1021
    // reinitialize references' signal connection
    KisReferenceImagesLayerSP referencesLayer = this->referenceImagesLayer();
1022
1023
1024
    if (referencesLayer) {
        d->referenceLayerConnections.clear();
        d->referenceLayerConnections.addConnection(
1025
1026
                    referencesLayer, SIGNAL(sigUpdateCanvas(QRectF)),
                    this, SIGNAL(sigReferenceImagesChanged()));
1027
1028
1029

        emit sigReferenceImagesLayerChanged(referencesLayer);
    }
1030

1031
    KisDecorationsWrapperLayerSP decorationsLayer =
1032
            KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(d->image->root());
1033
1034
1035
1036
1037
    if (decorationsLayer) {
        decorationsLayer->setDocument(this);
    }


1038
1039
1040
    if (policy == REPLACE) {
        setModified(true);
    }
1041
1042
}

1043
bool KisDocument::exportDocumentSync(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
1044
{