KisDocument.cpp 60.3 KB
Newer Older
1
/* This file is part of the Krita project
2
 *
Halla Rempt's avatar
Halla Rempt committed
3
 * Copyright (C) 2014 Boudewijn Rempt <boud@valdyas.org>
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

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

24
#include <KisMimeDatabase.h>
25
26
27
28
29
30
31
32
33
34

#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>
35
#include <KoUnit.h>
36
37
38
39
40
41
#include <KoID.h>
#include <KoOdfReadStore.h>
#include <KoProgressProxy.h>
#include <KoProgressUpdater.h>
#include <KoSelection.h>
#include <KoShape.h>
42
#include <KoShapeController.h>
43
44
45
#include <KoStore.h>
#include <KoUpdater.h>
#include <KoXmlWriter.h>
46
#include <KoXmlReader.h>
47
#include <KoStoreDevice.h>
48
#include <KoDialog.h>
49

50
#include <KisUsageLogger.h>
51
#include <klocalizedstring.h>
Halla Rempt's avatar
Halla Rempt committed
52
#include <kis_debug.h>
53
54
#include <kis_generator_layer.h>
#include <kis_generator_registry.h>
55
56
#include <kdesktopfile.h>
#include <kconfiggroup.h>
57
#include <kbackup.h>
58

59
#include <QTextBrowser>
60
61
#include <QApplication>
#include <QBuffer>
62
#include <QStandardPaths>
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#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>
77
78
#include <QFuture>
#include <QFutureWatcher>
79
80

// Krita Image
81
#include <kis_image_animation_interface.h>
82
83
84
85
86
87
88
89
90
91
92
#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>
93
94
#include <kis_idle_watcher.h>
#include <kis_signal_auto_connection.h>
95
#include <kis_canvas_widget_base.h>
96
#include "kis_layer_utils.h"
97
98
99
100
101
102
103
104
105
106

// 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"
107
#include "KisResourceServerProvider.h"
108
109
#include "kis_node_manager.h"
#include "KisPart.h"
110
111
112
113
#include "KisApplication.h"
#include "KisDocument.h"
#include "KisImportExportManager.h"
#include "KisView.h"
114
#include "kis_grid_config.h"
115
#include "kis_guides_config.h"
116
#include "kis_image_barrier_lock_adapter.h"
117
#include "KisReferenceImagesLayer.h"
118

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

124
125
#include <KisMirrorAxisConfig.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
173
174
175
176
177
178
        KisImageWSP image = this->image();
        image->requestStrokeCancellation();
        if(image->tryBarrierLock()) {
            KUndo2Stack::setIndex(idx);
            image->unlock();
        }
    }

179
    void notifySetIndexChangedOneCommand() override {
180
181
        KisImageWSP image = this->image();
        image->unlock();
182
183
184
185
186
187
188
189
190

        /**
         * 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();
        }
191
192
    }

193
    void undo() override {
194
195
        KisImageWSP image = this->image();
        image->requestUndoDuringStroke();
196
197
198
199
200

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

201
202
203
204
205
206
        if(image->tryBarrierLock()) {
            KUndo2Stack::undo();
            image->unlock();
        }
    }

207
    void redo() override {
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
        KisImageWSP image = this->image();
        if(image->tryBarrierLock()) {
            KUndo2Stack::redo();
            image->unlock();
        }
    }

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

private:
    KisDocument *m_doc;
};

226
class Q_DECL_HIDDEN KisDocument::Private
227
228
{
public:
229
230
231
    Private(KisDocument *q)
        : docInfo(new KoDocumentInfo(q)) // deleted by QObject
        , importExportManager(new KisImportExportManager(q)) // deleted manually
232
        , autoSaveTimer(new QTimer(q))
233
234
235
236
237
238
239
240
        , undoStack(new UndoStack(q)) // deleted by QObject
        , m_bAutoDetectedMime(false)
        , modified(false)
        , readwrite(true)
        , firstMod(QDateTime::currentDateTime())
        , lastMod(firstMod)
        , nserver(new KisNameServer(1))
        , imageIdleWatcher(2000 /*ms*/)
241
        , globalAssistantsColor(KisConfig(true).defaultAssistantsColor())
242
243
        , savingLock(&savingMutex)
        , batchMode(false)
244
    {
245
        if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
246
247
248
249
250
251
            unit = KoUnit::Inch;
        } else {
            unit = KoUnit::Centimeter;
        }
    }

