Krita Source Code Documentation
Loading...
Searching...
No Matches
KisDocument.cpp
Go to the documentation of this file.
1/* This file is part of the Krita project
2 *
3 * SPDX-FileCopyrightText: 2014 Boudewijn Rempt <boud@valdyas.org>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8#include "KisMainWindow.h" // XXX: remove
9#include <QMessageBox>
10
11#include <KisMimeDatabase.h>
12
13#include <KoCanvasBase.h>
14#include <KoColor.h>
15#include <KoColorProfile.h>
16#include <KoColorSpaceEngine.h>
17#include <KoColorSpace.h>
19#include <KoDocumentInfoDlg.h>
20#include <KoDocumentInfo.h>
21#include <KoUnit.h>
22#include <KoID.h>
23#include <KoProgressProxy.h>
24#include <KoProgressUpdater.h>
25#include <KoSelection.h>
26#include <KoShape.h>
27#include <KoShapeController.h>
28#include <KoStore.h>
29#include <KoUpdater.h>
30#include <KoXmlWriter.h>
31#include <KoStoreDevice.h>
32#include <KoDialog.h>
35#include <KoMD5Generator.h>
36#include <KisResourceStorage.h>
37#include <KisResourceLocator.h>
38#include <KisResourceTypes.h>
42#include <KisResourceCacheDb.h>
43#include <KoEmbeddedResource.h>
44#include <KisUsageLogger.h>
45#include <klocalizedstring.h>
46#include <kis_debug.h>
47#include <kis_generator_layer.h>
50#include <kdesktopfile.h>
51#include <kconfiggroup.h>
52#include <KisBackup.h>
53#include <KisView.h>
54
55#include <QTextBrowser>
56#include <QApplication>
57#include <QBuffer>
58#include <QStandardPaths>
59#include <QDir>
60#include <QDomDocument>
61#include <QDomElement>
62#include <QFileInfo>
63#include <QImage>
64#include <QList>
65#include <QMutex>
66#include <QPainter>
67#include <QRect>
68#include <QScopedPointer>
69#include <QSize>
70#include <QStringList>
71#include <QtGlobal>
72#include <QTimer>
73#include <QWidget>
74#include <QFuture>
75#include <QFutureWatcher>
76#include <QUuid>
77
78// Krita Image
80#include <kis_config.h>
82#include <kis_group_layer.h>
83#include <kis_image.h>
84#include <kis_layer.h>
85#include <kis_name_server.h>
86#include <kis_paint_layer.h>
87#include <kis_painter.h>
88#include <kis_selection.h>
89#include <kis_fill_painter.h>
91#include <kis_idle_watcher.h>
94#include "kis_layer_utils.h"
95#include "kis_selection_mask.h"
96
97// Local
98#include "KisViewManager.h"
99#include "kis_clipboard.h"
101#include "canvas/kis_canvas2.h"
106#include "kis_node_manager.h"
107#include "KisPart.h"
108#include "KisApplication.h"
109#include "KisDocument.h"
111#include "KisView.h"
112#include "kis_grid_config.h"
113#include "kis_guides_config.h"
114#include "KisImageBarrierLock.h"
117
118#include <mutex>
119#include "kis_config_notifier.h"
122
123#include <kis_algebra_2d.h>
124#include <KisMirrorAxisConfig.h>
128
129// Define the protocol used here for embedded documents' URL
130// This used to "store" but QUrl didn't like it,
131// so let's simply make it "tar" !
132#define STORE_PROTOCOL "tar"
133// The internal path is a hack to make QUrl happy and for document children
134#define INTERNAL_PROTOCOL "intern"
135#define INTERNAL_PREFIX "intern:/"
136// Warning, keep it sync in koStore.cc
137
138#include <unistd.h>
139
140#ifdef Q_OS_MACOS
142#endif
143
144using namespace std;
145
146namespace {
147constexpr int errorMessageTimeout = 5000;
148constexpr int successMessageTimeout = 1000;
149}
150
151
152/**********************************************************
153 *
154 * KisDocument
155 *
156 **********************************************************/
157
158//static
160{
161 static int s_docIFNumber = 0;
162 QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
163 return name;
164}
165
166
167class UndoStack : public KUndo2Stack
168{
169public:
171 : KUndo2Stack(doc),
172 m_doc(doc)
173 {
174 }
175
176 void setIndex(int idx) override {
179 }
180
182 KisImageWSP image = this->image();
183 image->unlock();
184
190 while(!image->tryBarrierLock()) {
191 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
192 }
193 }
194
195 void undo() override {
198 }
199
200
201 void redo() override {
204 }
205
206private:
208 KisImageWSP currentImage = m_doc->image();
209 Q_ASSERT(currentImage);
210 return currentImage;
211 }
212
213 void setIndexImpl(int idx) {
214 KisImageWSP image = this->image();
216 if(image->tryBarrierLock()) {
218 image->unlock();
219 }
220 }
221
222 void undoImpl() {
223 KisImageWSP image = this->image();
225
227 return;
228 }
229
230 if(image->tryBarrierLock()) {
232 image->unlock();
233 }
234 }
235
236 void redoImpl() {
237 KisImageWSP image = this->image();
239
240 if(image->tryBarrierLock()) {
242 image->unlock();
243 }
244 }
245
255 if (m_recursionCounter > 0) return;
256
258
259 while (!m_postponedJobs.isEmpty()) {
260 PostponedJob job = m_postponedJobs.dequeue();
261 switch (job.type) {
263 setIndexImpl(job.index);
264 break;
266 redoImpl();
267 break;
269 undoImpl();
270 break;
271 }
272 }
273
275 }
276
277private:
279
281 enum Type {
282 Undo = 0,
285 };
287 int index = 0;
288 };
289 QQueue<PostponedJob> m_postponedJobs;
290
292};
293
294class Q_DECL_HIDDEN KisDocument::Private
295{
296public:
298 : q(_q)
299 , docInfo(new KoDocumentInfo(_q)) // deleted by QObject
300 , importExportManager(new KisImportExportManager(_q)) // deleted manually
301 , autoSaveTimer(new QTimer(_q))
302 , undoStack(new UndoStack(_q)) // deleted by QObject
303 , m_bAutoDetectedMime(false)
304 , modified(false)
305 , readwrite(true)
306 , autoSaveActive(true)
307 , firstMod(QDateTime::currentDateTime())
308 , lastMod(firstMod)
309 , nserver(new KisNameServer(1))
310 , imageIdleWatcher(2000 /*ms*/)
311 , globalAssistantsColor(KisConfig(true).defaultAssistantsColor())
312 , batchMode(false)
313 {
314 if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
315 unit = KoUnit::Inch;
316 } else {
317 unit = KoUnit::Centimeter;
318 }
319 connect(&imageIdleWatcher, SIGNAL(startedIdleMode()), q, SLOT(slotPerformIdleRoutines()));
320 }
321
322 Private(const Private &rhs, KisDocument *_q)
323 : q(_q)
324 , docInfo(new KoDocumentInfo(*rhs.docInfo, _q))
325 , importExportManager(new KisImportExportManager(_q))
326 , autoSaveTimer(new QTimer(_q))
327 , undoStack(new UndoStack(_q))
328 , colorHistory(rhs.colorHistory)
329 , nserver(new KisNameServer(*rhs.nserver))
330 , preActivatedNode(0) // the node is from another hierarchy!
331 , imageIdleWatcher(2000 /*ms*/)
332 {
333 copyFromImpl(rhs, _q, CONSTRUCT);
334 connect(&imageIdleWatcher, SIGNAL(startedIdleMode()), q, SLOT(slotPerformIdleRoutines()));
335 }
336
338 // Don't delete m_d->shapeController because it's in a QObject hierarchy.
339 delete nserver;
340 }
341
343 KoDocumentInfo *docInfo = 0;
344
346
347 KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options]
348
349 QByteArray mimeType; // The actual mimeType of the document
350 QByteArray outputMimeType; // The mimeType to use when saving
351
353 QString lastErrorMessage; // see openFile()
355
356 int autoSaveDelay = 300; // in seconds, 0 to disable.
357 bool modifiedAfterAutosave = false;
358 bool isAutosaving = false;
359 bool disregardAutosaveFailure = false;
360 int autoSaveFailureCount = 0;
361
362 KUndo2Stack *undoStack = 0;
363
366
367 bool m_bAutoDetectedMime = false; // whether the mimeType in the arguments was detected by the part itself
368 QString m_path; // local url - the one displayed to the user.
369 QString m_file; // Local file - the only one the part implementation should deal with.
370
372
373 bool modified = false;
374 bool readwrite = false;
375 bool autoSaveActive = true;
376
377 QDateTime firstMod;
378 QDateTime lastMod;
379
381
384
386 KisShapeController* shapeController = 0;
387 KoShapeController* koShapeController = 0;
389 QScopedPointer<KisSignalAutoConnection> imageIdleConnection;
390
392
395
397 qreal audioLevel = 1.0;
398
401
403
404 bool imageModifiedWithoutUndo = false;
405 bool modifiedWhileSaving = false;
406 QScopedPointer<KisDocument> backgroundSaveDocument;
411
412 bool isRecovered = false;
413
414 bool batchMode { false };
415 bool decorationsSyncingDisabled = false;
416 bool wasStorageAdded = false;
417 bool documentIsClosing = false;
418
419 // Resources saved in the .kra document
422
423 // Resources saved into other components of the kra file
426
428
430 image = _image;
431
432 imageIdleWatcher.setTrackedImage(image);
433 }
434
435 void copyFrom(const Private &rhs, KisDocument *q);
437
439 KisDocument* lockAndCloneImpl(bool fetchResourcesFromLayers);
440
441 void updateDocumentMetadataOnSaving(const QString &filePath, const QByteArray &mimeType);
442
445 class StrippedSafeSavingLocker;
446};
447
448
449void KisDocument::Private::syncDecorationsWrapperLayerState()
450{
451 if (!this->image || this->decorationsSyncingDisabled) return;
452
453 KisImageSP image = this->image;
454 KisDecorationsWrapperLayerSP decorationsLayer =
455 KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(image->root());
456
457 const bool needsDecorationsWrapper =
458 gridConfig.showGrid() || (guidesConfig.showGuides() && guidesConfig.hasGuides()) || !assistants.isEmpty();
459
460 struct SyncDecorationsWrapperStroke : public KisSimpleStrokeStrategy {
461 SyncDecorationsWrapperStroke(KisDocument *document, bool needsDecorationsWrapper)
462 : KisSimpleStrokeStrategy(QLatin1String("sync-decorations-wrapper"),
463 kundo2_noi18n("start-isolated-mode")),
464 m_document(document),
465 m_needsDecorationsWrapper(needsDecorationsWrapper)
466 {
470 }
471
472 void initStrokeCallback() override {
473 KisDecorationsWrapperLayerSP decorationsLayer =
474 KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(m_document->image()->root());
475
476 if (m_needsDecorationsWrapper && !decorationsLayer) {
477 m_document->image()->addNode(new KisDecorationsWrapperLayer(m_document));
478 } else if (!m_needsDecorationsWrapper && decorationsLayer) {
479 m_document->image()->removeNode(decorationsLayer);
480 }
481 }
482
483 private:
484 KisDocument *m_document = 0;
485 bool m_needsDecorationsWrapper = false;
486 };
487
488 KisStrokeId id = image->startStroke(new SyncDecorationsWrapperStroke(q, needsDecorationsWrapper));
489 image->endStroke(id);
490}
491
492void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q)
493{
494 copyFromImpl(rhs, q, KisDocument::REPLACE);
495}
496
497void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy)
498{
499 if (policy == REPLACE) {
500 delete docInfo;
501 }
502 docInfo = (new KoDocumentInfo(*rhs.docInfo, q));
503 unit = rhs.unit;
504 mimeType = rhs.mimeType;
505 outputMimeType = rhs.outputMimeType;
506
507 if (policy == REPLACE) {
508 q->setGuidesConfig(rhs.guidesConfig);
509 q->setMirrorAxisConfig(rhs.mirrorAxisConfig);
510 q->setModified(rhs.modified);
513 q->setStoryboardCommentList(rhs.m_storyboardCommentList);
514 q->setAudioTracks(rhs.audioTracks);
515 q->setAudioVolume(rhs.audioLevel);
516 q->setGridConfig(rhs.gridConfig);
517 } else {
518 // in CONSTRUCT mode, we cannot use the functions of KisDocument
519 // because KisDocument does not yet have a pointer to us.
520 guidesConfig = rhs.guidesConfig;
521 mirrorAxisConfig = rhs.mirrorAxisConfig;
522 modified = rhs.modified;
523 assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants);
524 m_storyboardItemList = StoryboardItem::cloneStoryboardItemList(rhs.m_storyboardItemList);
525 m_storyboardCommentList = rhs.m_storyboardCommentList;
526 audioTracks = rhs.audioTracks;
527 audioLevel = rhs.audioLevel;
528 gridConfig = rhs.gridConfig;
529 }
530 imageModifiedWithoutUndo = rhs.imageModifiedWithoutUndo;
531 m_bAutoDetectedMime = rhs.m_bAutoDetectedMime;
532 m_path = rhs.m_path;
533 m_file = rhs.m_file;
534 readwrite = rhs.readwrite;
535 autoSaveActive = rhs.autoSaveActive;
536 firstMod = rhs.firstMod;
537 lastMod = rhs.lastMod;
538 // XXX: the display properties will be shared between different snapshots
539 globalAssistantsColor = rhs.globalAssistantsColor;
540 batchMode = rhs.batchMode;
541
542
543 if (rhs.linkedResourceStorage) {
544 linkedResourceStorage = rhs.linkedResourceStorage->clone();
545 }
546
547 if (rhs.embeddedResourceStorage) {
548 embeddedResourceStorage = rhs.embeddedResourceStorage->clone();
549 }
550
551}
552
554public:
555 StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
556 : m_locked(false)
557 , m_image(image)
558 , m_savingLock(savingMutex)
559 , m_imageLock(image, std::defer_lock)
560
561 {
571 m_locked = std::try_lock(m_imageLock, *m_savingLock) < 0;
572
573 if (!m_locked) {
575 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
576
577 // one more try...
578 m_locked = std::try_lock(m_imageLock, *m_savingLock) < 0;
579 }
580 }
581
583 if (m_locked) {
584 m_imageLock.unlock();
585 m_savingLock->unlock();
586 }
587 }
588
589 bool successfullyLocked() const {
590 return m_locked;
591 }
592
593private:
594 Q_DISABLE_COPY_MOVE(StrippedSafeSavingLocker)
595
596 bool m_locked;
597 KisImageSP m_image;
598 QMutex *m_savingLock;
599 KisImageReadOnlyBarrierLock m_imageLock;
600};
601
602KisDocument::KisDocument(bool addStorage)
603 : d(new Private(this))
604{
605 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
606 connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
607 connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
608 setObjectName(newObjectName());
609
610#ifdef Q_OS_MACOS
612 if (bookmarkmngr->isSandboxed()) {
613 connect(this, SIGNAL(sigSavingFinished(const QString&)), bookmarkmngr, SLOT(slotCreateBookmark(const QString&)));
614 }
615#endif
616
617
618 if (addStorage) {
619 d->linkedResourcesStorageID = QUuid::createUuid().toString();
620 d->linkedResourceStorage.reset(new KisResourceStorage(d->linkedResourcesStorageID));
621 KisResourceLocator::instance()->addStorage(d->linkedResourcesStorageID, d->linkedResourceStorage);
622
623 d->embeddedResourcesStorageID = QUuid::createUuid().toString();
624 d->embeddedResourceStorage.reset(new KisResourceStorage(d->embeddedResourcesStorageID));
625 KisResourceLocator::instance()->addStorage(d->embeddedResourcesStorageID, d->embeddedResourceStorage);
626
627 d->wasStorageAdded = true;
628 }
629
630 // preload the krita resources
632
633 d->shapeController = new KisShapeController(d->nserver, d->undoStack, this);
634 d->koShapeController = new KoShapeController(0, d->shapeController);
635
636 slotConfigChanged();
637}
638
639KisDocument::KisDocument(const KisDocument &rhs, bool addStorage)
640 : QObject(),
641 d(new Private(*rhs.d, this))
642{
644
645 if (addStorage) {
646 KisResourceLocator::instance()->addStorage(d->linkedResourcesStorageID, d->linkedResourceStorage);
647 KisResourceLocator::instance()->addStorage(d->embeddedResourcesStorageID, d->embeddedResourceStorage);
648 d->wasStorageAdded = true;
649 }
650}
651
653{
654 d->documentIsClosing = true;
655
656 // wait until all the pending operations are in progress
658 d->imageIdleWatcher.setTrackedImage(0);
659
665
666 d->autoSaveTimer->disconnect(this);
667 d->autoSaveTimer->stop();
668
669 delete d->importExportManager;
670
671 // Despite being QObject they needs to be deleted before the image
672 delete d->shapeController;
673
674 delete d->koShapeController;
675
676 if (d->image) {
677 d->image->animationInterface()->blockBackgroundFrameGeneration();
678
679 d->image->notifyAboutToBeDeleted();
680
693 d->image->requestStrokeCancellation();
694 d->image->waitForDone();
695
696 // clear undo commands that can still point to the image
697 d->undoStack->clear();
698 d->image->waitForDone();
699
700 KisImageWSP sanityCheckPointer = d->image;
701 Q_UNUSED(sanityCheckPointer);
702
703 // The following line trigger the deletion of the image
704 d->image.clear();
705
706 // check if the image has actually been deleted
707 KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
708 }
709
710 if (d->wasStorageAdded) {
711 if (KisResourceLocator::instance()->hasStorage(d->linkedResourcesStorageID)) {
712 KisResourceLocator::instance()->removeStorage(d->linkedResourcesStorageID);
713 }
714 if (KisResourceLocator::instance()->hasStorage(d->embeddedResourcesStorageID)) {
715 KisResourceLocator::instance()->removeStorage(d->embeddedResourcesStorageID);
716 }
717 }
718
719 delete d;
720}
721
723{
724 return d->embeddedResourcesStorageID;
725}
726
728{
729 return d->linkedResourcesStorageID;
730}
731
733{
734 return new KisDocument(*this, addStorage);
735}
736
737bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting)
738{
739 QFileInfo filePathInfo(job.filePath);
740
741 if (filePathInfo.exists() && !filePathInfo.isWritable()) {
743 i18n("%1 cannot be written to. Please save under a different name.", job.filePath),
744 "");
745 return false;
746 }
747
748 KisConfig cfg(true);
749 if (cfg.backupFile() && filePathInfo.exists()) {
750
751 QString backupDir;
752
753 switch(cfg.readEntry<int>("backupfilelocation", 0)) {
754 case 1:
755 backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
756 break;
757 case 2:
758 backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
759 break;
760 default:
761#ifdef Q_OS_ANDROID
762 // We deal with URIs, there may or may not be a "directory"
764 QDir().mkpath(backupDir);
765#endif
766
767#ifdef Q_OS_MACOS
769 if (bookmarkmngr->isSandboxed()) {
770 // If the user does not have directory permission force backup
771 // files to be inside Container tmp
772 QUrl fileUrl = QUrl::fromLocalFile(job.filePath);
773 if( !bookmarkmngr->parentDirHasPermissions(fileUrl.path()) ) {
774 backupDir = QDir::tempPath();
775 }
776 }
777#endif
778
779 // Do nothing: the empty string is user file location
780 break;
781 }
782
783 int numOfBackupsKept = cfg.readEntry<int>("numberofbackupfiles", 1);
784 QString suffix = cfg.readEntry<QString>("backupfilesuffix", "~");
785
786 if (numOfBackupsKept == 1) {
787 if (!KisBackup::simpleBackupFile(job.filePath, backupDir, suffix)) {
788 qWarning() << "Failed to create simple backup file!" << job.filePath << backupDir << suffix;
789 KisUsageLogger::log(QString("Failed to create a simple backup for %1 in %2.")
790 .arg(job.filePath, backupDir.isEmpty()
791 ? "the same location as the file"
792 : backupDir));
793 slotCompleteSavingDocument(job, ImportExportCodes::ErrorWhileWriting, i18nc("Saving error message", "Failed to create a backup file"), "");
794 return false;
795 }
796 else {
797 KisUsageLogger::log(QString("Create a simple backup for %1 in %2.")
798 .arg(job.filePath, backupDir.isEmpty()
799 ? "the same location as the file"
800 : backupDir));
801 }
802 }
803 else if (numOfBackupsKept > 1) {
804 if (!KisBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept)) {
805 qWarning() << "Failed to create numbered backup file!" << job.filePath << backupDir << suffix;
806 KisUsageLogger::log(QString("Failed to create a numbered backup for %2.")
807 .arg(job.filePath, backupDir.isEmpty()
808 ? "the same location as the file"
809 : backupDir));
810 slotCompleteSavingDocument(job, ImportExportCodes::ErrorWhileWriting, i18nc("Saving error message", "Failed to create a numbered backup file"), "");
811 return false;
812 }
813 else {
814 KisUsageLogger::log(QString("Create a simple backup for %1 in %2.")
815 .arg(job.filePath, backupDir.isEmpty()
816 ? "the same location as the file"
817 : backupDir));
818 }
819 }
820 }
821
822 //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
823 if (job.mimeType.isEmpty()) {
825 slotCompleteSavingDocument(job, error, error.errorMessage(), "");
826 return false;
827
828 }
829
830 const QString actionName =
832 i18n("Exporting Document...") :
833 i18n("Saving Document...");
834
838 job, exportConfiguration, isAdvancedExporting);
839
841 QString errorShortLog;
842 QString errorMessage;
844
845 switch (result) {
847 errorShortLog = "another save operation is in progress";
848 errorMessage = i18n("Could not start saving %1. Wait until the current save operation has finished.", job.filePath);
849 errorCode = ImportExportCodes::Failure;
850 break;
852 errorShortLog = "failed to lock and clone the image";
853 errorMessage = i18n("Could not start saving %1. Image is busy", job.filePath);
854 errorCode = ImportExportCodes::Busy;
855 break;
857 errorShortLog = "failed to start background saving";
858 errorMessage = i18n("Could not start saving %1. Unknown failure has happened", job.filePath);
859 errorCode = ImportExportCodes::Failure;
860 break;
863 break;
865 // noop, not possible
866 break;
867 }
868
869 KisUsageLogger::log(QString("Failed to initiate saving %1 in background: %2").arg(job.filePath).arg(errorShortLog));
870
871 slotCompleteSavingDocument(job, errorCode,
873 "");
874 return false;
875 }
876
878}
879
880bool KisDocument::exportDocument(const QString &path, const QByteArray &mimeType, bool isAdvancedExporting, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
881{
882 using namespace KritaUtils;
883
884 SaveFlags flags = SaveIsExporting;
885 if (showWarnings) {
886 flags |= SaveShowWarnings;
887 }
888
889 KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 "
890 "framerate. Export configuration: %8")
891 .arg(path, QString::fromLatin1(mimeType), QString::number(d->image->width()),
892 QString::number(d->image->height()), QString::number(d->image->nlayers()),
893 QString::number(d->image->animationInterface()->totalLength()),
894 QString::number(d->image->animationInterface()->framerate()),
895 (exportConfiguration ? exportConfiguration->toXML() : "No configuration")));
896
898 mimeType,
899 flags),
900 exportConfiguration, isAdvancedExporting);
901}
902
903bool KisDocument::saveAs(const QString &_path, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
904{
905 using namespace KritaUtils;
906
907 KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, "
908 "%7 framerate. Export configuration: %8")
909 .arg(_path, QString::fromLatin1(mimeType), QString::number(d->image->width()),
910 QString::number(d->image->height()), QString::number(d->image->nlayers()),
911 QString::number(d->image->animationInterface()->totalLength()),
912 QString::number(d->image->animationInterface()->framerate()),
913 (exportConfiguration ? exportConfiguration->toXML() : "No configuration"),
914 path()));
915
916 // Check whether it's an existing resource were are saving to
917 if (resourceSavingFilter(_path, mimeType, exportConfiguration)) {
918 return true;
919 }
920
922 mimeType,
923 showWarnings ? SaveShowWarnings : SaveNone),
924 exportConfiguration);
925}
926
927bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
928{
929 return saveAs(path(), mimeType(), showWarnings, exportConfiguration);
930}
931
933{
934 QBuffer buffer;
935
937 filter->setBatchMode(true);
938 filter->setMimeType(nativeFormatMimeType());
939
940 Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
941 if (!locker.successfullyLocked()) {
942 return buffer.data();
943 }
944
945 d->savingImage = d->image;
946
947 if (!filter->convert(this, &buffer).isOk()) {
948 qWarning() << "serializeToByteArray():: Could not export to our native format";
949 }
950
951 return buffer.data();
952}
953
954class DlgLoadMessages : public QMessageBox
955{
956public:
957 DlgLoadMessages(const QString &title,
958 const QString &message,
959 const QStringList &warnings = {},
960 const QString &details = {})
961 : QMessageBox(QMessageBox::Warning, title, message, QMessageBox::Ok, qApp->activeWindow())
962 {
963 if (!details.isEmpty()) {
964 setInformativeText(details);
965 }
966 if (!warnings.isEmpty()) {
967 setDetailedText(warnings);
968 }
969 }
970
971private:
972 void setDetailedText(const QStringList &text)
973 {
974 QMessageBox::setDetailedText(text.first());
975
976 QTextEdit *messageBox = findChild<QTextEdit *>();
977
978 if (messageBox) {
979 messageBox->setAcceptRichText(true);
980
981 QString warning = "<html><body><ul>";
982 Q_FOREACH (const QString &i, text) {
983 warning += "\n<li>" + i + "</li>";
984 }
985 warning += "</ul></body></html>";
986
987 messageBox->setText(warning);
988 }
989 }
990};
991
992void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
993{
994 if (status.isCancelled())
995 return;
996
997 const QString fileName = QFileInfo(job.filePath).fileName();
998
999 if (!status.isOk()) {
1000 Q_EMIT statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
1001 "Error during saving %1: %2",
1002 fileName,
1003 errorMessage), errorMessageTimeout);
1004
1005
1006 if (!fileBatchMode()) {
1007 DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
1008 i18n("Could not save %1.", job.filePath),
1009 errorMessage.split("\n", Qt::SkipEmptyParts)
1010 + warningMessage.split("\n", Qt::SkipEmptyParts),
1011 status.errorMessage());
1012
1013 dlg.exec();
1014 }
1015 }
1016 else {
1017 if (!fileBatchMode() && !warningMessage.isEmpty()) {
1018
1019 QStringList reasons = warningMessage.split("\n", Qt::SkipEmptyParts);
1020
1021 DlgLoadMessages dlg(
1022 i18nc("@title:window", "Krita"),
1023 i18nc("dialog box shown to the user if there were warnings while saving the document, "
1024 "%1 is the file path",
1025 "%1 has been saved but is incomplete.",
1026 job.filePath),
1027 reasons,
1028 reasons.isEmpty()
1029 ? ""
1030 : i18nc("dialog box shown to the user if there were warnings while saving the document",
1031 "Some problems were encountered when saving."));
1032 dlg.exec();
1033 }
1034
1035
1036 if (!(job.flags & KritaUtils::SaveIsExporting)) {
1037 const QString existingAutoSaveBaseName = localFilePath();
1038 const bool wasRecovered = isRecovered();
1039
1040 d->updateDocumentMetadataOnSaving(job.filePath, job.mimeType);
1041
1042 removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered);
1043 }
1044
1045 Q_EMIT completed();
1046 Q_EMIT sigSavingFinished(job.filePath);
1047
1048 Q_EMIT statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
1049 }
1050}
1051
1052void KisDocument::Private::updateDocumentMetadataOnSaving(const QString &filePath, const QByteArray &mimeType)
1053{
1054 q->setPath(filePath);
1055 q->setLocalFilePath(filePath);
1056 q->setMimeType(mimeType);
1057 q->updateEditingTime(true);
1058
1059 QFileInfo fi(filePath);
1060 q->setReadWrite(fi.isWritable());
1061
1062 if (!modifiedWhileSaving) {
1069 if (undoStack->isClean()) {
1070 q->setModified(false);
1071 } else {
1072 imageModifiedWithoutUndo = false;
1073 undoStack->setClean();
1074 }
1075 }
1076 q->setRecovered(false);
1077}
1078
1079QByteArray KisDocument::mimeType() const
1080{
1081 return d->mimeType;
1082}
1083
1084void KisDocument::setMimeType(const QByteArray & mimeType)
1085{
1086 d->mimeType = mimeType;
1087}
1088
1090{
1091 return d->batchMode;
1092}
1093
1094void KisDocument::setFileBatchMode(const bool batchMode)
1095{
1096 d->batchMode = batchMode;
1097}
1098
1099void KisDocument::Private::uploadLinkedResourcesFromLayersToStorage()
1100{
1105
1106 KisDocument *doc = q;
1107
1109 [doc] (KisNodeSP node) {
1110 if (KisNodeFilterInterface *layer = dynamic_cast<KisNodeFilterInterface*>(node.data())) {
1111 KisFilterConfigurationSP filterConfig = layer->filter();
1112 if (!filterConfig) return;
1113
1114 QList<KoResourceLoadResult> linkedResources = filterConfig->linkedResources(KisGlobalResourcesInterface::instance());
1115
1116 Q_FOREACH (const KoResourceLoadResult &result, linkedResources) {
1117 KIS_SAFE_ASSERT_RECOVER(result.type() != KoResourceLoadResult::EmbeddedResource) { continue; }
1118
1119 KoResourceSP resource = result.resource();
1120
1121 if (!resource) {
1122 qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to fetch a resource" << result.signature();
1123 continue;
1124 }
1125
1126 QBuffer buf;
1127 buf.open(QBuffer::WriteOnly);
1128
1129 KisResourceModel model(resource->resourceType().first);
1130 bool res = model.exportResource(resource, &buf);
1131
1132 buf.close();
1133
1134 if (!res) {
1135 qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to export resource" << result.signature();
1136 continue;
1137 }
1138
1139 buf.open(QBuffer::ReadOnly);
1140
1141 res = doc->d->linkedResourceStorage->importResource(resource->resourceType().first + "/" + resource->filename(), &buf);
1142
1143 buf.close();
1144
1145 if (!res) {
1146 qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to import resource" << result.signature();
1147 continue;
1148 }
1149 }
1150
1151 }
1152 });
1153}
1154
1155KisDocument *KisDocument::Private::lockAndCloneImpl(bool fetchResourcesFromLayers)
1156{
1157 // force update of all the asynchronous nodes before cloning
1158 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1160
1162 if (window) {
1163 if (window->viewManager()) {
1164 if (!window->viewManager()->blockUntilOperationsFinished(image)) {
1165 return 0;
1166 }
1167 }
1168 }
1169
1170 Private::StrippedSafeSavingLocker locker(&savingMutex, image);
1171 if (!locker.successfullyLocked()) {
1172 return 0;
1173 }
1174
1175 KisDocument *doc = new KisDocument(*this->q, false);
1176
1177 if (fetchResourcesFromLayers) {
1178 doc->d->uploadLinkedResourcesFromLayersToStorage();
1179 }
1180
1181 return doc;
1182}
1183
1185{
1186 return d->lockAndCloneImpl(true);
1187}
1188
1190{
1191 return d->lockAndCloneImpl(false);
1192}
1193
1198
1200{
1201 if (policy == REPLACE) {
1202 d->decorationsSyncingDisabled = true;
1203 d->copyFrom(*(rhs.d), this);
1204 d->decorationsSyncingDisabled = false;
1205
1206 d->undoStack->clear();
1207 } else {
1208 // in CONSTRUCT mode, d should be already initialized
1209 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
1210 connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
1211 connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
1212
1213 d->shapeController = new KisShapeController(d->nserver, d->undoStack, this);
1214 d->koShapeController = new KoShapeController(0, d->shapeController);
1215 }
1216
1217 setObjectName(rhs.objectName());
1218
1220
1221 if (rhs.d->image) {
1222 if (policy == REPLACE) {
1223 d->image->barrierLock(/* readOnly = */ false);
1224 rhs.d->image->barrierLock(/* readOnly = */ true);
1225 d->image->copyFromImage(*(rhs.d->image));
1226 d->image->unlock();
1227 rhs.d->image->unlock();
1228
1229 setCurrentImage(d->image, /* forceInitialUpdate = */ true);
1230 } else {
1231 // clone the image with keeping the GUIDs of the layers intact
1232 // NOTE: we expect the image to be locked!
1233 setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false);
1234 }
1235 }
1236
1237 if (policy == REPLACE) {
1238 d->syncDecorationsWrapperLayerState();
1239 }
1240
1241 if (rhs.d->preActivatedNode) {
1242 QQueue<KisNodeSP> linearizedNodes;
1243 KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(),
1244 [&linearizedNodes](KisNodeSP node) {
1245 linearizedNodes.enqueue(node);
1246 });
1248 [&linearizedNodes, &rhs, this](KisNodeSP node) {
1249 KisNodeSP refNode = linearizedNodes.dequeue();
1250 if (rhs.d->preActivatedNode.data() == refNode.data()) {
1251 d->preActivatedNode = node;
1252 }
1253 });
1254 }
1255
1256 // reinitialize references' signal connection
1257 KisReferenceImagesLayerSP referencesLayer = this->referenceImagesLayer();
1258 if (referencesLayer) {
1259 d->referenceLayerConnections.clear();
1260 d->referenceLayerConnections.addConnection(
1261 referencesLayer, SIGNAL(sigUpdateCanvas(QRectF)),
1262 this, SIGNAL(sigReferenceImagesChanged()));
1263
1264 Q_EMIT sigReferenceImagesLayerChanged(referencesLayer);
1265 Q_EMIT sigReferenceImagesChanged();
1266 }
1267
1268 KisDecorationsWrapperLayerSP decorationsLayer =
1269 KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(d->image->root());
1270 if (decorationsLayer) {
1271 decorationsLayer->setDocument(this);
1272 }
1273
1274
1275 if (policy == REPLACE) {
1276 setModified(true);
1277 }
1278}
1279
1280bool KisDocument::exportDocumentSync(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
1281{
1282 {
1289 Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
1290 if (!locker.successfullyLocked()) {
1291 return false;
1292 }
1293 }
1294
1295 d->savingImage = d->image;
1296
1298 d->importExportManager->
1299 exportDocument(path, path, mimeType, false, exportConfiguration);
1300
1301 d->savingImage = 0;
1302
1303 return status.isOk();
1304}
1305
1306
1308 const QObject *receiverObject, const char *receiverMethod,
1309 const KritaUtils::ExportFileJob &job,
1310 KisPropertiesConfigurationSP exportConfiguration,bool isAdvancedExporting)
1311{
1312 return initiateSavingInBackground(actionName, receiverObject, receiverMethod,
1313 job, exportConfiguration, std::unique_ptr<KisDocument>(), isAdvancedExporting);
1314}
1315
1317 const QObject *receiverObject, const char *receiverMethod,
1318 const KritaUtils::ExportFileJob &job,
1319 KisPropertiesConfigurationSP exportConfiguration,
1320 std::unique_ptr<KisDocument> &&optionalClonedDocument,bool isAdvancedExporting)
1321{
1323
1324 QScopedPointer<KisDocument> clonedDocument;
1325
1326 if (!optionalClonedDocument) {
1327 clonedDocument.reset(lockAndCloneForSaving());
1328 } else {
1329 clonedDocument.reset(optionalClonedDocument.release());
1330 }
1331
1332 if (!d->savingMutex.tryLock()){
1334 }
1335
1340 std::unique_lock<QMutex> savingMutexLock(d->savingMutex, std::adopt_lock);
1341
1342 if (!clonedDocument) {
1344 }
1345
1346 auto waitForImage = [] (KisImageSP image) {
1348 if (window) {
1349 if (window->viewManager()) {
1351 }
1352 }
1353 };
1354
1355 {
1356 KisNodeSP newRoot = clonedDocument->image()->root();
1359 waitForImage(clonedDocument->image());
1360 }
1361 }
1362
1363 if (clonedDocument->image()->hasOverlaySelectionMask()) {
1364 clonedDocument->image()->setOverlaySelectionMask(0);
1365 waitForImage(clonedDocument->image());
1366 }
1367
1368 KisConfig cfg(true);
1369 if (cfg.trimKra()) {
1370 clonedDocument->image()->cropImage(clonedDocument->image()->bounds());
1371 clonedDocument->image()->purgeUnusedData(false);
1372 waitForImage(clonedDocument->image());
1373 }
1374
1375 KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) {
1376 waitForImage(clonedDocument->image());
1377 }
1378
1381
1391 savingMutexLock.release();
1392
1393 d->backgroundSaveDocument.reset(clonedDocument.take());
1394 d->backgroundSaveJob = job;
1395 d->modifiedWhileSaving = false;
1396
1397 if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
1398 d->backgroundSaveDocument->d->isAutosaving = true;
1399 }
1400
1401 connect(d->backgroundSaveDocument.data(),
1402 SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString, QString)),
1403 this,
1405
1406
1407 connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString, QString)),
1408 receiverObject, receiverMethod, Qt::UniqueConnection);
1409
1411 d->backgroundSaveDocument->startExportInBackground(actionName,
1412 job.filePath,
1413 job.filePath,
1414 job.mimeType,
1416 exportConfiguration, isAdvancedExporting);
1417 if (!error.isOk()) {
1418 // the state should have been deinitialized in slotChildCompletedSavingInBackground()
1419 KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) {
1420 d->backgroundSaveDocument.take()->deleteLater();
1421 d->savingMutex.unlock();
1422 d->backgroundSaveJob = KritaUtils::ExportFileJob();
1423 }
1424 if (error.isCancelled()) {
1426 }
1428 }
1429
1431}
1432
1433
1434void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
1435{
1437
1443 std::unique_lock<QMutex> savingMutexLock(d->savingMutex, std::adopt_lock);
1444
1445 KIS_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument);
1446
1447 if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
1448 d->backgroundSaveDocument->d->isAutosaving = false;
1449 }
1450
1451 d->backgroundSaveDocument.take()->deleteLater();
1452
1453 KIS_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid());
1454
1455 const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
1456 d->backgroundSaveJob = KritaUtils::ExportFileJob();
1457
1458 // unlock at the very end
1459 savingMutexLock.unlock();
1460
1461 QFileInfo fi(job.filePath);
1462 KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3. Warning: %4. Size: %5")
1463 .arg(job.filePath, QString::fromLatin1(job.mimeType),
1464 (!status.isOk() ? errorMessage : "OK"), warningMessage,
1465 QString::number(fi.size())));
1466
1468}
1469
1470void KisDocument::slotAutoSaveImpl(std::unique_ptr<KisDocument> &&optionalClonedDocument)
1471{
1472 if (!d->modified || !d->modifiedAfterAutosave) return;
1473 const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
1474
1475 Q_EMIT statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout);
1476
1477 KisUsageLogger::log(QString("Autosaving: %1").arg(autoSaveFileName));
1478
1479 const bool hadClonedDocument = bool(optionalClonedDocument);
1481
1482 if (d->image->isIdle() || hadClonedDocument) {
1483 result = initiateSavingInBackground(i18n("Autosaving..."),
1486 0,
1487 std::move(optionalClonedDocument));
1488 } else {
1489 Q_EMIT statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout);
1490 }
1491
1492 if (result != KritaUtils::BackgroudSavingStartResult::Success && !hadClonedDocument && d->autoSaveFailureCount >= 3) {
1494 connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)),
1496 Qt::BlockingQueuedConnection);
1497 connect(stroke, SIGNAL(sigCloningCancelled()),
1498 this, SLOT(slotDocumentCloningCancelled()),
1499 Qt::BlockingQueuedConnection);
1500
1501 KisStrokeId strokeId = d->image->startStroke(stroke);
1502 d->image->endStroke(strokeId);
1503
1505
1508 } else {
1509 d->modifiedAfterAutosave = false;
1510 }
1511}
1512
1513bool KisDocument::resourceSavingFilter(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
1514{
1515 if (QFileInfo(path).absolutePath().startsWith(KisResourceLocator::instance()->resourceLocationBase())) {
1516
1517 QStringList pathParts = QFileInfo(path).absolutePath().split('/');
1518 if (pathParts.size() > 0) {
1519 QString resourceType = pathParts.last();
1520 if (KisResourceLoaderRegistry::instance()->resourceTypes().contains(resourceType)) {
1521
1522 KisResourceModel model(resourceType);
1524
1525 QString tempFileName = QDir::tempPath() + "/" + QFileInfo(path).fileName();
1526
1527 if (QFileInfo(path).exists()) {
1528
1529 int outResourceId;
1530 KoResourceSP res;
1531 if (KisResourceCacheDb::getResourceIdFromVersionedFilename(QFileInfo(path).fileName(), resourceType, "", outResourceId)) {
1532 res = model.resourceForId(outResourceId);
1533 }
1534
1535 if (res) {
1536 d->modifiedWhileSaving = false;
1537
1538 if (!exportConfiguration) {
1539 QScopedPointer<KisImportExportFilter> filter(
1541 if (filter) {
1542 exportConfiguration = filter->defaultConfiguration(nativeFormatMimeType(), mimeType);
1543 }
1544 }
1545
1546 if (exportConfiguration) {
1547 // make sure the name of the resource doesn't change
1548 exportConfiguration->setProperty("name", res->name());
1549 }
1550
1551 if (exportDocumentSync(tempFileName, mimeType, exportConfiguration)) {
1552 QFile f2(tempFileName);
1553 f2.open(QFile::ReadOnly);
1554
1555 QByteArray ba = f2.readAll();
1556
1557 QBuffer buf(&ba);
1558 buf.open(QBuffer::ReadOnly);
1559
1560
1561
1562 if (res->loadFromDevice(&buf, KisGlobalResourcesInterface::instance())) {
1563 if (model.updateResource(res)) {
1564 const QString filePath =
1566
1567 d->updateDocumentMetadataOnSaving(filePath, mimeType);
1568
1569 return true;
1570 }
1571 }
1572 }
1573 }
1574 }
1575 else {
1576 d->modifiedWhileSaving = false;
1577 if (exportDocumentSync(tempFileName, mimeType, exportConfiguration)) {
1578 KoResourceSP res = model.importResourceFile(tempFileName, false);
1579 if (res) {
1580 const QString filePath =
1582
1583 d->updateDocumentMetadataOnSaving(filePath, mimeType);
1584
1585 return true;
1586 }
1587 }
1588 }
1589 }
1590 }
1591 }
1592 return false;
1593}
1594
1596{
1597 slotAutoSaveImpl(std::unique_ptr<KisDocument>());
1598}
1599
1601{
1602 slotAutoSaveImpl(std::unique_ptr<KisDocument>(clonedDocument));
1603}
1604
1609
1611{
1612 d->image->explicitRegenerateLevelOfDetail();
1613
1614
1618
1619 // d->image->purgeUnusedData(true);
1620}
1621
1622void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
1623{
1624 Q_UNUSED(job);
1625 Q_UNUSED(warningMessage);
1626
1627 const QString fileName = QFileInfo(job.filePath).fileName();
1628
1629 if (!status.isOk()) {
1631 Q_EMIT statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
1632 "Error during autosaving %1: %2",
1633 fileName,
1634 exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
1635 } else {
1636 KisConfig cfg(true);
1637 d->autoSaveDelay = cfg.autoSaveInterval();
1638
1639 if (!d->modifiedWhileSaving) {
1640 d->autoSaveTimer->stop(); // until the next change
1641 d->autoSaveFailureCount = 0;
1642 } else {
1644 }
1645
1646 Q_EMIT statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout);
1647 }
1648}
1649
1651 const QString &location,
1652 const QString &realLocation,
1653 const QByteArray &mimeType,
1654 bool showWarnings,
1655 KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting)
1656{
1657 d->savingImage = d->image;
1658
1660 if (window) {
1661 if (window->viewManager()) {
1662 d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName);
1663 d->importExportManager->setUpdater(d->savingUpdater);
1664 }
1665 }
1666
1667 KisImportExportErrorCode initializationStatus(ImportExportCodes::OK);
1668 d->childSavingFuture =
1669 d->importExportManager->exportDocumentAsync(location,
1670 realLocation,
1671 mimeType,
1672 initializationStatus,
1673 showWarnings,
1674 exportConfiguration,
1675 isAdvancedExporting);
1676
1677 if (!initializationStatus.isOk()) {
1678 if (d->savingUpdater) {
1679 d->savingUpdater->cancel();
1680 }
1681 d->savingImage.clear();
1682 Q_EMIT sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage(), "");
1683 return initializationStatus;
1684 }
1685
1686 typedef QFutureWatcher<KisImportExportErrorCode> StatusWatcher;
1687 StatusWatcher *watcher = new StatusWatcher();
1688 watcher->setFuture(d->childSavingFuture);
1689
1690 connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground()));
1691 connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
1692
1693 return initializationStatus;
1694}
1695
1697{
1698 KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) {
1700 return;
1701 }
1702
1703 KisImportExportErrorCode status = d->childSavingFuture.result();
1704 QString errorMessage = status.errorMessage();
1705 QString warningMessage = d->lastWarningMessage;
1706
1707 if (!d->lastErrorMessage.isEmpty()) {
1709 errorMessage = d->lastErrorMessage;
1710 } else {
1711 errorMessage += "\n" + d->lastErrorMessage;
1712 }
1713 }
1714
1715 d->savingImage.clear();
1716 d->childSavingFuture = QFuture<KisImportExportErrorCode>();
1717 d->lastErrorMessage.clear();
1718 d->lastWarningMessage.clear();
1719
1720 if (d->savingUpdater) {
1721 d->savingUpdater->setProgress(100);
1722 }
1723
1725}
1726
1727void KisDocument::setReadWrite(bool readwrite)
1728{
1729 const bool changed = readwrite != d->readwrite;
1730
1731 d->readwrite = readwrite;
1732
1733 if (changed) {
1735 }
1736}
1737
1738void KisDocument::setAutoSaveActive(bool autoSaveActive)
1739{
1740 const bool changed = autoSaveActive != d->autoSaveActive;
1741
1742 if (changed) {
1743 d->autoSaveActive = autoSaveActive;
1745 }
1746}
1747
1749{
1750 if (isReadWrite() && delay > 0 && d->autoSaveActive) {
1751 d->autoSaveTimer->start(delay * 1000);
1752 } else {
1753 d->autoSaveTimer->stop();
1754 }
1755}
1756
1758{
1759 setAutoSaveDelay(d->autoSaveDelay);
1760 d->autoSaveFailureCount = 0;
1761}
1762
1764{
1765 const int emergencyAutoSaveInterval = 10; /* sec */
1766 setAutoSaveDelay(emergencyAutoSaveInterval);
1767 d->autoSaveFailureCount++;
1768}
1769
1774
1776{
1777 return d->autoSaveActive;
1778}
1779
1781{
1782 return d->docInfo;
1783}
1784
1786{
1787 return d->modified;
1788}
1789
1790QPixmap KisDocument::generatePreview(const QSize& size)
1791{
1792 KisImageSP image = d->image;
1793 if (d->savingImage) image = d->savingImage;
1794
1795 if (image) {
1796 QRect bounds = image->bounds();
1797 QSize originalSize = bounds.size();
1798 // QSize may round down one dimension to zero on extreme aspect rations, so ensure 1px minimum
1799 QSize newSize = originalSize.scaled(size, Qt::KeepAspectRatio).expandedTo({1, 1});
1800
1801 bool pixelArt = false;
1802 // determine if the image is pixel art or not
1803 if (originalSize.width() < size.width() && originalSize.height() < size.height()) {
1804 // the image must be smaller than the requested preview
1805 // the scale must be integer
1806 if (newSize.height()%originalSize.height() == 0 && newSize.width()%originalSize.width() == 0) {
1807 pixelArt = true;
1808 }
1809 }
1810
1811 QPixmap px;
1812 if (pixelArt) {
1813 // do not scale while converting (because it uses Bicubic)
1814 QImage original = image->convertToQImage(originalSize, 0);
1815 // scale using FastTransformation, which is probably Nearest neighbour, suitable for pixel art
1816 QImage scaled = original.scaled(newSize, Qt::KeepAspectRatio, Qt::FastTransformation);
1817 px = QPixmap::fromImage(scaled);
1818 } else {
1819 px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
1820 }
1821 if (px.size() == QSize(0,0)) {
1822 px = QPixmap(newSize);
1823 QPainter gc(&px);
1824 QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
1825 gc.fillRect(px.rect(), checkBrush);
1826 gc.end();
1827 }
1828 return px;
1829 }
1830 return QPixmap(size);
1831}
1832
1833QString KisDocument::generateAutoSaveFileName(const QString & path) const
1834{
1835 QString retval;
1836
1837 // Using the extension allows to avoid relying on the mime magic when opening
1838 const QString extension (".kra");
1839 QString prefix = KisConfig(true).readEntry<bool>("autosavefileshidden") ? QString(".") : QString();
1840 QRegularExpression autosavePattern1("^\\..+-autosave.kra$");
1841 QRegularExpression autosavePattern2("^.+-autosave.kra$");
1842
1843 QFileInfo fi(path);
1844 QString dir = fi.absolutePath();
1845
1846#ifdef Q_OS_ANDROID
1847 // URIs may or may not have a directory backing them, so we save to our default autosave location
1848 if (path.startsWith("content://")) {
1850 QDir().mkpath(dir);
1851 }
1852#endif
1853
1854 QString filename = fi.fileName();
1855
1856 if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) {
1857 // Never saved?
1858 retval = QString("%1%2%3%4-%5-%6-autosave%7")
1860 .arg('/')
1861 .arg(prefix)
1862 .arg("krita")
1863 .arg(qApp->applicationPid())
1864 .arg(objectName())
1865 .arg(extension);
1866 } else {
1867 // Beware: don't reorder arguments
1868 // otherwise in case of filename = '1-file.kra' it will become '.-file.kra-autosave.kra' instead of '.1-file.kra-autosave.kra'
1869 retval = QString("%1%2%3%4-autosave%5").arg(dir).arg('/').arg(prefix).arg(filename).arg(extension);
1870 }
1871
1872 //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
1873 return retval;
1874}
1875
1876bool KisDocument::importDocument(const QString &_path)
1877{
1878 bool ret;
1879
1880 dbgUI << "path=" << _path;
1881
1882 // open...
1883 ret = openPath(_path);
1884
1885 // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
1886 // File --> Import
1887 if (ret) {
1888 dbgUI << "success, resetting url";
1889 resetPath();
1890 }
1891
1892 return ret;
1893}
1894
1895
1896bool KisDocument::openPath(const QString &_path, OpenFlags flags)
1897{
1898 dbgUI << "path=" << _path;
1899 d->lastErrorMessage.clear();
1900
1901 // Reimplemented, to add a check for autosave files and to improve error reporting
1902 if (_path.isEmpty()) {
1903 d->lastErrorMessage = i18n("Malformed Path\n%1", _path); // ## used anywhere ?
1904 return false;
1905 }
1906
1907 QString path = _path;
1908 QString original = "";
1909 bool autosaveOpened = false;
1910 if (!fileBatchMode()) {
1911 QString file = path;
1912 QString asf = generateAutoSaveFileName(file);
1913 if (QFile::exists(asf)) {
1914 KisApplication *kisApp = static_cast<KisApplication*>(qApp);
1915 kisApp->hideSplashScreen();
1916 //qDebug() <<"asf=" << asf;
1917 // ## TODO compare timestamps ?
1918 KisRecoverNamedAutosaveDialog dlg(0, file, asf);
1919 dlg.exec();
1920 int res = dlg.result();
1921
1922 switch (res) {
1924 original = file;
1925 path = asf;
1926 autosaveOpened = true;
1927 break;
1929 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
1930 QFile::remove(asf);
1931 break;
1932 default: // Cancel
1933 return false;
1934 }
1935 }
1936 }
1937
1938 bool ret = openPathInternal(path);
1939
1940 if (autosaveOpened || flags & RecoveryFile) {
1941 setReadWrite(true); // enable save button
1942 setModified(true);
1943 setRecovered(true);
1944
1945 setPath(original); // since it was an autosave, it will be a local file
1946 setLocalFilePath(original);
1947 }
1948 else {
1949 if (ret) {
1950
1951 if (!(flags & DontAddToRecent)) {
1952 KisPart::instance()->addRecentURLToAllMainWindows(QUrl::fromLocalFile(_path));
1953 }
1954
1955 QFileInfo fi(_path);
1956 setReadWrite(fi.isWritable());
1957 }
1958
1959 setRecovered(false);
1960 }
1961
1962 return ret;
1963}
1964
1966{
1967 //dbgUI <<"for" << localFilePath();
1968 if (!QFile::exists(localFilePath()) && !fileBatchMode()) {
1969 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
1970 return false;
1971 }
1972
1973 QString filename = localFilePath();
1974 QString typeName = mimeType();
1975
1976 if (typeName.isEmpty()) {
1977 typeName = KisMimeDatabase::mimeTypeForFile(filename);
1978 }
1979
1980 // Allow to open backup files, don't keep the mimeType application/x-trash.
1981 if (typeName == "application/x-trash") {
1982 QString path = filename;
1983 while (path.length() > 0) {
1984 path.chop(1);
1985 typeName = KisMimeDatabase::mimeTypeForFile(path);
1986 //qDebug() << "\t" << path << typeName;
1987 if (!typeName.isEmpty()) {
1988 break;
1989 }
1990 }
1991 //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
1992 }
1993 dbgUI << localFilePath() << "type:" << typeName;
1994
1996 KoUpdaterPtr updater;
1997 if (window && window->viewManager()) {
1998 updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
1999 d->importExportManager->setUpdater(updater);
2000 }
2001
2002 KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName);
2003
2004 if (!status.isOk()) {
2005 if (window && window->viewManager()) {
2006 updater->cancel();
2007 }
2008 QString msg = status.errorMessage();
2009 KisUsageLogger::log(QString("Loading %1 failed: %2").arg(prettyPath(), msg));
2010
2011 if (!msg.isEmpty() && !fileBatchMode()) {
2012 DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
2013 i18n("Could not open %1.", prettyPath()),
2014 errorMessage().split("\n", Qt::SkipEmptyParts)
2015 + warningMessage().split("\n", Qt::SkipEmptyParts),
2016 msg);
2017
2018 dlg.exec();
2019 }
2020 return false;
2021 }
2022 else if (!warningMessage().isEmpty() && !fileBatchMode()) {
2023 DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
2024 i18n("There were problems opening %1.", prettyPath()),
2025 warningMessage().split("\n", Qt::SkipEmptyParts));
2026
2027 dlg.exec();
2028 setPath(QString());
2029 }
2030
2031 setMimeTypeAfterLoading(typeName);
2032 d->syncDecorationsWrapperLayerState();
2033 Q_EMIT sigLoadingFinished();
2034
2035 undoStack()->clear();
2036
2037 return true;
2038}
2039
2041{
2042 if (!d->modified || !d->modifiedAfterAutosave)
2043 return;
2044
2045 const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
2046
2047 bool started = exportDocumentSync(autoSaveFileName, nativeFormatMimeType());
2048
2049 if (started)
2050 {
2051 d->modifiedAfterAutosave = false;
2052 dbgAndroid << "autoSaveOnPause successful";
2053 }
2054 else
2055 {
2056 qWarning() << "Could not auto-save when paused";
2057 }
2058}
2059
2060// shared between openFile and koMainWindow's "create new empty document" code
2061void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
2062{
2063 d->mimeType = mimeType.toLatin1();
2064 d->outputMimeType = d->mimeType;
2065}
2066
2067
2068bool KisDocument::loadNativeFormat(const QString & file_)
2069{
2070 return openPath(file_);
2071}
2072
2074{
2075 if (mod) {
2076 updateEditingTime(false);
2077 }
2078
2083 if (d->isAutosaving || d->documentIsClosing)
2084 return;
2085
2086 //dbgUI<<" url:" << url.path();
2087 //dbgUI<<" mod="<<mod<<" MParts mod="<<KisParts::ReadWritePart::isModified()<<" isModified="<<isModified();
2088
2089 if (mod && !d->autoSaveTimer->isActive()) {
2090 // First change since last autosave -> start the autosave timer
2092 }
2093 d->modifiedAfterAutosave = mod;
2094 d->modifiedWhileSaving = mod;
2095
2096 if (!mod) {
2097 d->imageModifiedWithoutUndo = mod;
2098 }
2099
2100 if (mod == isModified())
2101 return;
2102
2103 d->modified = mod;
2104
2105 if (mod) {
2107 }
2108
2109 Q_EMIT modified(mod);
2110}
2111
2113{
2114 const bool changed = value != d->isRecovered;
2115
2116 d->isRecovered = value;
2117
2118 if (changed) {
2119 Q_EMIT sigRecoveredChanged(value);
2120 }
2121}
2122
2123bool KisDocument::isRecovered() const
2124{
2125 return d->isRecovered;
2126}
2127
2128void KisDocument::updateEditingTime(bool forceStoreElapsed)
2129{
2130 QDateTime now = QDateTime::currentDateTime();
2131 int firstModDelta = d->firstMod.secsTo(now);
2132 int lastModDelta = d->lastMod.secsTo(now);
2133
2134 if (lastModDelta > 30) {
2135 d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
2136 d->firstMod = now;
2137 } else if (firstModDelta > 60 || forceStoreElapsed) {
2138 d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
2139 d->firstMod = now;
2140 }
2141
2142 d->lastMod = now;
2143}
2144
2146{
2147 QString _url(path());
2148#ifdef Q_OS_WIN
2149 _url = QDir::toNativeSeparators(_url);
2150#endif
2151 return _url;
2152}
2153
2154// Get caption from document info (title(), in about page)
2156{
2157 QString c;
2158 const QString _url(QFileInfo(path()).fileName());
2159
2160 // if URL is empty...it is probably an unsaved file
2161 if (_url.isEmpty()) {
2162 c = " [" + i18n("Not Saved") + "] ";
2163 } else {
2164 c = _url; // Fall back to document URL
2165 }
2166
2167 return c;
2168}
2169
2170QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
2171{
2172 return createDomDocument("krita", tagName, version);
2173}
2174
2175//static
2176QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
2177{
2178 QDomImplementation impl;
2179 QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
2180 QDomDocumentType dtype = impl.createDocumentType(tagName,
2181 QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
2182 url);
2183 // The namespace URN doesn't need to include the version number.
2184 QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
2185 QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
2186 doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
2187 return doc;
2188}
2189
2190bool KisDocument::isNativeFormat(const QByteArray& mimeType) const
2191{
2193 return true;
2194 return extraNativeMimeTypes().contains(mimeType);
2195}
2196
2197void KisDocument::setErrorMessage(const QString& errMsg)
2198{
2199 d->lastErrorMessage = errMsg;
2200}
2201
2203{
2204 return d->lastErrorMessage;
2205}
2206
2207void KisDocument::setWarningMessage(const QString& warningMsg)
2208{
2209 d->lastWarningMessage = warningMsg;
2210}
2211
2213{
2214 return d->lastWarningMessage;
2215}
2216
2217
2218void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered)
2219{
2220 // Eliminate any auto-save file
2221 QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir
2222 if (QFile::exists(asf)) {
2223 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
2224 QFile::remove(asf);
2225 }
2226 asf = generateAutoSaveFileName(QString()); // and the one in $HOME
2227
2228 if (QFile::exists(asf)) {
2229 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
2230 QFile::remove(asf);
2231 }
2232
2233 QList<QRegularExpression> expressions;
2234
2235 expressions << QRegularExpression("^\\..+-autosave.kra$")
2236 << QRegularExpression("^.+-autosave.kra$");
2237
2238 Q_FOREACH(const QRegularExpression &rex, expressions) {
2239 if (wasRecovered &&
2240 !autosaveBaseName.isEmpty() &&
2241 rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() &&
2242 QFile::exists(autosaveBaseName)) {
2243
2244 KisUsageLogger::log(QString("Removing autosave file: %1").arg(autosaveBaseName));
2245 QFile::remove(autosaveBaseName);
2246 }
2247 }
2248}
2249
2251{
2252 return d->unit;
2253}
2254
2256{
2257 if (d->unit != unit) {
2258 d->unit = unit;
2259 Q_EMIT unitChanged(unit);
2260 }
2261}
2262
2264{
2265 return d->undoStack;
2266}
2267
2269{
2270 return d->importExportManager;
2271}
2272
2274{
2275 setModified(!value || d->imageModifiedWithoutUndo);
2276}
2277
2279{
2280 KisConfig cfg(true);
2281
2282 if (d->undoStack->undoLimit() != cfg.undoStackLimit()) {
2283 if (!d->undoStack->isClean()) {
2284 d->undoStack->clear();
2285 // we set this because the document *has* changed, even though the
2286 // undo history was purged.
2288 }
2289 d->undoStack->setUndoLimit(cfg.undoStackLimit());
2290 }
2291 d->undoStack->setUseCumulativeUndoRedo(cfg.useCumulativeUndoRedo());
2292 d->undoStack->setCumulativeUndoData(cfg.cumulativeUndoData());
2293
2294 d->autoSaveDelay = cfg.autoSaveInterval();
2296}
2297
2299{
2300 d->syncDecorationsWrapperLayerState();
2301}
2302
2304{
2305 d->undoStack->clear();
2306}
2307
2309{
2310 return d->gridConfig;
2311}
2312
2314{
2315 if (d->gridConfig != config) {
2316 d->gridConfig = config;
2317 d->syncDecorationsWrapperLayerState();
2318 Q_EMIT sigGridConfigChanged(config);
2319
2320 // Store last assigned value as future default...
2321 KisConfig cfg(false);
2322 cfg.setDefaultGridSpacing(config.spacing());
2323 }
2324}
2325
2327{
2329 if (!d->linkedResourceStorage) {
2330 return result;
2331 }
2332
2333 Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) {
2334 QSharedPointer<KisResourceStorage::ResourceIterator> iter = d->linkedResourceStorage->resources(resourceType);
2335 while (iter->hasNext()) {
2336 iter->next();
2337
2338 QBuffer buf;
2339 buf.open(QBuffer::WriteOnly);
2340 bool exportSuccessful =
2341 d->linkedResourceStorage->exportResource(iter->url(), &buf);
2342
2343 KoResourceSP resource = d->linkedResourceStorage->resource(iter->url());
2344 exportSuccessful &= bool(resource);
2345
2346 const QString name = resource ? resource->name() : QString();
2347 const QString fileName = QFileInfo(iter->url()).fileName();
2348 const KoResourceSignature signature(resourceType,
2349 KoMD5Generator::generateHash(buf.data()),
2350 fileName, name);
2351
2352 if (exportSuccessful) {
2353 result << KoEmbeddedResource(signature, buf.data());
2354 } else {
2355 result << signature;
2356 }
2357 }
2358 }
2359
2360 return result;
2361}
2362
2363void KisDocument::setPaletteList(const QList<KoColorSetSP > &paletteList, bool emitSignal)
2364{
2365 QList<KoColorSetSP> oldPaletteList;
2366 if (d->linkedResourceStorage) {
2367 QSharedPointer<KisResourceStorage::ResourceIterator> iter = d->linkedResourceStorage->resources(ResourceType::Palettes);
2368 while (iter->hasNext()) {
2369 iter->next();
2370 KoResourceSP resource = iter->resource();
2371 if (resource && resource->valid()) {
2372 oldPaletteList << resource.dynamicCast<KoColorSet>();
2373 }
2374 }
2375 if (oldPaletteList != paletteList) {
2377 Q_FOREACH(KoColorSetSP palette, oldPaletteList) {
2378 if (!paletteList.contains(palette)) {
2379 resourceModel.setResourceInactive(resourceModel.indexForResource(palette));
2380 }
2381 }
2382 Q_FOREACH(KoColorSetSP palette, paletteList) {
2383 if (!oldPaletteList.contains(palette)) {
2384 resourceModel.addResource(palette, d->linkedResourcesStorageID);
2385 }
2386 else {
2387 palette->setStorageLocation(d->linkedResourcesStorageID);
2388 resourceModel.updateResource(palette);
2389 }
2390 }
2391 if (emitSignal) {
2392 Q_EMIT sigPaletteListChanged(oldPaletteList, paletteList);
2393 }
2394 }
2395 }
2396}
2397
2399{
2400 return d->m_storyboardItemList;
2401}
2402
2403void KisDocument::setStoryboardItemList(const StoryboardItemList &storyboardItemList, bool emitSignal)
2404{
2405 d->m_storyboardItemList = storyboardItemList;
2406 if (emitSignal) {
2408 }
2409}
2410
2412{
2413 return d->m_storyboardCommentList;
2414}
2415
2416void KisDocument::setStoryboardCommentList(const QVector<StoryboardComment> &storyboardCommentList, bool emitSignal)
2417{
2418 d->m_storyboardCommentList = storyboardCommentList;
2419 if (emitSignal) {
2421 }
2422}
2423
2425 return d->audioTracks;
2426}
2427
2429{
2430 d->audioTracks = f;
2431 Q_EMIT sigAudioTracksChanged();
2432}
2433
2435{
2436 d->audioLevel = level;
2437 Q_EMIT sigAudioLevelChanged(level);
2438}
2439
2441{
2442 return d->audioLevel;
2443}
2444
2446{
2447 return d->guidesConfig;
2448}
2449
2451{
2452 if (d->guidesConfig == data) return;
2453
2454 d->guidesConfig = data;
2455 d->syncDecorationsWrapperLayerState();
2456 Q_EMIT sigGuidesConfigChanged(d->guidesConfig);
2457}
2458
2459
2461{
2462 return d->mirrorAxisConfig;
2463}
2464
2466{
2467 if (d->mirrorAxisConfig == config) {
2468 return;
2469 }
2470
2471 d->mirrorAxisConfig = config;
2472 if (d->image) {
2473 d->image->setMirrorAxesCenter(KisAlgebra2D::absoluteToRelative(d->mirrorAxisConfig.axisPosition(),
2474 d->image->bounds()));
2475 }
2476 setModified(true);
2477
2479}
2480
2482 setPath(QString());
2483 setLocalFilePath(QString());
2484}
2485
2487{
2488 return new KoDocumentInfoDlg(parent, docInfo);
2489}
2490
2492{
2493 return d->readwrite;
2494}
2495
2496QString KisDocument::path() const
2497{
2498 return d->m_path;
2499}
2500
2501bool KisDocument::closePath(bool promptToSave)
2502{
2503 if (promptToSave) {
2504 if ( isReadWrite() && isModified()) {
2505 Q_FOREACH (KisView *view, KisPart::instance()->views()) {
2506 if (view && view->document() == this) {
2507 if (!view->queryClose()) {
2508 return false;
2509 }
2510 }
2511 }
2512 }
2513 }
2514 // Not modified => ok and delete temp file.
2515 d->mimeType = QByteArray();
2516
2517 // It always succeeds for a read-only part,
2518 // but the return value exists for reimplementations
2519 // (e.g. pressing cancel for a modified read-write part)
2520 return true;
2521}
2522
2523
2524
2525void KisDocument::setPath(const QString &path)
2526{
2527 const bool changed = path != d->m_path;
2528
2529 d->m_path = path;
2530
2531 if (changed) {
2532 Q_EMIT sigPathChanged(path);
2533 }
2534}
2535
2537{
2538 return d->m_file;
2539}
2540
2541
2542void KisDocument::setLocalFilePath( const QString &localFilePath )
2543{
2544 d->m_file = localFilePath;
2545}
2546
2547bool KisDocument::openPathInternal(const QString &path)
2548{
2549 if ( path.isEmpty() ) {
2550 return false;
2551 }
2552
2553 if (d->m_bAutoDetectedMime) {
2554 d->mimeType = QByteArray();
2555 d->m_bAutoDetectedMime = false;
2556 }
2557
2558 QByteArray mimeType = d->mimeType;
2559
2560 if ( !closePath() ) {
2561 return false;
2562 }
2563
2564 d->mimeType = mimeType;
2565 setPath(path);
2566
2567 d->m_file.clear();
2568
2569 d->m_file = d->m_path;
2570
2571 bool ret = false;
2572 // set the mimeType only if it was not already set (for example, by the host application)
2573 if (d->mimeType.isEmpty()) {
2574 // get the mimeType of the file
2575 // using findByUrl() to avoid another string -> url conversion
2576 QString mime = KisMimeDatabase::mimeTypeForFile(d->m_path);
2577 d->mimeType = mime.toLocal8Bit();
2578 d->m_bAutoDetectedMime = true;
2579 }
2580
2581 setPath(d->m_path);
2582 ret = openFile();
2583
2584 if (ret) {
2585 Q_EMIT completed();
2586 }
2587 else {
2588 Q_EMIT canceled(QString());
2589 }
2590 return ret;
2591}
2592
2593bool KisDocument::newImage(const QString& name,
2594 qint32 width, qint32 height,
2595 const KoColorSpace* cs,
2596 const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle,
2597 int numberOfLayers,
2598 const QString &description, const double imageResolution)
2599{
2600 Q_ASSERT(cs);
2601
2603
2604 if (!cs) return false;
2605
2606 KisCursorOverrideLock cursorLock(Qt::BusyCursor);
2607
2608 image = new KisImage(createUndoStore(), width, height, cs, name);
2609
2610 Q_CHECK_PTR(image);
2611
2612 connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
2613 connect(image, SIGNAL(sigImageModifiedWithoutUndo()), this, SLOT(setImageModifiedWithoutUndo()), Qt::UniqueConnection);
2614 image->setResolution(imageResolution, imageResolution);
2615
2617 image->waitForDone();
2618
2619 documentInfo()->setAboutInfo("title", name);
2620 documentInfo()->setAboutInfo("abstract", description);
2621
2622 KisConfig cfg(false);
2623 cfg.defImageWidth(width);
2624 cfg.defImageHeight(height);
2625 cfg.defImageResolution(imageResolution);
2626 if (!cfg.useDefaultColorSpace())
2627 {
2631 }
2632
2633 bool autopin = cfg.autoPinLayersToTimeline();
2634
2635 KisLayerSP bgLayer;
2636 if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) {
2637 KoColor strippedAlpha = bgColor;
2638 strippedAlpha.setOpacity(OPACITY_OPAQUE_U8);
2639
2640 if (bgStyle == KisConfig::RASTER_LAYER) {
2641 bgLayer = new KisPaintLayer(image.data(), i18nc("Name for the bottom-most layer in the layerstack", "Background"), OPACITY_OPAQUE_U8, cs);
2642 bgLayer->paintDevice()->setDefaultPixel(strippedAlpha);
2643 bgLayer->setPinnedToTimeline(autopin);
2644 } else if (bgStyle == KisConfig::FILL_LAYER) {
2646 filter_config->setProperty("color", strippedAlpha.toQColor());
2647 filter_config->createLocalResourcesSnapshot();
2648 bgLayer = new KisGeneratorLayer(image.data(), i18nc("Name of automatically created background color fill layer", "Background Fill"), filter_config, image->globalSelection());
2649 }
2650
2651 bgLayer->setOpacity(bgColor.opacityU8());
2652
2653 if (numberOfLayers > 1) {
2654 //Lock bg layer if others are present.
2655 bgLayer->setUserLocked(true);
2656 }
2657 }
2658 else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer).
2660 bgLayer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
2661 }
2662
2663 Q_CHECK_PTR(bgLayer);
2664 image->addNode(bgLayer.data(), image->rootLayer().data());
2665 bgLayer->setDirty(QRect(0, 0, width, height));
2666
2667 // reset mirror axis to default:
2668 d->mirrorAxisConfig.setAxisPosition(QRectF(image->bounds()).center());
2670
2671 for(int i = 1; i < numberOfLayers; ++i) {
2673 layer->setPinnedToTimeline(autopin);
2674 image->addNode(layer, image->root(), i);
2675 layer->setDirty(QRect(0, 0, width, height));
2676 }
2677
2678 {
2680 if (window) {
2685 }
2686 }
2687
2689 QString("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8")
2690 .arg(name, QString::number(width), QString::number(height),
2691 QString::number(imageResolution * 72.0), image->colorSpace()->colorModelId().name(),
2693 QString::number(numberOfLayers)));
2694
2695 return true;
2696}
2697
2699{
2700 const bool result = d->savingMutex.tryLock();
2701 if (result) {
2702 d->savingMutex.unlock();
2703 }
2704 return !result;
2705}
2706
2708{
2709 if (isSaving()) {
2710 KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0);
2711 f.waitForMutex(d->savingMutex);
2712 }
2713}
2714
2716{
2717 return d->shapeController;
2718}
2719
2721{
2722 return d->shapeController->shapeForNode(layer);
2723}
2724
2726{
2727 return d->assistants;
2728}
2729
2731{
2732 if (d->assistants != value) {
2733 d->assistants = value;
2734 d->syncDecorationsWrapperLayerState();
2735 Q_EMIT sigAssistantsChanged();
2736 }
2737}
2738
2740{
2741 if (!d->image) return KisReferenceImagesLayerSP();
2742
2743 KisReferenceImagesLayerSP referencesLayer =
2744 KisLayerUtils::findNodeByType<KisReferenceImagesLayer>(d->image->root());
2745
2746 return referencesLayer;
2747}
2748
2750{
2751 KisReferenceImagesLayerSP currentReferenceLayer = referenceImagesLayer();
2752
2753 // updateImage=false inherently means we are not changing the
2754 // reference images layer, but just would like to update its signals.
2755 if (currentReferenceLayer == layer && updateImage) {
2756 return;
2757 }
2758
2759 d->referenceLayerConnections.clear();
2760
2761 if (updateImage) {
2762 if (currentReferenceLayer) {
2763 d->image->removeNode(currentReferenceLayer);
2764 }
2765
2766 if (layer) {
2767 d->image->addNode(layer);
2768 }
2769 }
2770
2771 currentReferenceLayer = layer;
2772
2773 if (currentReferenceLayer) {
2774 d->referenceLayerConnections.addConnection(
2775 currentReferenceLayer, SIGNAL(sigUpdateCanvas(QRectF)),
2776 this, SIGNAL(sigReferenceImagesChanged()));
2777 }
2778
2779 Q_EMIT sigReferenceImagesLayerChanged(layer);
2781}
2782
2784{
2785 d->preActivatedNode = activatedNode;
2786}
2787
2789{
2790 return d->preActivatedNode;
2791}
2792
2794{
2795 return d->image;
2796}
2797
2799{
2800 return d->savingImage;
2801}
2802
2803
2804void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate, KisNodeSP preActivatedNode)
2805{
2806 if (d->image) {
2807 // Disconnect existing sig/slot connections
2808 d->image->setUndoStore(new KisDumbUndoStore());
2809 d->image->disconnect(this);
2810 d->shapeController->setImage(0);
2811 d->image = 0;
2812 }
2813
2814 if (!image) return;
2815
2816 if (d->linkedResourceStorage){
2817 d->linkedResourceStorage->setMetaData(KisResourceStorage::s_meta_name, image->objectName());
2818 }
2819
2820 d->setImageAndInitIdleWatcher(image);
2821 d->image->setUndoStore(new KisDocumentUndoStore(this));
2822 d->shapeController->setImage(image, preActivatedNode);
2823 d->image->setMirrorAxesCenter(KisAlgebra2D::absoluteToRelative(d->mirrorAxisConfig.axisPosition(), image->bounds()));
2824 setModified(false);
2825 connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
2826 connect(d->image, SIGNAL(sigImageModifiedWithoutUndo()), this, SLOT(setImageModifiedWithoutUndo()), Qt::UniqueConnection);
2827 connect(d->image, SIGNAL(sigLayersChangedAsync()), this, SLOT(slotImageRootChanged()));
2828
2829 if (forceInitialUpdate) {
2830 d->image->initialRefreshGraph();
2831 }
2832}
2833
2835{
2837
2838 // we set image without connecting idle-watcher, because loading
2839 // hasn't been finished yet
2840 d->image = image;
2841 d->shapeController->setImage(image);
2842}
2843
2845{
2846 // we only set as modified if undo stack is not at clean state
2847 setModified(d->imageModifiedWithoutUndo || !d->undoStack->isClean());
2848}
2849
2851{
2852 d->imageModifiedWithoutUndo = true;
2854}
2855
2856
2861
2862bool KisDocument::isAutosaving() const
2863{
2864 return d->isAutosaving;
2865}
2866
2867QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage)
2868{
2869 return errorMessage.isEmpty() ? status.errorMessage() : errorMessage;
2870}
2871
2873{
2874 d->globalAssistantsColor = color;
2875}
2876
2878{
2879 return d->globalAssistantsColor;
2880}
2881
2883{
2884 return d->colorHistory;
2885}
2886
2888{
2889 QRectF bounds = d->image->bounds();
2890
2892
2893 if (referenceImagesLayer) {
2895 }
2896
2897 return bounds;
2898}
2899
2901{
2902 d->colorHistory = colors;
2903}
float value(const T *src, size_t ch)
KisSharedPtr< KisReferenceImagesLayer > KisReferenceImagesLayerSP
const quint8 OPACITY_OPAQUE_U8
void setDetailedText(const QStringList &text)
DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings={}, const QString &details={})
virtual void setIndex(int idx)
virtual void undo()
virtual void redo()
bool setResourceInactive(const QModelIndex &index)
Base class for the Krita app.
static bool simpleBackupFile(const QString &filename, const QString &backupDir=QString(), const QString &backupExtension=QStringLiteral("~"))
Definition KisBackup.cpp:24
static bool numberedBackupFile(const QString &filename, const QString &backupDir=QString(), const QString &backupExtension=QStringLiteral("~"), const uint maxBackups=10)
Definition KisBackup.cpp:38
static QImage createCheckersImage(qint32 checkSize=-1)
static KisConfigNotifier * instance()
bool backupFile(bool defaultValue=false) const
qint32 defImageHeight(bool defaultValue=false) const
qint32 defImageWidth(bool defaultValue=false) const
bool useDefaultColorSpace(bool defaultvalue=false) const
void setDefaultColorDepth(const QString &depth) const
void setDefaultGridSpacing(QPoint gridSpacing)
qreal defImageResolution(bool defaultValue=false) const
bool useCumulativeUndoRedo(bool defaultValue=false) const
QString defColorProfile(bool defaultValue=false) const
KisCumulativeUndoData cumulativeUndoData(bool defaultValue=false) const
bool autoPinLayersToTimeline(bool defaultValue=false) const
bool trimKra(bool defaultValue=false) const
T readEntry(const QString &name, const T &defaultValue=T())
Definition kis_config.h:819
int autoSaveInterval(bool defaultValue=false) const
QString defColorModel(bool defaultValue=false) const
int undoStackLimit(bool defaultValue=false) const
StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
QScopedPointer< KisSignalAutoConnection > imageIdleConnection
void slotImageRootChanged()
void completed()
void setAssistants(const QList< KisPaintingAssistantSP > &value)
@replace the current list of assistants with
void waitForSavingToComplete()
QRectF documentBounds() const
QString warningMessage() const
void copyFrom(const Private &rhs, KisDocument *q)
QMutex savingMutex
void removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered)
KisUndoStore * createUndoStore()
bool isReadWrite() const
void slotInitiateAsyncAutosaving(KisDocument *clonedDocument)
void updateDocumentMetadataOnSaving(const QString &filePath, const QByteArray &mimeType)
QList< KoColor > colorHistory
KisNameServer * nserver
void sigStoryboardCommentListChanged()
QFuture< KisImportExportErrorCode > childSavingFuture
void setEmergencyAutoSaveInterval()
void setFileBatchMode(const bool batchMode)
KisMirrorAxisConfig mirrorAxisConfig
KisSharedPtr< KisReferenceImagesLayer > referenceImagesLayer() const
void clearUndoHistory()
KUndo2Stack * undoStack
bool exportDocument(const QString &path, const QByteArray &mimeType, bool isAdvancedExporting=false, bool showWarnings=false, KisPropertiesConfigurationSP exportConfiguration=0)
KoDocumentInfo * documentInfo() const
void syncDecorationsWrapperLayerState()
void hackPreliminarySetImage(KisImageSP image)
void sigBackgroundSavingFinished(KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
QTimer * autoSaveTimer
KisImageSP image
QString linkedResourcesStorageId() const
void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy)
QDateTime lastMod
QPixmap generatePreview(const QSize &size)
Generates a preview picture of the document.
KisShapeController * shapeController
static QStringList extraNativeMimeTypes()
KisSignalAutoConnectionsStore referenceLayerConnections
KritaUtils::BackgroudSavingStartResult initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr< KisDocument > &&optionalClonedDocument, bool isAdvancedExporting=false)
bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
KisImportExportManager * importExportManager
void statusBarMessage(const QString &text, int timeout=0)
bool isNativeFormat(const QByteArray &mimeType) const
Checks whether a given mimeType can be handled natively.
void setAudioTracks(QVector< QFileInfo > f)
void sigAudioLevelChanged(qreal level)
KisDocument * lockAndCloneImpl(bool fetchResourcesFromLayers)
void uploadLinkedResourcesFromLayersToStorage()
void slotUndoStackCleanChanged(bool value)
QString localFilePath() const
void sigAssistantsChanged()
void setErrorMessage(const QString &errMsg)
void setMirrorAxisConfig(const KisMirrorAxisConfig &config)
KoDocumentInfoDlg * createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
void sigStoryboardItemListChanged()
bool isModified() const
KisImportExportErrorCode startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting=false)
QString exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage)
void setAssistantsGlobalColor(QColor color)
bool loadNativeFormat(const QString &file)
void updateEditingTime(bool forceStoreElapsed)
void slotAutoSave()
void sigPaletteListChanged(const QList< KoColorSetSP > &oldPaletteList, const QList< KoColorSetSP > &newPaletteList)
void setModified(bool _mod)
void setMimeType(const QByteArray &mimeType)
Sets the mime type for the document.
QDomDocument createDomDocument(const QString &tagName, const QString &version) const
void sigLoadingFinished()
KisDocument * lockAndCloneForSaving()
try to clone the image. This method handles all the locking for you. If locking has failed,...
void sigReadWriteChanged(bool value)
void finishExportInBackground()
void copyFromDocument(const KisDocument &rhs)
void slotDocumentCloningCancelled()
void setWarningMessage(const QString &warningMsg)
QDateTime firstMod
qreal getAudioLevel()
void setUnit(const KoUnit &unit)
void setImageModified()
void setPath(const QString &path)
bool saveAs(const QString &path, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration=0)
bool newImage(const QString &name, qint32 width, qint32 height, const KoColorSpace *cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &imageDescription, const double imageResolution)
void setLocalFilePath(const QString &localFilePath)
QList< KoResourceLoadResult > linkedDocumentResources()
linkedDocumentResources List returns all the resources linked to the document, such as palettes
KisDocument * lockAndCreateSnapshot()
void setAutoSaveDelay(int delay)
void setPreActivatedNode(KisNodeSP activatedNode)
QString lastWarningMessage
void sigGuidesConfigChanged(const KisGuidesConfig &config)
void setRecovered(bool value)
QString caption() const
KisGridConfig gridConfig
void slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
Private(const Private &rhs, KisDocument *_q)
bool importDocument(const QString &path)
QString errorMessage() const
KritaUtils::ExportFileJob backgroundSaveJob
Private(KisDocument *_q)
QColor globalAssistantsColor
KisResourceStorageSP embeddedResourceStorage
static QByteArray nativeFormatMimeType()
void sigAudioTracksChanged()
void setReferenceImagesLayer(KisSharedPtr< KisReferenceImagesLayer > layer, bool updateImage)
bool resourceSavingFilter(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
KisDocument(bool addStorage=true)
void canceled(const QString &)
QString path() const
void setReadWrite(bool readwrite=true)
Sets whether the document can be edited or is read only.
bool isSaving() const
void setImageModifiedWithoutUndo()
Private *const d
void sigGridConfigChanged(const KisGridConfig &config)
QColor assistantsGlobalColor()
QPointer< KoUpdater > savingUpdater
void autoSaveOnPause()
Start saving when android activity is pushed to the background.
StoryboardItemList getStoryboardItemList()
returns the list of pointers to storyboard Items for the document
QString embeddedResourcesStorageId() const
QString linkedResourcesStorageID
void sigSavingFinished(const QString &filePath)
QString generateAutoSaveFileName(const QString &path) const
KisResourceStorageSP linkedResourceStorage
KisNodeWSP preActivatedNode
KisImageSP savingImage
void setNormalAutoSaveInterval()
void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
void unitChanged(const KoUnit &unit)
KoShapeLayer * shapeForNode(KisNodeSP layer) const
QByteArray mimeType
bool exportDocumentSync(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration=0)
void setGridConfig(const KisGridConfig &config)
bool closePath(bool promptToSave=true)
QString newObjectName()
void sigPathChanged(const QString &path)
QByteArray serializeToNativeByteArray()
serializeToNativeByteArray daves the document into a .kra file written to a memory-based byte-array
QList< KisPaintingAssistantSP > assistants
void setImageAndInitIdleWatcher(KisImageSP _image)
void setAudioVolume(qreal level)
void setAutoSaveActive(bool autoSaveIsActive)
@ CONSTRUCT
we are copy-constructing a new KisDocument
@ REPLACE
we are replacing the current KisDocument with another
void setGuidesConfig(const KisGuidesConfig &data)
bool openPathInternal(const QString &path)
KisIdleWatcher imageIdleWatcher
bool fileBatchMode() const
QByteArray outputMimeType
QVector< StoryboardComment > getStoryboardCommentsList()
returns the list of comments for the storyboard docker in the document
void setMimeTypeAfterLoading(const QString &mimeType)
void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
void copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy)
QScopedPointer< KisDocument > backgroundSaveDocument
void sigReferenceImagesLayerChanged(KisSharedPtr< KisReferenceImagesLayer > layer)
void slotPerformIdleRoutines()
StoryboardItemList m_storyboardItemList
void slotAutoSaveImpl(std::unique_ptr< KisDocument > &&optionalClonedDocument)
void setCurrentImage(KisImageSP image, bool forceInitialUpdate=true, KisNodeSP preActivatedNode=nullptr)
void sigRecoveredChanged(bool value)
QVector< StoryboardComment > m_storyboardCommentList
QVector< QFileInfo > getAudioTracks() const
bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting=false)
void setStoryboardCommentList(const QVector< StoryboardComment > &storyboardCommentList, bool emitSignal=false)
sets the list of comments for the storyboard docker in the document, emits empty signal if emitSignal...
void setPaletteList(const QList< KoColorSetSP > &paletteList, bool emitSignal=false)
setPaletteList replaces the palettes in the document's local resource storage with the list of palett...
void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
void slotConfigChanged()
KisDocument * clone(bool addStorage=false)
creates a clone of the document and returns it. Please make sure that you hold all the necessary lock...
void setInfiniteAutoSaveInterval()
void sigReferenceImagesChanged()
QString lastErrorMessage
KoDocumentInfo * docInfo
QString embeddedResourcesStorageID
void setStoryboardItemList(const StoryboardItemList &storyboardItemList, bool emitSignal=false)
sets the storyboardItemList in the document, emits empty signal if emitSignal is true.
bool openPath(const QString &path, OpenFlags flags=None)
openPath Open a Path
KisGuidesConfig guidesConfig
bool isAutoSaveActive()
void setColorHistory(const QList< KoColor > &colors)
QVector< QFileInfo > audioTracks
void sigMirrorAxisConfigChanged()
QString prettyPath() const
The KisDumbUndoStore class doesn't actually save commands, so you cannot undo or redo!
static KisGeneratorRegistry * instance()
static KisResourcesInterfaceSP instance()
QPoint spacing() const
void setTrackedImage(KisImageSP image)
QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile *profile)
void waitForDone()
KisGroupLayerSP rootLayer() const
UndoResult tryUndoUnfinishedLod0Stroke()
const KoColorSpace * colorSpace() const
void unlock()
Definition kis_image.cc:805
void requestUndoDuringStroke()
QString nextLayerName(const QString &baseName="") const
Definition kis_image.cc:715
bool assignImageProfile(const KoColorProfile *profile, bool blockAllUpdates=false)
KisImage * clone(bool exactCopy=false)
Definition kis_image.cc:405
void setDefaultProjectionColor(const KoColor &color)
void requestStrokeCancellation()
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override
void requestStrokeEnd()
KisSelectionSP globalSelection() const
Definition kis_image.cc:695
QRect bounds() const override
bool tryBarrierLock(bool readOnly=false)
Tries to lock the image without waiting for the jobs to finish.
Definition kis_image.cc:771
void endStroke(KisStrokeId id) override
void requestRedoDuringStroke()
void setResolution(double xres, double yres)
The class managing all the filters.
static KisImportExportFilter * filterForMimeType(const QString &mimetype, Direction direction)
filterForMimeType loads the relevant import/export plugin and returns it. The caller is responsible f...
static KisMacosSecurityBookmarkManager * instance()
Main window for Krita.
KisViewManager * viewManager
static QString mimeTypeForFile(const QString &file, bool checkExistingFiles=true)
Find the mimetype for the given filename. The filename must include a suffix.
The KisMirrorAxisConfig class stores configuration for the KisMirrorAxis canvas decoration....
void setDefaultPixel(const KoColor &defPixel)
static MemoryReleaseObject * createMemoryReleaseObject()
static QList< KisPaintingAssistantSP > cloneAssistantList(const QList< KisPaintingAssistantSP > &list)
void addRecentURLToAllMainWindows(QUrl url, QUrl oldUrl=QUrl())
Definition KisPart.cpp:574
static KisPart * instance()
Definition KisPart.cpp:131
KisMainWindow * currentMainwindow() const
Definition KisPart.cpp:459
The KisRecoverNamedAutosaveDialog class is a dialog to recover already existing files from autosave.
static bool getResourceIdFromVersionedFilename(QString filename, QString resourceType, QString storageLocation, int &outResourceId)
Note that here you can put even the original filename - any filename from the versioned_resources - a...
static KisResourceLoaderRegistry * instance()
bool removeStorage(const QString &storageLocation)
removeStorage removes the temporary storage from the database
bool addStorage(const QString &storageLocation, KisResourceStorageSP storage)
addStorage Adds a new resource storage to the database. The storage is will be marked as not pre-inst...
QString filePathForResource(KoResourceSP resource)
static KisResourceLocator * instance()
The KisResourceModel class provides the main access to resources. It is possible to filter the resour...
KoResourceSP resourceForId(int id) const
KoResourceSP importResourceFile(const QString &filename, const bool allowOverwrite, const QString &storageId=QString("")) override
importResourceFile
bool updateResource(KoResourceSP resource) override
updateResource creates a new version of the resource in the storage and in the database....
bool addResource(KoResourceSP resource, const QString &storageId=QString("")) override
addResource adds the given resource to the database and storage. If the resource already exists in th...
void setResourceFilter(ResourceFilter filter) override
QModelIndex indexForResource(KoResourceSP resource) const override
indexFromResource
static KisResourceServerProvider * instance()
static const QString s_meta_name
void enableJob(JobType type, bool enable=true, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
void setClearsRedoOnStart(bool value)
void setRequestsOtherStrokesToEnd(bool value)
static void log(const QString &message)
Logs with date/time.
bool blockUntilOperationsFinished(KisImageSP image)
blockUntilOperationsFinished blocks the GUI of the application until execution of actions on image is...
QPointer< KoUpdater > createThreadedUpdater(const QString &name)
void blockUntilOperationsFinishedForced(KisImageSP image)
blockUntilOperationsFinished blocks the GUI of the application until execution of actions on image is...
QPointer< KoUpdater > createUnthreadedUpdater(const QString &name)
create a new progress updater
KisCanvasResourceProvider * canvasResourceProvider()
QPointer< KisDocument > document
Definition KisView.cpp:121
bool queryClose()
Definition KisView.cpp:1155
bool isValid() const
virtual KoID colorModelId() const =0
virtual KoID colorDepthId() const =0
virtual const KoColorProfile * profile() const =0
void setOpacity(quint8 alpha)
Definition KoColor.cpp:333
quint8 opacityU8() const
Definition KoColor.cpp:341
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
The dialog that shows information about the document.
The class containing all meta information about a document.
void setAboutInfo(const QString &info, const QString &data)
T get(const QString &id) const
QString name() const
Definition KoID.cpp:68
QString id() const
Definition KoID.cpp:63
static QString generateHash(const QString &filename)
generateHash reads the given file and generates a hex-encoded md5sum for the file.
A simple wrapper object for the main information about the resource.
@ Centimeter
Definition KoUnit.h:78
@ Inch
Definition KoUnit.h:77
static StoryboardItemList cloneStoryboardItemList(const StoryboardItemList &list)
KisDocument * m_doc
QQueue< PostponedJob > m_postponedJobs
void notifySetIndexChangedOneCommand() override
KisImageWSP image()
int m_recursionCounter
void undo() override
UndoStack(KisDocument *doc)
void processPostponedJobs()
void undoImpl()
void redoImpl()
void setIndex(int idx) override
void redo() override
void setIndexImpl(int idx)
#define KIS_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:85
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define bounds(x, a, b)
#define dbgUI
Definition kis_debug.h:52
#define dbgAndroid
Definition kis_debug.h:62
KUndo2MagicString kundo2_noi18n(const QString &text)
QPointF absoluteToRelative(const QPointF &pt, const QRectF &rc)
bool hasDelayedNodeWithUpdates(KisNodeSP root)
void recursiveApplyNodes(NodePointer node, Functor func)
void forceAllDelayedNodesUpdate(KisNodeSP root)
const QString Palettes
rgba palette[MAX_PALETTE]
Definition palette.c:35
void setPinnedToTimeline(bool pinned)
virtual void setUserLocked(bool l)
void setOpacity(quint8 val)
KisImageWSP image
virtual KisPaintDeviceSP paintDevice() const =0
virtual KisFilterConfigurationSP defaultConfiguration(KisResourcesInterfaceSP resourcesInterface) const
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
virtual void setDirty()
Definition kis_node.cpp:577