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;
862 // noop, not possible
863 break;
864 }
865
866 KisUsageLogger::log(QString("Failed to initiate saving %1 in background: %2").arg(job.filePath).arg(errorShortLog));
867
868 slotCompleteSavingDocument(job, errorCode,
870 "");
871 return false;
872 }
873
875}
876
877bool KisDocument::exportDocument(const QString &path, const QByteArray &mimeType, bool isAdvancedExporting, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
878{
879 using namespace KritaUtils;
880
881 SaveFlags flags = SaveIsExporting;
882 if (showWarnings) {
883 flags |= SaveShowWarnings;
884 }
885
886 KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 "
887 "framerate. Export configuration: %8")
888 .arg(path, QString::fromLatin1(mimeType), QString::number(d->image->width()),
889 QString::number(d->image->height()), QString::number(d->image->nlayers()),
890 QString::number(d->image->animationInterface()->totalLength()),
891 QString::number(d->image->animationInterface()->framerate()),
892 (exportConfiguration ? exportConfiguration->toXML() : "No configuration")));
893
895 mimeType,
896 flags),
897 exportConfiguration, isAdvancedExporting);
898}
899
900bool KisDocument::saveAs(const QString &_path, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
901{
902 using namespace KritaUtils;
903
904 KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, "
905 "%7 framerate. Export configuration: %8")
906 .arg(_path, QString::fromLatin1(mimeType), QString::number(d->image->width()),
907 QString::number(d->image->height()), QString::number(d->image->nlayers()),
908 QString::number(d->image->animationInterface()->totalLength()),
909 QString::number(d->image->animationInterface()->framerate()),
910 (exportConfiguration ? exportConfiguration->toXML() : "No configuration"),
911 path()));
912
913 // Check whether it's an existing resource were are saving to
914 if (resourceSavingFilter(_path, mimeType, exportConfiguration)) {
915 return true;
916 }
917
919 mimeType,
920 showWarnings ? SaveShowWarnings : SaveNone),
921 exportConfiguration);
922}
923
924bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
925{
926 return saveAs(path(), mimeType(), showWarnings, exportConfiguration);
927}
928
930{
931 QBuffer buffer;
932
934 filter->setBatchMode(true);
935 filter->setMimeType(nativeFormatMimeType());
936
937 Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
938 if (!locker.successfullyLocked()) {
939 return buffer.data();
940 }
941
942 d->savingImage = d->image;
943
944 if (!filter->convert(this, &buffer).isOk()) {
945 qWarning() << "serializeToByteArray():: Could not export to our native format";
946 }
947
948 return buffer.data();
949}
950
951class DlgLoadMessages : public QMessageBox
952{
953public:
954 DlgLoadMessages(const QString &title,
955 const QString &message,
956 const QStringList &warnings = {},
957 const QString &details = {})
958 : QMessageBox(QMessageBox::Warning, title, message, QMessageBox::Ok, qApp->activeWindow())
959 {
960 if (!details.isEmpty()) {
961 setInformativeText(details);
962 }
963 if (!warnings.isEmpty()) {
964 setDetailedText(warnings);
965 }
966 }
967
968private:
969 void setDetailedText(const QStringList &text)
970 {
971 QMessageBox::setDetailedText(text.first());
972
973 QTextEdit *messageBox = findChild<QTextEdit *>();
974
975 if (messageBox) {
976 messageBox->setAcceptRichText(true);
977
978 QString warning = "<html><body><ul>";
979 Q_FOREACH (const QString &i, text) {
980 warning += "\n<li>" + i + "</li>";
981 }
982 warning += "</ul></body></html>";
983
984 messageBox->setText(warning);
985 }
986 }
987};
988
989void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
990{
991 if (status.isCancelled())
992 return;
993
994 const QString fileName = QFileInfo(job.filePath).fileName();
995
996 if (!status.isOk()) {
997 Q_EMIT statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
998 "Error during saving %1: %2",
999 fileName,
1000 errorMessage), errorMessageTimeout);
1001
1002
1003 if (!fileBatchMode()) {
1004 DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
1005 i18n("Could not save %1.", job.filePath),
1006 errorMessage.split("\n", Qt::SkipEmptyParts)
1007 + warningMessage.split("\n", Qt::SkipEmptyParts),
1008 status.errorMessage());
1009
1010 dlg.exec();
1011 }
1012 }
1013 else {
1014 if (!fileBatchMode() && !warningMessage.isEmpty()) {
1015
1016 QStringList reasons = warningMessage.split("\n", Qt::SkipEmptyParts);
1017
1018 DlgLoadMessages dlg(
1019 i18nc("@title:window", "Krita"),
1020 i18nc("dialog box shown to the user if there were warnings while saving the document, "
1021 "%1 is the file path",
1022 "%1 has been saved but is incomplete.",
1023 job.filePath),
1024 reasons,
1025 reasons.isEmpty()
1026 ? ""
1027 : i18nc("dialog box shown to the user if there were warnings while saving the document",
1028 "Some problems were encountered when saving."));
1029 dlg.exec();
1030 }
1031
1032
1033 if (!(job.flags & KritaUtils::SaveIsExporting)) {
1034 const QString existingAutoSaveBaseName = localFilePath();
1035 const bool wasRecovered = isRecovered();
1036
1037 d->updateDocumentMetadataOnSaving(job.filePath, job.mimeType);
1038
1039 removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered);
1040 }
1041
1042 Q_EMIT completed();
1043 Q_EMIT sigSavingFinished(job.filePath);
1044
1045 Q_EMIT statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
1046 }
1047}
1048
1049void KisDocument::Private::updateDocumentMetadataOnSaving(const QString &filePath, const QByteArray &mimeType)
1050{
1051 q->setPath(filePath);
1052 q->setLocalFilePath(filePath);
1053 q->setMimeType(mimeType);
1054 q->updateEditingTime(true);
1055
1056 QFileInfo fi(filePath);
1057 q->setReadWrite(fi.isWritable());
1058
1059 if (!modifiedWhileSaving) {
1066 if (undoStack->isClean()) {
1067 q->setModified(false);
1068 } else {
1069 imageModifiedWithoutUndo = false;
1070 undoStack->setClean();
1071 }
1072 }
1073 q->setRecovered(false);
1074}
1075
1076QByteArray KisDocument::mimeType() const
1077{
1078 return d->mimeType;
1079}
1080
1081void KisDocument::setMimeType(const QByteArray & mimeType)
1082{
1083 d->mimeType = mimeType;
1084}
1085
1087{
1088 return d->batchMode;
1089}
1090
1091void KisDocument::setFileBatchMode(const bool batchMode)
1092{
1093 d->batchMode = batchMode;
1094}
1095
1096void KisDocument::Private::uploadLinkedResourcesFromLayersToStorage()
1097{
1102
1103 KisDocument *doc = q;
1104
1106 [doc] (KisNodeSP node) {
1107 if (KisNodeFilterInterface *layer = dynamic_cast<KisNodeFilterInterface*>(node.data())) {
1108 KisFilterConfigurationSP filterConfig = layer->filter();
1109 if (!filterConfig) return;
1110
1111 QList<KoResourceLoadResult> linkedResources = filterConfig->linkedResources(KisGlobalResourcesInterface::instance());
1112
1113 Q_FOREACH (const KoResourceLoadResult &result, linkedResources) {
1114 KIS_SAFE_ASSERT_RECOVER(result.type() != KoResourceLoadResult::EmbeddedResource) { continue; }
1115
1116 KoResourceSP resource = result.resource();
1117
1118 if (!resource) {
1119 qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to fetch a resource" << result.signature();
1120 continue;
1121 }
1122
1123 QBuffer buf;
1124 buf.open(QBuffer::WriteOnly);
1125
1126 KisResourceModel model(resource->resourceType().first);
1127 bool res = model.exportResource(resource, &buf);
1128
1129 buf.close();
1130
1131 if (!res) {
1132 qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to export resource" << result.signature();
1133 continue;
1134 }
1135
1136 buf.open(QBuffer::ReadOnly);
1137
1138 res = doc->d->linkedResourceStorage->importResource(resource->resourceType().first + "/" + resource->filename(), &buf);
1139
1140 buf.close();
1141
1142 if (!res) {
1143 qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to import resource" << result.signature();
1144 continue;
1145 }
1146 }
1147
1148 }
1149 });
1150}
1151
1152KisDocument *KisDocument::Private::lockAndCloneImpl(bool fetchResourcesFromLayers)
1153{
1154 // force update of all the asynchronous nodes before cloning
1155 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1157
1159 if (window) {
1160 if (window->viewManager()) {
1161 if (!window->viewManager()->blockUntilOperationsFinished(image)) {
1162 return 0;
1163 }
1164 }
1165 }
1166
1167 Private::StrippedSafeSavingLocker locker(&savingMutex, image);
1168 if (!locker.successfullyLocked()) {
1169 return 0;
1170 }
1171
1172 KisDocument *doc = new KisDocument(*this->q, false);
1173
1174 if (fetchResourcesFromLayers) {
1175 doc->d->uploadLinkedResourcesFromLayersToStorage();
1176 }
1177
1178 return doc;
1179}
1180
1182{
1183 return d->lockAndCloneImpl(true);
1184}
1185
1187{
1188 return d->lockAndCloneImpl(false);
1189}
1190
1195
1197{
1198 if (policy == REPLACE) {
1199 d->decorationsSyncingDisabled = true;
1200 d->copyFrom(*(rhs.d), this);
1201 d->decorationsSyncingDisabled = false;
1202
1203 d->undoStack->clear();
1204 } else {
1205 // in CONSTRUCT mode, d should be already initialized
1206 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
1207 connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
1208 connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
1209
1210 d->shapeController = new KisShapeController(d->nserver, d->undoStack, this);
1211 d->koShapeController = new KoShapeController(0, d->shapeController);
1212 }
1213
1214 setObjectName(rhs.objectName());
1215
1217
1218 if (rhs.d->image) {
1219 if (policy == REPLACE) {
1220 d->image->barrierLock(/* readOnly = */ false);
1221 rhs.d->image->barrierLock(/* readOnly = */ true);
1222 d->image->copyFromImage(*(rhs.d->image));
1223 d->image->unlock();
1224 rhs.d->image->unlock();
1225
1226 setCurrentImage(d->image, /* forceInitialUpdate = */ true);
1227 } else {
1228 // clone the image with keeping the GUIDs of the layers intact
1229 // NOTE: we expect the image to be locked!
1230 setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false);
1231 }
1232 }
1233
1234 if (policy == REPLACE) {
1235 d->syncDecorationsWrapperLayerState();
1236 }
1237
1238 if (rhs.d->preActivatedNode) {
1239 QQueue<KisNodeSP> linearizedNodes;
1240 KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(),
1241 [&linearizedNodes](KisNodeSP node) {
1242 linearizedNodes.enqueue(node);
1243 });
1245 [&linearizedNodes, &rhs, this](KisNodeSP node) {
1246 KisNodeSP refNode = linearizedNodes.dequeue();
1247 if (rhs.d->preActivatedNode.data() == refNode.data()) {
1248 d->preActivatedNode = node;
1249 }
1250 });
1251 }
1252
1253 // reinitialize references' signal connection
1254 KisReferenceImagesLayerSP referencesLayer = this->referenceImagesLayer();
1255 if (referencesLayer) {
1256 d->referenceLayerConnections.clear();
1257 d->referenceLayerConnections.addConnection(
1258 referencesLayer, SIGNAL(sigUpdateCanvas(QRectF)),
1259 this, SIGNAL(sigReferenceImagesChanged()));
1260
1261 Q_EMIT sigReferenceImagesLayerChanged(referencesLayer);
1262 Q_EMIT sigReferenceImagesChanged();
1263 }
1264
1265 KisDecorationsWrapperLayerSP decorationsLayer =
1266 KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(d->image->root());
1267 if (decorationsLayer) {
1268 decorationsLayer->setDocument(this);
1269 }
1270
1271
1272 if (policy == REPLACE) {
1273 setModified(true);
1274 }
1275}
1276
1277bool KisDocument::exportDocumentSync(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
1278{
1279 {
1286 Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
1287 if (!locker.successfullyLocked()) {
1288 return false;
1289 }
1290 }
1291
1292 d->savingImage = d->image;
1293
1295 d->importExportManager->
1296 exportDocument(path, path, mimeType, false, exportConfiguration);
1297
1298 d->savingImage = 0;
1299
1300 return status.isOk();
1301}
1302
1303
1305 const QObject *receiverObject, const char *receiverMethod,
1306 const KritaUtils::ExportFileJob &job,
1307 KisPropertiesConfigurationSP exportConfiguration,bool isAdvancedExporting)
1308{
1309 return initiateSavingInBackground(actionName, receiverObject, receiverMethod,
1310 job, exportConfiguration, std::unique_ptr<KisDocument>(), isAdvancedExporting);
1311}
1312
1314 const QObject *receiverObject, const char *receiverMethod,
1315 const KritaUtils::ExportFileJob &job,
1316 KisPropertiesConfigurationSP exportConfiguration,
1317 std::unique_ptr<KisDocument> &&optionalClonedDocument,bool isAdvancedExporting)
1318{
1320
1321 QScopedPointer<KisDocument> clonedDocument;
1322
1323 if (!optionalClonedDocument) {
1324 clonedDocument.reset(lockAndCloneForSaving());
1325 } else {
1326 clonedDocument.reset(optionalClonedDocument.release());
1327 }
1328
1329 if (!d->savingMutex.tryLock()){
1331 }
1332
1337 std::unique_lock<QMutex> savingMutexLock(d->savingMutex, std::adopt_lock);
1338
1339 if (!clonedDocument) {
1341 }
1342
1343 auto waitForImage = [] (KisImageSP image) {
1345 if (window) {
1346 if (window->viewManager()) {
1348 }
1349 }
1350 };
1351
1352 {
1353 KisNodeSP newRoot = clonedDocument->image()->root();
1356 waitForImage(clonedDocument->image());
1357 }
1358 }
1359
1360 if (clonedDocument->image()->hasOverlaySelectionMask()) {
1361 clonedDocument->image()->setOverlaySelectionMask(0);
1362 waitForImage(clonedDocument->image());
1363 }
1364
1365 KisConfig cfg(true);
1366 if (cfg.trimKra()) {
1367 clonedDocument->image()->cropImage(clonedDocument->image()->bounds());
1368 clonedDocument->image()->purgeUnusedData(false);
1369 waitForImage(clonedDocument->image());
1370 }
1371
1372 KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) {
1373 waitForImage(clonedDocument->image());
1374 }
1375
1378
1388 savingMutexLock.release();
1389
1390 d->backgroundSaveDocument.reset(clonedDocument.take());
1391 d->backgroundSaveJob = job;
1392 d->modifiedWhileSaving = false;
1393
1394 if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
1395 d->backgroundSaveDocument->d->isAutosaving = true;
1396 }
1397
1398 connect(d->backgroundSaveDocument.data(),
1399 SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString, QString)),
1400 this,
1402
1403
1405 receiverObject, receiverMethod, Qt::UniqueConnection);
1406
1407 bool started =
1408 d->backgroundSaveDocument->startExportInBackground(actionName,
1409 job.filePath,
1410 job.filePath,
1411 job.mimeType,
1413 exportConfiguration, isAdvancedExporting);
1414
1415 if (!started) {
1416 // the state should have been deinitialized in slotChildCompletedSavingInBackground()
1417 KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) {
1418 d->backgroundSaveDocument.take()->deleteLater();
1419 d->savingMutex.unlock();
1420 d->backgroundSaveJob = KritaUtils::ExportFileJob();
1421 }
1423 }
1424
1426}
1427
1428
1429void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
1430{
1432
1438 std::unique_lock<QMutex> savingMutexLock(d->savingMutex, std::adopt_lock);
1439
1440 KIS_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument);
1441
1442 if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
1443 d->backgroundSaveDocument->d->isAutosaving = false;
1444 }
1445
1446 d->backgroundSaveDocument.take()->deleteLater();
1447
1448 KIS_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid());
1449
1450 const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
1451 d->backgroundSaveJob = KritaUtils::ExportFileJob();
1452
1453 // unlock at the very end
1454 savingMutexLock.unlock();
1455
1456 QFileInfo fi(job.filePath);
1457 KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3. Warning: %4. Size: %5")
1458 .arg(job.filePath, QString::fromLatin1(job.mimeType),
1459 (!status.isOk() ? errorMessage : "OK"), warningMessage,
1460 QString::number(fi.size())));
1461
1463}
1464
1465void KisDocument::slotAutoSaveImpl(std::unique_ptr<KisDocument> &&optionalClonedDocument)
1466{
1467 if (!d->modified || !d->modifiedAfterAutosave) return;
1468 const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
1469
1470 Q_EMIT statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout);
1471
1472 KisUsageLogger::log(QString("Autosaving: %1").arg(autoSaveFileName));
1473
1474 const bool hadClonedDocument = bool(optionalClonedDocument);
1476
1477 if (d->image->isIdle() || hadClonedDocument) {
1478 result = initiateSavingInBackground(i18n("Autosaving..."),
1481 0,
1482 std::move(optionalClonedDocument));
1483 } else {
1484 Q_EMIT statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout);
1485 }
1486
1487 if (result != KritaUtils::BackgroudSavingStartResult::Success && !hadClonedDocument && d->autoSaveFailureCount >= 3) {
1489 connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)),
1491 Qt::BlockingQueuedConnection);
1492 connect(stroke, SIGNAL(sigCloningCancelled()),
1493 this, SLOT(slotDocumentCloningCancelled()),
1494 Qt::BlockingQueuedConnection);
1495
1496 KisStrokeId strokeId = d->image->startStroke(stroke);
1497 d->image->endStroke(strokeId);
1498
1500
1503 } else {
1504 d->modifiedAfterAutosave = false;
1505 }
1506}
1507
1508bool KisDocument::resourceSavingFilter(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
1509{
1510 if (QFileInfo(path).absolutePath().startsWith(KisResourceLocator::instance()->resourceLocationBase())) {
1511
1512 QStringList pathParts = QFileInfo(path).absolutePath().split('/');
1513 if (pathParts.size() > 0) {
1514 QString resourceType = pathParts.last();
1515 if (KisResourceLoaderRegistry::instance()->resourceTypes().contains(resourceType)) {
1516
1517 KisResourceModel model(resourceType);
1519
1520 QString tempFileName = QDir::tempPath() + "/" + QFileInfo(path).fileName();
1521
1522 if (QFileInfo(path).exists()) {
1523
1524 int outResourceId;
1525 KoResourceSP res;
1526 if (KisResourceCacheDb::getResourceIdFromVersionedFilename(QFileInfo(path).fileName(), resourceType, "", outResourceId)) {
1527 res = model.resourceForId(outResourceId);
1528 }
1529
1530 if (res) {
1531 d->modifiedWhileSaving = false;
1532
1533 if (!exportConfiguration) {
1534 QScopedPointer<KisImportExportFilter> filter(
1536 if (filter) {
1537 exportConfiguration = filter->defaultConfiguration(nativeFormatMimeType(), mimeType);
1538 }
1539 }
1540
1541 if (exportConfiguration) {
1542 // make sure the name of the resource doesn't change
1543 exportConfiguration->setProperty("name", res->name());
1544 }
1545
1546 if (exportDocumentSync(tempFileName, mimeType, exportConfiguration)) {
1547 QFile f2(tempFileName);
1548 f2.open(QFile::ReadOnly);
1549
1550 QByteArray ba = f2.readAll();
1551
1552 QBuffer buf(&ba);
1553 buf.open(QBuffer::ReadOnly);
1554
1555
1556
1557 if (res->loadFromDevice(&buf, KisGlobalResourcesInterface::instance())) {
1558 if (model.updateResource(res)) {
1559 const QString filePath =
1561
1562 d->updateDocumentMetadataOnSaving(filePath, mimeType);
1563
1564 return true;
1565 }
1566 }
1567 }
1568 }
1569 }
1570 else {
1571 d->modifiedWhileSaving = false;
1572 if (exportDocumentSync(tempFileName, mimeType, exportConfiguration)) {
1573 KoResourceSP res = model.importResourceFile(tempFileName, false);
1574 if (res) {
1575 const QString filePath =
1577
1578 d->updateDocumentMetadataOnSaving(filePath, mimeType);
1579
1580 return true;
1581 }
1582 }
1583 }
1584 }
1585 }
1586 }
1587 return false;
1588}
1589
1591{
1592 slotAutoSaveImpl(std::unique_ptr<KisDocument>());
1593}
1594
1596{
1597 slotAutoSaveImpl(std::unique_ptr<KisDocument>(clonedDocument));
1598}
1599
1604
1606{
1607 d->image->explicitRegenerateLevelOfDetail();
1608
1609
1613
1614 // d->image->purgeUnusedData(true);
1615}
1616
1617void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
1618{
1619 Q_UNUSED(job);
1620 Q_UNUSED(warningMessage);
1621
1622 const QString fileName = QFileInfo(job.filePath).fileName();
1623
1624 if (!status.isOk()) {
1626 Q_EMIT statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
1627 "Error during autosaving %1: %2",
1628 fileName,
1629 exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
1630 } else {
1631 KisConfig cfg(true);
1632 d->autoSaveDelay = cfg.autoSaveInterval();
1633
1634 if (!d->modifiedWhileSaving) {
1635 d->autoSaveTimer->stop(); // until the next change
1636 d->autoSaveFailureCount = 0;
1637 } else {
1639 }
1640
1641 Q_EMIT statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout);
1642 }
1643}
1644
1645bool KisDocument::startExportInBackground(const QString &actionName,
1646 const QString &location,
1647 const QString &realLocation,
1648 const QByteArray &mimeType,
1649 bool showWarnings,
1650 KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting)
1651{
1652 d->savingImage = d->image;
1653
1655 if (window) {
1656 if (window->viewManager()) {
1657 d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName);
1658 d->importExportManager->setUpdater(d->savingUpdater);
1659 }
1660 }
1661
1662 KisImportExportErrorCode initializationStatus(ImportExportCodes::OK);
1663 d->childSavingFuture =
1664 d->importExportManager->exportDocumentAsync(location,
1665 realLocation,
1666 mimeType,
1667 initializationStatus,
1668 showWarnings,
1669 exportConfiguration,
1670 isAdvancedExporting);
1671
1672 if (!initializationStatus.isOk()) {
1673 if (d->savingUpdater) {
1674 d->savingUpdater->cancel();
1675 }
1676 d->savingImage.clear();
1677 Q_EMIT sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage(), "");
1678 return false;
1679 }
1680
1681 typedef QFutureWatcher<KisImportExportErrorCode> StatusWatcher;
1682 StatusWatcher *watcher = new StatusWatcher();
1683 watcher->setFuture(d->childSavingFuture);
1684
1685 connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground()));
1686 connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
1687
1688 return true;
1689}
1690
1692{
1693 KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) {
1695 return;
1696 }
1697
1698 KisImportExportErrorCode status = d->childSavingFuture.result();
1699 QString errorMessage = status.errorMessage();
1700 QString warningMessage = d->lastWarningMessage;
1701
1702 if (!d->lastErrorMessage.isEmpty()) {
1704 errorMessage = d->lastErrorMessage;
1705 } else {
1706 errorMessage += "\n" + d->lastErrorMessage;
1707 }
1708 }
1709
1710 d->savingImage.clear();
1711 d->childSavingFuture = QFuture<KisImportExportErrorCode>();
1712 d->lastErrorMessage.clear();
1713 d->lastWarningMessage.clear();
1714
1715 if (d->savingUpdater) {
1716 d->savingUpdater->setProgress(100);
1717 }
1718
1720}
1721
1722void KisDocument::setReadWrite(bool readwrite)
1723{
1724 const bool changed = readwrite != d->readwrite;
1725
1726 d->readwrite = readwrite;
1727
1728 if (changed) {
1730 }
1731}
1732
1733void KisDocument::setAutoSaveActive(bool autoSaveActive)
1734{
1735 const bool changed = autoSaveActive != d->autoSaveActive;
1736
1737 if (changed) {
1738 d->autoSaveActive = autoSaveActive;
1740 }
1741}
1742
1744{
1745 if (isReadWrite() && delay > 0 && d->autoSaveActive) {
1746 d->autoSaveTimer->start(delay * 1000);
1747 } else {
1748 d->autoSaveTimer->stop();
1749 }
1750}
1751
1753{
1754 setAutoSaveDelay(d->autoSaveDelay);
1755 d->autoSaveFailureCount = 0;
1756}
1757
1759{
1760 const int emergencyAutoSaveInterval = 10; /* sec */
1761 setAutoSaveDelay(emergencyAutoSaveInterval);
1762 d->autoSaveFailureCount++;
1763}
1764
1769
1771{
1772 return d->autoSaveActive;
1773}
1774
1776{
1777 return d->docInfo;
1778}
1779
1781{
1782 return d->modified;
1783}
1784
1785QPixmap KisDocument::generatePreview(const QSize& size)
1786{
1787 KisImageSP image = d->image;
1788 if (d->savingImage) image = d->savingImage;
1789
1790 if (image) {
1791 QRect bounds = image->bounds();
1792 QSize originalSize = bounds.size();
1793 // QSize may round down one dimension to zero on extreme aspect rations, so ensure 1px minimum
1794 QSize newSize = originalSize.scaled(size, Qt::KeepAspectRatio).expandedTo({1, 1});
1795
1796 bool pixelArt = false;
1797 // determine if the image is pixel art or not
1798 if (originalSize.width() < size.width() && originalSize.height() < size.height()) {
1799 // the image must be smaller than the requested preview
1800 // the scale must be integer
1801 if (newSize.height()%originalSize.height() == 0 && newSize.width()%originalSize.width() == 0) {
1802 pixelArt = true;
1803 }
1804 }
1805
1806 QPixmap px;
1807 if (pixelArt) {
1808 // do not scale while converting (because it uses Bicubic)
1809 QImage original = image->convertToQImage(originalSize, 0);
1810 // scale using FastTransformation, which is probably Nearest neighbour, suitable for pixel art
1811 QImage scaled = original.scaled(newSize, Qt::KeepAspectRatio, Qt::FastTransformation);
1812 px = QPixmap::fromImage(scaled);
1813 } else {
1814 px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
1815 }
1816 if (px.size() == QSize(0,0)) {
1817 px = QPixmap(newSize);
1818 QPainter gc(&px);
1819 QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
1820 gc.fillRect(px.rect(), checkBrush);
1821 gc.end();
1822 }
1823 return px;
1824 }
1825 return QPixmap(size);
1826}
1827
1828QString KisDocument::generateAutoSaveFileName(const QString & path) const
1829{
1830 QString retval;
1831
1832 // Using the extension allows to avoid relying on the mime magic when opening
1833 const QString extension (".kra");
1834 QString prefix = KisConfig(true).readEntry<bool>("autosavefileshidden") ? QString(".") : QString();
1835 QRegularExpression autosavePattern1("^\\..+-autosave.kra$");
1836 QRegularExpression autosavePattern2("^.+-autosave.kra$");
1837
1838 QFileInfo fi(path);
1839 QString dir = fi.absolutePath();
1840
1841#ifdef Q_OS_ANDROID
1842 // URIs may or may not have a directory backing them, so we save to our default autosave location
1843 if (path.startsWith("content://")) {
1845 QDir().mkpath(dir);
1846 }
1847#endif
1848
1849 QString filename = fi.fileName();
1850
1851 if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) {
1852 // Never saved?
1853 retval = QString("%1%2%3%4-%5-%6-autosave%7")
1855 .arg('/')
1856 .arg(prefix)
1857 .arg("krita")
1858 .arg(qApp->applicationPid())
1859 .arg(objectName())
1860 .arg(extension);
1861 } else {
1862 // Beware: don't reorder arguments
1863 // otherwise in case of filename = '1-file.kra' it will become '.-file.kra-autosave.kra' instead of '.1-file.kra-autosave.kra'
1864 retval = QString("%1%2%3%4-autosave%5").arg(dir).arg('/').arg(prefix).arg(filename).arg(extension);
1865 }
1866
1867 //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
1868 return retval;
1869}
1870
1871bool KisDocument::importDocument(const QString &_path)
1872{
1873 bool ret;
1874
1875 dbgUI << "path=" << _path;
1876
1877 // open...
1878 ret = openPath(_path);
1879
1880 // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
1881 // File --> Import
1882 if (ret) {
1883 dbgUI << "success, resetting url";
1884 resetPath();
1885 }
1886
1887 return ret;
1888}
1889
1890
1891bool KisDocument::openPath(const QString &_path, OpenFlags flags)
1892{
1893 dbgUI << "path=" << _path;
1894 d->lastErrorMessage.clear();
1895
1896 // Reimplemented, to add a check for autosave files and to improve error reporting
1897 if (_path.isEmpty()) {
1898 d->lastErrorMessage = i18n("Malformed Path\n%1", _path); // ## used anywhere ?
1899 return false;
1900 }
1901
1902 QString path = _path;
1903 QString original = "";
1904 bool autosaveOpened = false;
1905 if (!fileBatchMode()) {
1906 QString file = path;
1907 QString asf = generateAutoSaveFileName(file);
1908 if (QFile::exists(asf)) {
1909 KisApplication *kisApp = static_cast<KisApplication*>(qApp);
1910 kisApp->hideSplashScreen();
1911 //qDebug() <<"asf=" << asf;
1912 // ## TODO compare timestamps ?
1913 KisRecoverNamedAutosaveDialog dlg(0, file, asf);
1914 dlg.exec();
1915 int res = dlg.result();
1916
1917 switch (res) {
1919 original = file;
1920 path = asf;
1921 autosaveOpened = true;
1922 break;
1924 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
1925 QFile::remove(asf);
1926 break;
1927 default: // Cancel
1928 return false;
1929 }
1930 }
1931 }
1932
1933 bool ret = openPathInternal(path);
1934
1935 if (autosaveOpened || flags & RecoveryFile) {
1936 setReadWrite(true); // enable save button
1937 setModified(true);
1938 setRecovered(true);
1939
1940 setPath(original); // since it was an autosave, it will be a local file
1941 setLocalFilePath(original);
1942 }
1943 else {
1944 if (ret) {
1945
1946 if (!(flags & DontAddToRecent)) {
1947 KisPart::instance()->addRecentURLToAllMainWindows(QUrl::fromLocalFile(_path));
1948 }
1949
1950 QFileInfo fi(_path);
1951 setReadWrite(fi.isWritable());
1952 }
1953
1954 setRecovered(false);
1955 }
1956
1957 return ret;
1958}
1959
1961{
1962 //dbgUI <<"for" << localFilePath();
1963 if (!QFile::exists(localFilePath()) && !fileBatchMode()) {
1964 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
1965 return false;
1966 }
1967
1968 QString filename = localFilePath();
1969 QString typeName = mimeType();
1970
1971 if (typeName.isEmpty()) {
1972 typeName = KisMimeDatabase::mimeTypeForFile(filename);
1973 }
1974
1975 // Allow to open backup files, don't keep the mimeType application/x-trash.
1976 if (typeName == "application/x-trash") {
1977 QString path = filename;
1978 while (path.length() > 0) {
1979 path.chop(1);
1980 typeName = KisMimeDatabase::mimeTypeForFile(path);
1981 //qDebug() << "\t" << path << typeName;
1982 if (!typeName.isEmpty()) {
1983 break;
1984 }
1985 }
1986 //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
1987 }
1988 dbgUI << localFilePath() << "type:" << typeName;
1989
1991 KoUpdaterPtr updater;
1992 if (window && window->viewManager()) {
1993 updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
1994 d->importExportManager->setUpdater(updater);
1995 }
1996
1997 KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName);
1998
1999 if (!status.isOk()) {
2000 if (window && window->viewManager()) {
2001 updater->cancel();
2002 }
2003 QString msg = status.errorMessage();
2004 KisUsageLogger::log(QString("Loading %1 failed: %2").arg(prettyPath(), msg));
2005
2006 if (!msg.isEmpty() && !fileBatchMode()) {
2007 DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
2008 i18n("Could not open %1.", prettyPath()),
2009 errorMessage().split("\n", Qt::SkipEmptyParts)
2010 + warningMessage().split("\n", Qt::SkipEmptyParts),
2011 msg);
2012
2013 dlg.exec();
2014 }
2015 return false;
2016 }
2017 else if (!warningMessage().isEmpty() && !fileBatchMode()) {
2018 DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
2019 i18n("There were problems opening %1.", prettyPath()),
2020 warningMessage().split("\n", Qt::SkipEmptyParts));
2021
2022 dlg.exec();
2023 setPath(QString());
2024 }
2025
2026 setMimeTypeAfterLoading(typeName);
2027 d->syncDecorationsWrapperLayerState();
2028 Q_EMIT sigLoadingFinished();
2029
2030 undoStack()->clear();
2031
2032 return true;
2033}
2034
2036{
2037 if (!d->modified || !d->modifiedAfterAutosave)
2038 return;
2039
2040 const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
2041
2042 bool started = exportDocumentSync(autoSaveFileName, nativeFormatMimeType());
2043
2044 if (started)
2045 {
2046 d->modifiedAfterAutosave = false;
2047 dbgAndroid << "autoSaveOnPause successful";
2048 }
2049 else
2050 {
2051 qWarning() << "Could not auto-save when paused";
2052 }
2053}
2054
2055// shared between openFile and koMainWindow's "create new empty document" code
2056void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
2057{
2058 d->mimeType = mimeType.toLatin1();
2059 d->outputMimeType = d->mimeType;
2060}
2061
2062
2063bool KisDocument::loadNativeFormat(const QString & file_)
2064{
2065 return openPath(file_);
2066}
2067
2069{
2070 if (mod) {
2071 updateEditingTime(false);
2072 }
2073
2078 if (d->isAutosaving || d->documentIsClosing)
2079 return;
2080
2081 //dbgUI<<" url:" << url.path();
2082 //dbgUI<<" mod="<<mod<<" MParts mod="<<KisParts::ReadWritePart::isModified()<<" isModified="<<isModified();
2083
2084 if (mod && !d->autoSaveTimer->isActive()) {
2085 // First change since last autosave -> start the autosave timer
2087 }
2088 d->modifiedAfterAutosave = mod;
2089 d->modifiedWhileSaving = mod;
2090
2091 if (!mod) {
2092 d->imageModifiedWithoutUndo = mod;
2093 }
2094
2095 if (mod == isModified())
2096 return;
2097
2098 d->modified = mod;
2099
2100 if (mod) {
2102 }
2103
2104 Q_EMIT modified(mod);
2105}
2106
2108{
2109 const bool changed = value != d->isRecovered;
2110
2111 d->isRecovered = value;
2112
2113 if (changed) {
2114 Q_EMIT sigRecoveredChanged(value);
2115 }
2116}
2117
2118bool KisDocument::isRecovered() const
2119{
2120 return d->isRecovered;
2121}
2122
2123void KisDocument::updateEditingTime(bool forceStoreElapsed)
2124{
2125 QDateTime now = QDateTime::currentDateTime();
2126 int firstModDelta = d->firstMod.secsTo(now);
2127 int lastModDelta = d->lastMod.secsTo(now);
2128
2129 if (lastModDelta > 30) {
2130 d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
2131 d->firstMod = now;
2132 } else if (firstModDelta > 60 || forceStoreElapsed) {
2133 d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
2134 d->firstMod = now;
2135 }
2136
2137 d->lastMod = now;
2138}
2139
2141{
2142 QString _url(path());
2143#ifdef Q_OS_WIN
2144 _url = QDir::toNativeSeparators(_url);
2145#endif
2146 return _url;
2147}
2148
2149// Get caption from document info (title(), in about page)
2151{
2152 QString c;
2153 const QString _url(QFileInfo(path()).fileName());
2154
2155 // if URL is empty...it is probably an unsaved file
2156 if (_url.isEmpty()) {
2157 c = " [" + i18n("Not Saved") + "] ";
2158 } else {
2159 c = _url; // Fall back to document URL
2160 }
2161
2162 return c;
2163}
2164
2165QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
2166{
2167 return createDomDocument("krita", tagName, version);
2168}
2169
2170//static
2171QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
2172{
2173 QDomImplementation impl;
2174 QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
2175 QDomDocumentType dtype = impl.createDocumentType(tagName,
2176 QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
2177 url);
2178 // The namespace URN doesn't need to include the version number.
2179 QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
2180 QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
2181 doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
2182 return doc;
2183}
2184
2185bool KisDocument::isNativeFormat(const QByteArray& mimeType) const
2186{
2188 return true;
2189 return extraNativeMimeTypes().contains(mimeType);
2190}
2191
2192void KisDocument::setErrorMessage(const QString& errMsg)
2193{
2194 d->lastErrorMessage = errMsg;
2195}
2196
2198{
2199 return d->lastErrorMessage;
2200}
2201
2202void KisDocument::setWarningMessage(const QString& warningMsg)
2203{
2204 d->lastWarningMessage = warningMsg;
2205}
2206
2208{
2209 return d->lastWarningMessage;
2210}
2211
2212
2213void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered)
2214{
2215 // Eliminate any auto-save file
2216 QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir
2217 if (QFile::exists(asf)) {
2218 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
2219 QFile::remove(asf);
2220 }
2221 asf = generateAutoSaveFileName(QString()); // and the one in $HOME
2222
2223 if (QFile::exists(asf)) {
2224 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
2225 QFile::remove(asf);
2226 }
2227
2228 QList<QRegularExpression> expressions;
2229
2230 expressions << QRegularExpression("^\\..+-autosave.kra$")
2231 << QRegularExpression("^.+-autosave.kra$");
2232
2233 Q_FOREACH(const QRegularExpression &rex, expressions) {
2234 if (wasRecovered &&
2235 !autosaveBaseName.isEmpty() &&
2236 rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() &&
2237 QFile::exists(autosaveBaseName)) {
2238
2239 KisUsageLogger::log(QString("Removing autosave file: %1").arg(autosaveBaseName));
2240 QFile::remove(autosaveBaseName);
2241 }
2242 }
2243}
2244
2246{
2247 return d->unit;
2248}
2249
2251{
2252 if (d->unit != unit) {
2253 d->unit = unit;
2254 Q_EMIT unitChanged(unit);
2255 }
2256}
2257
2259{
2260 return d->undoStack;
2261}
2262
2264{
2265 return d->importExportManager;
2266}
2267
2269{
2270 setModified(!value || d->imageModifiedWithoutUndo);
2271}
2272
2274{
2275 KisConfig cfg(true);
2276
2277 if (d->undoStack->undoLimit() != cfg.undoStackLimit()) {
2278 if (!d->undoStack->isClean()) {
2279 d->undoStack->clear();
2280 // we set this because the document *has* changed, even though the
2281 // undo history was purged.
2283 }
2284 d->undoStack->setUndoLimit(cfg.undoStackLimit());
2285 }
2286 d->undoStack->setUseCumulativeUndoRedo(cfg.useCumulativeUndoRedo());
2287 d->undoStack->setCumulativeUndoData(cfg.cumulativeUndoData());
2288
2289 d->autoSaveDelay = cfg.autoSaveInterval();
2291}
2292
2294{
2295 d->syncDecorationsWrapperLayerState();
2296}
2297
2299{
2300 d->undoStack->clear();
2301}
2302
2304{
2305 return d->gridConfig;
2306}
2307
2309{
2310 if (d->gridConfig != config) {
2311 d->gridConfig = config;
2312 d->syncDecorationsWrapperLayerState();
2313 Q_EMIT sigGridConfigChanged(config);
2314
2315 // Store last assigned value as future default...
2316 KisConfig cfg(false);
2317 cfg.setDefaultGridSpacing(config.spacing());
2318 }
2319}
2320
2322{
2324 if (!d->linkedResourceStorage) {
2325 return result;
2326 }
2327
2328 Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) {
2329 QSharedPointer<KisResourceStorage::ResourceIterator> iter = d->linkedResourceStorage->resources(resourceType);
2330 while (iter->hasNext()) {
2331 iter->next();
2332
2333 QBuffer buf;
2334 buf.open(QBuffer::WriteOnly);
2335 bool exportSuccessful =
2336 d->linkedResourceStorage->exportResource(iter->url(), &buf);
2337
2338 KoResourceSP resource = d->linkedResourceStorage->resource(iter->url());
2339 exportSuccessful &= bool(resource);
2340
2341 const QString name = resource ? resource->name() : QString();
2342 const QString fileName = QFileInfo(iter->url()).fileName();
2343 const KoResourceSignature signature(resourceType,
2344 KoMD5Generator::generateHash(buf.data()),
2345 fileName, name);
2346
2347 if (exportSuccessful) {
2348 result << KoEmbeddedResource(signature, buf.data());
2349 } else {
2350 result << signature;
2351 }
2352 }
2353 }
2354
2355 return result;
2356}
2357
2358void KisDocument::setPaletteList(const QList<KoColorSetSP > &paletteList, bool emitSignal)
2359{
2360 QList<KoColorSetSP> oldPaletteList;
2361 if (d->linkedResourceStorage) {
2362 QSharedPointer<KisResourceStorage::ResourceIterator> iter = d->linkedResourceStorage->resources(ResourceType::Palettes);
2363 while (iter->hasNext()) {
2364 iter->next();
2365 KoResourceSP resource = iter->resource();
2366 if (resource && resource->valid()) {
2367 oldPaletteList << resource.dynamicCast<KoColorSet>();
2368 }
2369 }
2370 if (oldPaletteList != paletteList) {
2372 Q_FOREACH(KoColorSetSP palette, oldPaletteList) {
2373 if (!paletteList.contains(palette)) {
2374 resourceModel.setResourceInactive(resourceModel.indexForResource(palette));
2375 }
2376 }
2377 Q_FOREACH(KoColorSetSP palette, paletteList) {
2378 if (!oldPaletteList.contains(palette)) {
2379 resourceModel.addResource(palette, d->linkedResourcesStorageID);
2380 }
2381 else {
2382 palette->setStorageLocation(d->linkedResourcesStorageID);
2383 resourceModel.updateResource(palette);
2384 }
2385 }
2386 if (emitSignal) {
2387 Q_EMIT sigPaletteListChanged(oldPaletteList, paletteList);
2388 }
2389 }
2390 }
2391}
2392
2394{
2395 return d->m_storyboardItemList;
2396}
2397
2398void KisDocument::setStoryboardItemList(const StoryboardItemList &storyboardItemList, bool emitSignal)
2399{
2400 d->m_storyboardItemList = storyboardItemList;
2401 if (emitSignal) {
2403 }
2404}
2405
2407{
2408 return d->m_storyboardCommentList;
2409}
2410
2411void KisDocument::setStoryboardCommentList(const QVector<StoryboardComment> &storyboardCommentList, bool emitSignal)
2412{
2413 d->m_storyboardCommentList = storyboardCommentList;
2414 if (emitSignal) {
2416 }
2417}
2418
2420 return d->audioTracks;
2421}
2422
2424{
2425 d->audioTracks = f;
2426 Q_EMIT sigAudioTracksChanged();
2427}
2428
2430{
2431 d->audioLevel = level;
2432 Q_EMIT sigAudioLevelChanged(level);
2433}
2434
2436{
2437 return d->audioLevel;
2438}
2439
2441{
2442 return d->guidesConfig;
2443}
2444
2446{
2447 if (d->guidesConfig == data) return;
2448
2449 d->guidesConfig = data;
2450 d->syncDecorationsWrapperLayerState();
2451 Q_EMIT sigGuidesConfigChanged(d->guidesConfig);
2452}
2453
2454
2456{
2457 return d->mirrorAxisConfig;
2458}
2459
2461{
2462 if (d->mirrorAxisConfig == config) {
2463 return;
2464 }
2465
2466 d->mirrorAxisConfig = config;
2467 if (d->image) {
2468 d->image->setMirrorAxesCenter(KisAlgebra2D::absoluteToRelative(d->mirrorAxisConfig.axisPosition(),
2469 d->image->bounds()));
2470 }
2471 setModified(true);
2472
2474}
2475
2477 setPath(QString());
2478 setLocalFilePath(QString());
2479}
2480
2482{
2483 return new KoDocumentInfoDlg(parent, docInfo);
2484}
2485
2487{
2488 return d->readwrite;
2489}
2490
2491QString KisDocument::path() const
2492{
2493 return d->m_path;
2494}
2495
2496bool KisDocument::closePath(bool promptToSave)
2497{
2498 if (promptToSave) {
2499 if ( isReadWrite() && isModified()) {
2500 Q_FOREACH (KisView *view, KisPart::instance()->views()) {
2501 if (view && view->document() == this) {
2502 if (!view->queryClose()) {
2503 return false;
2504 }
2505 }
2506 }
2507 }
2508 }
2509 // Not modified => ok and delete temp file.
2510 d->mimeType = QByteArray();
2511
2512 // It always succeeds for a read-only part,
2513 // but the return value exists for reimplementations
2514 // (e.g. pressing cancel for a modified read-write part)
2515 return true;
2516}
2517
2518
2519
2520void KisDocument::setPath(const QString &path)
2521{
2522 const bool changed = path != d->m_path;
2523
2524 d->m_path = path;
2525
2526 if (changed) {
2527 Q_EMIT sigPathChanged(path);
2528 }
2529}
2530
2532{
2533 return d->m_file;
2534}
2535
2536
2537void KisDocument::setLocalFilePath( const QString &localFilePath )
2538{
2539 d->m_file = localFilePath;
2540}
2541
2542bool KisDocument::openPathInternal(const QString &path)
2543{
2544 if ( path.isEmpty() ) {
2545 return false;
2546 }
2547
2548 if (d->m_bAutoDetectedMime) {
2549 d->mimeType = QByteArray();
2550 d->m_bAutoDetectedMime = false;
2551 }
2552
2553 QByteArray mimeType = d->mimeType;
2554
2555 if ( !closePath() ) {
2556 return false;
2557 }
2558
2559 d->mimeType = mimeType;
2560 setPath(path);
2561
2562 d->m_file.clear();
2563
2564 d->m_file = d->m_path;
2565
2566 bool ret = false;
2567 // set the mimeType only if it was not already set (for example, by the host application)
2568 if (d->mimeType.isEmpty()) {
2569 // get the mimeType of the file
2570 // using findByUrl() to avoid another string -> url conversion
2571 QString mime = KisMimeDatabase::mimeTypeForFile(d->m_path);
2572 d->mimeType = mime.toLocal8Bit();
2573 d->m_bAutoDetectedMime = true;
2574 }
2575
2576 setPath(d->m_path);
2577 ret = openFile();
2578
2579 if (ret) {
2580 Q_EMIT completed();
2581 }
2582 else {
2583 Q_EMIT canceled(QString());
2584 }
2585 return ret;
2586}
2587
2588bool KisDocument::newImage(const QString& name,
2589 qint32 width, qint32 height,
2590 const KoColorSpace* cs,
2591 const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle,
2592 int numberOfLayers,
2593 const QString &description, const double imageResolution)
2594{
2595 Q_ASSERT(cs);
2596
2598
2599 if (!cs) return false;
2600
2601 KisCursorOverrideLock cursorLock(Qt::BusyCursor);
2602
2603 image = new KisImage(createUndoStore(), width, height, cs, name);
2604
2605 Q_CHECK_PTR(image);
2606
2607 connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
2608 connect(image, SIGNAL(sigImageModifiedWithoutUndo()), this, SLOT(setImageModifiedWithoutUndo()), Qt::UniqueConnection);
2609 image->setResolution(imageResolution, imageResolution);
2610
2612 image->waitForDone();
2613
2614 documentInfo()->setAboutInfo("title", name);
2615 documentInfo()->setAboutInfo("abstract", description);
2616
2617 KisConfig cfg(false);
2618 cfg.defImageWidth(width);
2619 cfg.defImageHeight(height);
2620 cfg.defImageResolution(imageResolution);
2621 if (!cfg.useDefaultColorSpace())
2622 {
2626 }
2627
2628 bool autopin = cfg.autoPinLayersToTimeline();
2629
2630 KisLayerSP bgLayer;
2631 if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) {
2632 KoColor strippedAlpha = bgColor;
2633 strippedAlpha.setOpacity(OPACITY_OPAQUE_U8);
2634
2635 if (bgStyle == KisConfig::RASTER_LAYER) {
2636 bgLayer = new KisPaintLayer(image.data(), i18nc("Name for the bottom-most layer in the layerstack", "Background"), OPACITY_OPAQUE_U8, cs);
2637 bgLayer->paintDevice()->setDefaultPixel(strippedAlpha);
2638 bgLayer->setPinnedToTimeline(autopin);
2639 } else if (bgStyle == KisConfig::FILL_LAYER) {
2641 filter_config->setProperty("color", strippedAlpha.toQColor());
2642 filter_config->createLocalResourcesSnapshot();
2643 bgLayer = new KisGeneratorLayer(image.data(), i18nc("Name of automatically created background color fill layer", "Background Fill"), filter_config, image->globalSelection());
2644 }
2645
2646 bgLayer->setOpacity(bgColor.opacityU8());
2647
2648 if (numberOfLayers > 1) {
2649 //Lock bg layer if others are present.
2650 bgLayer->setUserLocked(true);
2651 }
2652 }
2653 else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer).
2655 bgLayer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
2656 }
2657
2658 Q_CHECK_PTR(bgLayer);
2659 image->addNode(bgLayer.data(), image->rootLayer().data());
2660 bgLayer->setDirty(QRect(0, 0, width, height));
2661
2662 // reset mirror axis to default:
2663 d->mirrorAxisConfig.setAxisPosition(QRectF(image->bounds()).center());
2665
2666 for(int i = 1; i < numberOfLayers; ++i) {
2668 layer->setPinnedToTimeline(autopin);
2669 image->addNode(layer, image->root(), i);
2670 layer->setDirty(QRect(0, 0, width, height));
2671 }
2672
2673 {
2675 if (window) {
2680 }
2681 }
2682
2684 QString("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8")
2685 .arg(name, QString::number(width), QString::number(height),
2686 QString::number(imageResolution * 72.0), image->colorSpace()->colorModelId().name(),
2688 QString::number(numberOfLayers)));
2689
2690 return true;
2691}
2692
2694{
2695 const bool result = d->savingMutex.tryLock();
2696 if (result) {
2697 d->savingMutex.unlock();
2698 }
2699 return !result;
2700}
2701
2703{
2704 if (isSaving()) {
2705 KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0);
2706 f.waitForMutex(d->savingMutex);
2707 }
2708}
2709
2711{
2712 return d->shapeController;
2713}
2714
2716{
2717 return d->shapeController->shapeForNode(layer);
2718}
2719
2721{
2722 return d->assistants;
2723}
2724
2726{
2727 if (d->assistants != value) {
2728 d->assistants = value;
2729 d->syncDecorationsWrapperLayerState();
2730 Q_EMIT sigAssistantsChanged();
2731 }
2732}
2733
2735{
2736 if (!d->image) return KisReferenceImagesLayerSP();
2737
2738 KisReferenceImagesLayerSP referencesLayer =
2739 KisLayerUtils::findNodeByType<KisReferenceImagesLayer>(d->image->root());
2740
2741 return referencesLayer;
2742}
2743
2745{
2746 KisReferenceImagesLayerSP currentReferenceLayer = referenceImagesLayer();
2747
2748 // updateImage=false inherently means we are not changing the
2749 // reference images layer, but just would like to update its signals.
2750 if (currentReferenceLayer == layer && updateImage) {
2751 return;
2752 }
2753
2754 d->referenceLayerConnections.clear();
2755
2756 if (updateImage) {
2757 if (currentReferenceLayer) {
2758 d->image->removeNode(currentReferenceLayer);
2759 }
2760
2761 if (layer) {
2762 d->image->addNode(layer);
2763 }
2764 }
2765
2766 currentReferenceLayer = layer;
2767
2768 if (currentReferenceLayer) {
2769 d->referenceLayerConnections.addConnection(
2770 currentReferenceLayer, SIGNAL(sigUpdateCanvas(QRectF)),
2771 this, SIGNAL(sigReferenceImagesChanged()));
2772 }
2773
2774 Q_EMIT sigReferenceImagesLayerChanged(layer);
2776}
2777
2779{
2780 d->preActivatedNode = activatedNode;
2781}
2782
2784{
2785 return d->preActivatedNode;
2786}
2787
2789{
2790 return d->image;
2791}
2792
2794{
2795 return d->savingImage;
2796}
2797
2798
2799void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate, KisNodeSP preActivatedNode)
2800{
2801 if (d->image) {
2802 // Disconnect existing sig/slot connections
2803 d->image->setUndoStore(new KisDumbUndoStore());
2804 d->image->disconnect(this);
2805 d->shapeController->setImage(0);
2806 d->image = 0;
2807 }
2808
2809 if (!image) return;
2810
2811 if (d->linkedResourceStorage){
2812 d->linkedResourceStorage->setMetaData(KisResourceStorage::s_meta_name, image->objectName());
2813 }
2814
2815 d->setImageAndInitIdleWatcher(image);
2816 d->image->setUndoStore(new KisDocumentUndoStore(this));
2817 d->shapeController->setImage(image, preActivatedNode);
2818 d->image->setMirrorAxesCenter(KisAlgebra2D::absoluteToRelative(d->mirrorAxisConfig.axisPosition(), image->bounds()));
2819 setModified(false);
2820 connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
2821 connect(d->image, SIGNAL(sigImageModifiedWithoutUndo()), this, SLOT(setImageModifiedWithoutUndo()), Qt::UniqueConnection);
2822 connect(d->image, SIGNAL(sigLayersChangedAsync()), this, SLOT(slotImageRootChanged()));
2823
2824 if (forceInitialUpdate) {
2825 d->image->initialRefreshGraph();
2826 }
2827}
2828
2830{
2832
2833 // we set image without connecting idle-watcher, because loading
2834 // hasn't been finished yet
2835 d->image = image;
2836 d->shapeController->setImage(image);
2837}
2838
2840{
2841 // we only set as modified if undo stack is not at clean state
2842 setModified(d->imageModifiedWithoutUndo || !d->undoStack->isClean());
2843}
2844
2846{
2847 d->imageModifiedWithoutUndo = true;
2849}
2850
2851
2856
2857bool KisDocument::isAutosaving() const
2858{
2859 return d->isAutosaving;
2860}
2861
2862QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage)
2863{
2864 return errorMessage.isEmpty() ? status.errorMessage() : errorMessage;
2865}
2866
2868{
2869 d->globalAssistantsColor = color;
2870}
2871
2873{
2874 return d->globalAssistantsColor;
2875}
2876
2878{
2879 return d->colorHistory;
2880}
2881
2883{
2884 QRectF bounds = d->image->bounds();
2885
2887
2888 if (referenceImagesLayer) {
2890 }
2891
2892 return bounds;
2893}
2894
2896{
2897 d->colorHistory = colors;
2898}
float value(const T *src, size_t ch)
KisSharedPtr< KisReferenceImagesLayer > KisReferenceImagesLayerSP
const quint8 OPACITY_OPAQUE_U8
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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:789
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
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)
bool startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting=false)
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:598
static KisPart * instance()
Definition KisPart.cpp:131
KisMainWindow * currentMainwindow() const
Definition KisPart.cpp:483
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:1159
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