252
253
254
255
256
257
    Private(const Private &rhs, KisDocument *q)
        : docInfo(new KoDocumentInfo(*rhs.docInfo, q))
        , unit(rhs.unit)
        , importExportManager(new KisImportExportManager(q))
        , mimeType(rhs.mimeType)
        , outputMimeType(rhs.outputMimeType)
258
        , autoSaveTimer(new QTimer(q))
259
260
        , undoStack(new UndoStack(q))
        , guidesConfig(rhs.guidesConfig)
261
        , mirrorAxisConfig(rhs.mirrorAxisConfig)
262
263
264
265
266
267
268
269
270
271
272
        , m_bAutoDetectedMime(rhs.m_bAutoDetectedMime)
        , m_url(rhs.m_url)
        , m_file(rhs.m_file)
        , modified(rhs.modified)
        , readwrite(rhs.readwrite)
        , firstMod(rhs.firstMod)
        , lastMod(rhs.lastMod)
        , nserver(new KisNameServer(*rhs.nserver))
        , preActivatedNode(0) // the node is from another hierarchy!
        , imageIdleWatcher(2000 /*ms*/)
        , assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document!
Dmitry Kazakov's avatar
Dmitry Kazakov committed
273
        , globalAssistantsColor(rhs.globalAssistantsColor)
274
        , paletteList(rhs.paletteList)
275
        , gridConfig(rhs.gridConfig)
276
277
        , savingLock(&savingMutex)
        , batchMode(rhs.batchMode)
278
    {
279
        // TODO: clone assistants
280
281
    }

282
283
284
285
286
    ~Private() {
        // Don't delete m_d->shapeController because it's in a QObject hierarchy.
        delete nserver;
    }

287
    KoDocumentInfo *docInfo = 0;
288
289
290

    KoUnit unit;

291
    KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options]
292
293
294

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

296
    QTimer *autoSaveTimer;
297
    QString lastErrorMessage; // see openFile()
298
    QString lastWarningMessage;
299
300
301
302
    int autoSaveDelay = 300; // in seconds, 0 to disable.
    bool modifiedAfterAutosave = false;
    bool isAutosaving = false;
    bool disregardAutosaveFailure = false;
303
    int autoSaveFailureCount = 0;
304

305
    KUndo2Stack *undoStack = 0;
306

307
    KisGuidesConfig guidesConfig;
308
    KisMirrorAxisConfig mirrorAxisConfig;
309

310
    bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself
311
    QUrl m_url; // local url - the one displayed to the user.
312
    QString m_file; // Local file - the only one the part implementation should deal with.
313

314
    QMutex savingMutex;
315

316
317
    bool modified = false;
    bool readwrite = false;
318

319
320
321
    QDateTime firstMod;
    QDateTime lastMod;

322
323
324
    KisNameServer *nserver;

    KisImageSP image;
325
326
    KisImageSP savingImage;

327
    KisNodeWSP preActivatedNode;
328
329
    KisShapeController* shapeController = 0;
    KoShapeController* koShapeController = 0;
330
331
332
    KisIdleWatcher imageIdleWatcher;
    QScopedPointer<KisSignalAutoConnection> imageIdleConnection;

333
    QList<KisPaintingAssistantSP> assistants;
334

Dmitry Kazakov's avatar
Dmitry Kazakov committed
335
336
    QColor globalAssistantsColor;

337
    KisSharedPtr<KisReferenceImagesLayer> referenceImagesLayer;
338

339
    QList<KoColorSet*> paletteList;
340

341
    KisGridConfig gridConfig;
342

343
344
    StdLockableWrapper<QMutex> savingLock;

345
    bool modifiedWhileSaving = false;
346
    QScopedPointer<KisDocument> backgroundSaveDocument;
347
    QPointer<KoUpdater> savingUpdater;
348
    QFuture<KisImportExportFilter::ConversionStatus> childSavingFuture;
349
    KritaUtils::ExportFileJob backgroundSaveJob;
350
351
352

    bool isRecovered = false;

353
354
    bool batchMode { false };

355
356
    void setImageAndInitIdleWatcher(KisImageSP _image) {
        image = _image;
357

358
359
360
361
        imageIdleWatcher.setTrackedImage(image);

        if (image) {
            imageIdleConnection.reset(
362
363
364
                        new KisSignalAutoConnection(
                            &imageIdleWatcher, SIGNAL(startedIdleMode()),
                            image.data(), SLOT(explicitRegenerateLevelOfDetail())));
365
366
        }
    }
367

368
    class StrippedSafeSavingLocker;
369
370
};

371
class KisDocument::Private::StrippedSafeSavingLocker {
372
public:
373
374
375
376
377
    StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
        : m_locked(false)
        , m_image(image)
        , m_savingLock(savingMutex)
        , m_imageLock(image, true)
378

379
    {
380
381
        /**
         * Initial try to lock both objects. Locking the image guards
382
         * us from any image composition threads running in the
383
384
385
386
387
388
         * 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.
         */
389
        m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
390
391

        if (!m_locked) {
392
393
            m_image->requestStrokeEnd();
            QApplication::processEvents();
394

395
396
            // one more try...
            m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
397
398
399
        }
    }

400
    ~StrippedSafeSavingLocker() {
401
402
403
404
405
        if (m_locked) {
            m_imageLock.unlock();
            m_savingLock.unlock();
        }
    }
406
407
408
409
410
411
412

    bool successfullyLocked() const {
        return m_locked;
    }

private:
    bool m_locked;
413
414
    KisImageSP m_image;
    StdLockableWrapper<QMutex> m_savingLock;
415
    KisImageBarrierLockAdapter m_imageLock;
416
417
};

418
KisDocument::KisDocument()
419
    : d(new Private(this))
420
{
421
    connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
422
    connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
423
    connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
424
425
426
427
428
    setObjectName(newObjectName());

    // preload the krita resources
    KisResourceServerProvider::instance();

429
    d->shapeController = new KisShapeController(this, d->nserver),
430
            d->koShapeController = new KoShapeController(0, d->shapeController),
431

432
            slotConfigChanged();
433
}
434

435
436
437
438
439
KisDocument::KisDocument(const KisDocument &rhs)
    : QObject(),
      d(new Private(*rhs.d, this))
{
    connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
440
    connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
441
    connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
442
443
444
    setObjectName(rhs.objectName());

    d->shapeController = new KisShapeController(this, d->nserver),
445
            d->koShapeController = new KoShapeController(0, d->shapeController),
446

447
            slotConfigChanged();
448

449
450
    // clone the image with keeping the GUIDs of the layers intact
    // NOTE: we expect the image to be locked!
451
    setCurrentImage(rhs.image()->clone(true), false);
452

453
454
455
456
457
458
    if (rhs.d->preActivatedNode) {
        // since we clone uuid's, we can use them for lacating new
        // nodes. Otherwise we would need to use findSymmetricClone()
        d->preActivatedNode =
                KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid());
    }
459
460
461
462
}

KisDocument::~KisDocument()
{
463
464
465
    // wait until all the pending operations are in progress
    waitForSavingToComplete();

466
467
468
469
470
471
    /**
     * Push a timebomb, which will try to release the memory after
     * the document has been deleted
     */
    KisPaintDevice::createMemoryReleaseObject()->deleteLater();

472
473
    d->autoSaveTimer->disconnect(this);
    d->autoSaveTimer->stop();
474

Halla Rempt's avatar
Halla Rempt committed
475
    delete d->importExportManager;
476
477
478

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

480
    delete d->koShapeController;
481
482
483

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

485
486
487
488
489
490
491
492
493
494
495
496
497
498
        /**
         * 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();
499

500
501
502
        // clear undo commands that can still point to the image
        d->undoStack->clear();
        d->image->waitForDone();
503

504
505
        KisImageWSP sanityCheckPointer = d->image;
        Q_UNUSED(sanityCheckPointer);
506

507
508
        // The following line trigger the deletion of the image
        d->image.clear();
509

510
511
512
        // check if the image has actually been deleted
        KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
    }
513
514
515
516
517
518
519
520
521
522

    delete d;
}

bool KisDocument::reload()
{
    // XXX: reimplement!
    return false;
}

523
KisDocument *KisDocument::clone()
524
{
525
    return new KisDocument(*this);
526
527
}

528
bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration)
529
{
530
    QFileInfo filePathInfo(job.filePath);
Halla Rempt's avatar
Halla Rempt committed
531

532
533
534
535
    if (filePathInfo.exists() && !filePathInfo.isWritable()) {
        slotCompleteSavingDocument(job,
                                   KisImportExportFilter::CreationError,
                                   i18n("%1 cannot be written to. Please save under a different name.", job.filePath));
536
537
538
        return false;
    }

539
    KisConfig cfg(true);
540
    if (cfg.backupFile() && filePathInfo.exists()) {
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564

        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:
            // 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) {
            KBackup::simpleBackupFile(job.filePath, backupDir, suffix);
        }
        else if (numOfBackupsKept > 2) {
            KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept);
        }
565
    }
566

567
    KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
568

569
    const QString actionName =
570
571
572
            job.flags & KritaUtils::SaveIsExporting ?
                i18n("Exporting Document...") :
                i18n("Saving Document...");
Halla Rempt's avatar
Halla Rempt committed
573

574
    bool started =
575
576
577
            initiateSavingInBackground(actionName,
                                       this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
                                       job, exportConfiguration);
578

579
580
    if (!started) {
        emit canceled(QString());
581
582
    }

583
    return started;
584
585
}

586
bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
587
{
588
    using namespace KritaUtils;
589

590
591
592
    SaveFlags flags = SaveIsExporting;
    if (showWarnings) {
        flags |= SaveShowWarnings;
593
594
    }

595
596
597
598
599
600
601
602
603
604
    KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8")
                        .arg(url.toLocalFile())
                        .arg(QString::fromLatin1(mimeType))
                        .arg(d->image->width())
                        .arg(d->image->height())
                        .arg(d->image->nlayers())
                        .arg(d->image->animationInterface()->totalLength())
                        .arg(d->image->animationInterface()->framerate())
                        .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration"));

605
606
607
608
    return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(),
                                                        mimeType,
                                                        flags),
                              exportConfiguration);
609

610
}
611

612
bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
613
614
{
    using namespace KritaUtils;
615

616
617
618
619
620
621
622
623
624
625
626
627
628
    KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers.  %6 frames, %7 framerate. Export configuration: %8")
                        .arg(_url.toLocalFile())
                        .arg(QString::fromLatin1(mimeType))
                        .arg(d->image->width())
                        .arg(d->image->height())
                        .arg(d->image->nlayers())
                        .arg(d->image->animationInterface()->totalLength())
                        .arg(d->image->animationInterface()->framerate())
                        .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")
                        .arg(url().toLocalFile()));


    return exportDocumentImpl(ExportFileJob(_url.toLocalFile(),
629
630
631
                                            mimeType,
                                            showWarnings ? SaveShowWarnings : SaveNone),
                              exportConfiguration);
632
633
}

634
bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
635
{
636
    return saveAs(url(), mimeType(), showWarnings, exportConfiguration);
637
638
}

639
640
641
642
643
644
645
646
647
QByteArray KisDocument::serializeToNativeByteArray()
{
    QByteArray byteArray;
    QBuffer buffer(&byteArray);

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

648
649
    Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
    if (!locker.successfullyLocked()) {
650
651
652
        return byteArray;
    }

653
654
    d->savingImage = d->image;

655
656
657
658
659
660
661
    if (filter->convert(this, &buffer) != KisImportExportFilter::OK) {
        qWarning() << "serializeToByteArray():: Could not export to our native format";
    }

    return byteArray;
}

662
void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
663
{
Igor Wect's avatar
Igor Wect committed
664
665
666
    if (status == KisImportExportFilter::UserCancelled)
        return;

667
668
    const QString fileName = QFileInfo(job.filePath).fileName();

669
    if (status != KisImportExportFilter::OK) {
luz paz's avatar
luz paz committed
670
        emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
671
672
673
                                    "Error during saving %1: %2",
                                    fileName,
                                    exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
674

675
676
        if (!fileBatchMode()) {
            const QString filePath = job.filePath;
677
            QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage)));
678
        }
679
680
    } else {
        if (!(job.flags & KritaUtils::SaveIsExporting)) {
681
682
683
            const QString existingAutoSaveBaseName = localFilePath();
            const bool wasRecovered = isRecovered();

684
685
686
687
688
            setUrl(QUrl::fromLocalFile(job.filePath));
            setLocalFilePath(job.filePath);
            setMimeType(job.mimeType);
            updateEditingTime(true);

689
            if (!d->modifiedWhileSaving) {
690
                /**
Yuri Chornoivan's avatar
Yuri Chornoivan committed
691
                 * If undo stack is already clean/empty, it doesn't emit any
692
693
694
695
696
697
698
699
700
                 * signals, so we might forget update document modified state
                 * (which was set, e.g. while recovering an autosave file)
                 */

                if (d->undoStack->isClean()) {
                    setModified(false);
                } else {
                    d->undoStack->setClean();
                }
701
            }
702
            setRecovered(false);
703
            removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered);
704
705
        }

706
707
        emit completed();
        emit sigSavingFinished();
708
709

        emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
710
    }
711
712
}

713
714
715
716
QByteArray KisDocument::mimeType() const
{
    return d->mimeType;
}
717

718
719
720
721
void KisDocument::setMimeType(const QByteArray & mimeType)
{
    d->mimeType = mimeType;
}
722

723
bool KisDocument::fileBatchMode() const
724
{
725
    return d->batchMode;
726
727
}

728
void KisDocument::setFileBatchMode(const bool batchMode)
729
{
730
    d->batchMode = batchMode;
731
}
732

733
KisDocument* KisDocument::lockAndCloneForSaving()
734
{
735
736
737
738
739
740
741
742
743
744
745
746
747
    // force update of all the asynchronous nodes before cloning
    QApplication::processEvents();
    KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root());

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

748
749
750
    Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
    if (!locker.successfullyLocked()) {
        return 0;
751
    }
752

753
    return new KisDocument(*this);
754
}
755

756
757
758
759
760
761
bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
{
    Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
    if (!locker.successfullyLocked()) {
        return false;
    }
762

763
    d->savingImage = d->image;
764

765
    const QString fileName = url.toLocalFile();
766

767
    KisImportExportFilter::ConversionStatus status =
768
            d->importExportManager->
769
            exportDocument(fileName, fileName, mimeType, false, exportConfiguration);
770

771
    d->savingImage = 0;
772

773
774
    return status == KisImportExportFilter::OK;
}
775

776
bool KisDocument::initiateSavingInBackground(const QString actionName,
777
778
779
                                             const QObject *receiverObject, const char *receiverMethod,
                                             const KritaUtils::ExportFileJob &job,
                                             KisPropertiesConfigurationSP exportConfiguration)
780
781
782
783
784
785
786
787
788
789
{
    return initiateSavingInBackground(actionName, receiverObject, receiverMethod,
                                      job, exportConfiguration, std::unique_ptr<KisDocument>());
}

bool KisDocument::initiateSavingInBackground(const QString actionName,
                                             const QObject *receiverObject, const char *receiverMethod,
                                             const KritaUtils::ExportFileJob &job,
                                             KisPropertiesConfigurationSP exportConfiguration,
                                             std::unique_ptr<KisDocument> &&optionalClonedDocument)
790
791
{
    KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false);
792

793
794
795
796
797
798
799
    QScopedPointer<KisDocument> clonedDocument;

    if (!optionalClonedDocument) {
        clonedDocument.reset(lockAndCloneForSaving());
    } else {
        clonedDocument.reset(optionalClonedDocument.release());
    }
800

801
    // we block saving until the current saving is finished!
802
803
804
    if (!clonedDocument || !d->savingMutex.tryLock()) {
        return false;
    }
805

806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
    auto waitForImage = [] (KisImageSP image) {
        KisMainWindow *window = KisPart::instance()->currentMainwindow();
        if (window) {
            if (window->viewManager()) {
                window->viewManager()->blockUntilOperationsFinishedForced(image);
            }
        }
    };

    {
        KisNodeSP newRoot = clonedDocument->image()->root();
        KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) {
            KisLayerUtils::forceAllDelayedNodesUpdate(newRoot);
            waitForImage(clonedDocument->image());
        }
    }

823
824
825
826
    KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) {
        waitForImage(clonedDocument->image());
    }

827
828
    KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false);
    KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false);
829
    d->backgroundSaveDocument.reset(clonedDocument.take());
830
    d->backgroundSaveJob = job;
831
    d->modifiedWhileSaving = false;
832

833
834
    if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
        d->backgroundSaveDocument->d->isAutosaving = true;
835
    }
836

837
    connect(d->backgroundSaveDocument.data(),
838
            SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus,QString)),
839
            this,
840
            SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus,QString)));
841
842


843
844
    connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
            receiverObject, receiverMethod, Qt::UniqueConnection);
845

846
    bool started =
847
848
849
850
851
852
            d->backgroundSaveDocument->startExportInBackground(actionName,
                                                               job.filePath,
                                                               job.filePath,
                                                               job.mimeType,
                                                               job.flags & KritaUtils::SaveShowWarnings,
                                                               exportConfiguration);
853

854
    if (!started) {
855
856
857
858
859
860
861
        // the state should have been deinitialized in slotChildCompletedSavingInBackground()

        KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) {
            d->backgroundSaveDocument.take()->deleteLater();
            d->savingMutex.unlock();
            d->backgroundSaveJob = KritaUtils::ExportFileJob();
        }
862
863
    }

864
    return started;
865
}
866
867


868
void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
869
870
871
872
{
    KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) {
        d->savingMutex.unlock();
        return;
873
874
    }

875
    KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument);
876

877
878
879
    if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
        d->backgroundSaveDocument->d->isAutosaving = false;
    }
880

881
882
    d->backgroundSaveDocument.take()->deleteLater();
    d->savingMutex.unlock();
883

884
885
886
    KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid());
    const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
    d->backgroundSaveJob = KritaUtils::ExportFileJob();
887

888
889
890
891
892
    KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3")
                        .arg(job.filePath)
                        .arg(QString::fromLatin1(job.mimeType))
                        .arg(status != KisImportExportFilter::OK ? exportErrorToUserMessage(status, errorMessage) : "OK"));

893
    emit sigCompleteBackgroundSaving(job, status, errorMessage);
894
895
}

896
void KisDocument::slotAutoSaveImpl(std::unique_ptr<KisDocument> &&optionalClonedDocument)
897
{
898
899
    if (!d->modified || !d->modifiedAfterAutosave) return;
    const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
900

901
902
    emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout);

903
    const bool hadClonedDocument = bool(optionalClonedDocument);
904
905
    bool started = false;

906
    if (d->image->isIdle() || hadClonedDocument) {
907
        started = initiateSavingInBackground(i18n("Autosaving..."),
908
                                             this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
909
                                             KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode),
910
911
                                             0,
                                             std::move(optionalClonedDocument));
912
913
914
    } else {
        emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout);
    }
915

916
917
918
919
920
921
922
923
924
925
926
927
928
    if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) {
        KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this);
        connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)),
                this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)),
                Qt::BlockingQueuedConnection);

        KisStrokeId strokeId = d->image->startStroke(stroke);
        d->image->endStroke(strokeId);

        setInfiniteAutoSaveInterval();

    } else if (!started) {
        setEmergencyAutoSaveInterval();
929
930
    } else {
        d->modifiedAfterAutosave = false;
931
    }
932
933
}

934
935
936
937
938
939
940
941
942
943
void KisDocument::slotAutoSave()
{
    slotAutoSaveImpl(std::unique_ptr<KisDocument>());
}

void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument)
{
    slotAutoSaveImpl(std::unique_ptr<KisDocument>(clonedDocument));
}

944
void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
945
{
946
    Q_UNUSED(job);
947

948
    const QString fileName = QFileInfo(job.filePath).fileName();
949

950
    if (status != KisImportExportFilter::OK) {
951
        setEmergencyAutoSaveInterval();
luz paz's avatar
luz paz committed
952
        emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
953
954
955
                                    "Error during autosaving %1: %2",
                                    fileName,
                                    exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
956
    } else {
957
        KisConfig cfg(true);
958
        d->autoSaveDelay = cfg.autoSaveInterval();
959

960
        if (!d->modifiedWhileSaving) {
961
962
            d->autoSaveTimer->stop(); // until the next change
            d->autoSaveFailureCount = 0;
963
        } else {
964
            setNormalAutoSaveInterval();
965
        }
966
967

        emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout);
968
    }
969
970
}

971
972
bool KisDocument::startExportInBackground(const QString &actionName,
                                          const QString &location,
973
974
975
976
                                          const QString &realLocation,
                                          const QByteArray &mimeType,
                                          bool showWarnings,
                                          KisPropertiesConfigurationSP exportConfiguration)
977
{
978
    d->savingImage = d->image;
979

980
    KisMainWindow *window = KisPart::instance()->currentMainwindow();
981
982
983
984
985
986
    if (window) {
        if (window->viewManager()) {
            d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName);
            d->importExportManager->setUpdater(d->savingUpdater);
        }
    }
987

Igor Wect's avatar
Igor Wect committed
988
    KisImportExportFilter::ConversionStatus initializationStatus;
989
    d->childSavingFuture =
990
991
992
993
994
995
            d->importExportManager->exportDocumentAsyc(location,
                                                       realLocation,
                                                       mimeType,
                                                       initializationStatus,
                                                       showWarnings,
                                                       exportConfiguration);
996

Igor Wect's avatar
Igor Wect committed
997
    if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) {
998
999
1000
        if (d->savingUpdater) {
            d->savingUpdater->cancel();
        }
For faster browsing, not all history is shown. View entire blame