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 , nserver(new KisNameServer(*rhs.nserver))
329 , preActivatedNode(0) // the node is from another hierarchy!
330 , imageIdleWatcher(2000 /*ms*/)
331 , colorHistory(rhs.colorHistory)
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;
410 QMetaObject::Connection completeSavingConnection;
412
413 bool isRecovered = false;
414
415 bool batchMode { false };
416 bool decorationsSyncingDisabled = false;
417 bool wasStorageAdded = false;
418 bool documentIsClosing = false;
419
420 // Resources saved in the .kra document
423
424 // Resources saved into other components of the kra file
427
429
431 image = _image;
432
433 imageIdleWatcher.setTrackedImage(image);
434 }
435
436 void copyFrom(const Private &rhs, KisDocument *q);
438
440 KisDocument* lockAndCloneImpl(bool fetchResourcesFromLayers);
441
442 void updateDocumentMetadataOnSaving(const QString &filePath, const QByteArray &mimeType);
443
446 class StrippedSafeSavingLocker;
447};
448
449
450void KisDocument::Private::syncDecorationsWrapperLayerState()
451{
452 if (!this->image || this->decorationsSyncingDisabled) return;
453
454 KisImageSP image = this->image;
455 KisDecorationsWrapperLayerSP decorationsLayer =
456 KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(image->root());
457
458 const bool needsDecorationsWrapper =
459 gridConfig.showGrid() || (guidesConfig.showGuides() && guidesConfig.hasGuides()) || !assistants.isEmpty();
460
461 struct SyncDecorationsWrapperStroke : public KisSimpleStrokeStrategy {
462 SyncDecorationsWrapperStroke(KisDocument *document, bool needsDecorationsWrapper)
463 : KisSimpleStrokeStrategy(QLatin1String("sync-decorations-wrapper"),
464 kundo2_noi18n("start-isolated-mode")),
465 m_document(document),
466 m_needsDecorationsWrapper(needsDecorationsWrapper)
467 {
471 }
472
473 void initStrokeCallback() override {
474 KisDecorationsWrapperLayerSP decorationsLayer =
475 KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(m_document->image()->root());
476
477 if (m_needsDecorationsWrapper && !decorationsLayer) {
478 m_document->image()->addNode(new KisDecorationsWrapperLayer(m_document));
479 } else if (!m_needsDecorationsWrapper && decorationsLayer) {
480 m_document->image()->removeNode(decorationsLayer);
481 }
482 }
483
484 private:
485 KisDocument *m_document = 0;
486 bool m_needsDecorationsWrapper = false;
487 };
488
489 KisStrokeId id = image->startStroke(new SyncDecorationsWrapperStroke(q, needsDecorationsWrapper));
490 image->endStroke(id);
491}
492
493void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q)
494{
495 copyFromImpl(rhs, q, KisDocument::REPLACE);
496}
497
498void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy)
499{
500 if (policy == REPLACE) {
501 delete docInfo;
502 }
503 docInfo = (new KoDocumentInfo(*rhs.docInfo, q));
504 unit = rhs.unit;
505 mimeType = rhs.mimeType;
506 outputMimeType = rhs.outputMimeType;
507
508 if (policy == REPLACE) {
509 q->setGuidesConfig(rhs.guidesConfig);
510 q->setMirrorAxisConfig(rhs.mirrorAxisConfig);
511 q->setModified(rhs.modified);
514 q->setStoryboardCommentList(rhs.m_storyboardCommentList);
515 q->setAudioTracks(rhs.audioTracks);
516 q->setAudioVolume(rhs.audioLevel);
517 q->setGridConfig(rhs.gridConfig);
518 } else {
519 // in CONSTRUCT mode, we cannot use the functions of KisDocument
520 // because KisDocument does not yet have a pointer to us.
521 guidesConfig = rhs.guidesConfig;
522 mirrorAxisConfig = rhs.mirrorAxisConfig;
523 modified = rhs.modified;
524 assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants);
525 m_storyboardItemList = StoryboardItem::cloneStoryboardItemList(rhs.m_storyboardItemList);
526 m_storyboardCommentList = rhs.m_storyboardCommentList;
527 audioTracks = rhs.audioTracks;
528 audioLevel = rhs.audioLevel;
529 gridConfig = rhs.gridConfig;
530 }
531 imageModifiedWithoutUndo = rhs.imageModifiedWithoutUndo;
532 m_bAutoDetectedMime = rhs.m_bAutoDetectedMime;
533 m_path = rhs.m_path;
534 m_file = rhs.m_file;
535 readwrite = rhs.readwrite;
536 autoSaveActive = rhs.autoSaveActive;
537 firstMod = rhs.firstMod;
538 lastMod = rhs.lastMod;
539 // XXX: the display properties will be shared between different snapshots
540 globalAssistantsColor = rhs.globalAssistantsColor;
541 batchMode = rhs.batchMode;
542
543
544 if (rhs.linkedResourceStorage) {
545 linkedResourceStorage = rhs.linkedResourceStorage->clone();
546 }
547
548 if (rhs.embeddedResourceStorage) {
549 embeddedResourceStorage = rhs.embeddedResourceStorage->clone();
550 }
551
552}
553
555public:
556 StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
557 : m_locked(false)
558 , m_image(image)
559 , m_savingLock(savingMutex)
560 , m_imageLock(image, std::defer_lock)
561
562 {
572 m_locked = std::try_lock(m_imageLock, *m_savingLock) < 0;
573
574 if (!m_locked) {
576 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
577
578 // one more try...
579 m_locked = std::try_lock(m_imageLock, *m_savingLock) < 0;
580 }
581 }
582
584 if (m_locked) {
585 m_imageLock.unlock();
586 m_savingLock->unlock();
587 }
588 }
589
590 bool successfullyLocked() const {
591 return m_locked;
592 }
593
594private:
595 Q_DISABLE_COPY_MOVE(StrippedSafeSavingLocker)
596
597 bool m_locked;
598 KisImageSP m_image;
599 QMutex *m_savingLock;
600 KisImageReadOnlyBarrierLock m_imageLock;
601};
602
603KisDocument::KisDocument(bool addStorage)
604 : d(new Private(this))
605{
606 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
607 connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
608 connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
609 setObjectName(newObjectName());
610
611#ifdef Q_OS_MACOS
613 if (bookmarkmngr->isSandboxed()) {
614 connect(this, SIGNAL(sigSavingFinished(const QString&)), bookmarkmngr, SLOT(slotCreateBookmark(const QString&)));
615 }
616#endif
617
618
619 if (addStorage) {
620 d->linkedResourcesStorageID = QUuid::createUuid().toString();
621 d->linkedResourceStorage.reset(new KisResourceStorage(d->linkedResourcesStorageID));
622 KisResourceLocator::instance()->addStorage(d->linkedResourcesStorageID, d->linkedResourceStorage);
623
624 d->embeddedResourcesStorageID = QUuid::createUuid().toString();
625 d->embeddedResourceStorage.reset(new KisResourceStorage(d->embeddedResourcesStorageID));
626 KisResourceLocator::instance()->addStorage(d->embeddedResourcesStorageID, d->embeddedResourceStorage);
627
628 d->wasStorageAdded = true;
629 }
630
631 // preload the krita resources
633
634 d->shapeController = new KisShapeController(d->nserver, d->undoStack, this);
635 d->koShapeController = new KoShapeController(0, d->shapeController);
636
637 slotConfigChanged();
638}
639
640KisDocument::KisDocument(const KisDocument &rhs, bool addStorage)
641 : QObject(),
642 d(new Private(*rhs.d, this))
643{
645
646 if (addStorage) {
647 KisResourceLocator::instance()->addStorage(d->linkedResourcesStorageID, d->linkedResourceStorage);
648 KisResourceLocator::instance()->addStorage(d->embeddedResourcesStorageID, d->embeddedResourceStorage);
649 d->wasStorageAdded = true;
650 }
651}
652
654{
655 d->documentIsClosing = true;
656
657 // wait until all the pending operations are in progress
659 d->imageIdleWatcher.setTrackedImage(0);
660
666
667 d->autoSaveTimer->disconnect(this);
668 d->autoSaveTimer->stop();
669
670 delete d->importExportManager;
671
672 // Despite being QObject they needs to be deleted before the image
673 delete d->shapeController;
674
675 delete d->koShapeController;
676
677 if (d->image) {
678 d->image->animationInterface()->blockBackgroundFrameGeneration();
679
680 d->image->notifyAboutToBeDeleted();
681
694 d->image->requestStrokeCancellation();
695 d->image->waitForDone();
696
697 // clear undo commands that can still point to the image
698 d->undoStack->clear();
699 d->image->waitForDone();
700
701 KisImageWSP sanityCheckPointer = d->image;
702 Q_UNUSED(sanityCheckPointer);
703
704 // The following line trigger the deletion of the image
705 d->image.clear();
706
707 // check if the image has actually been deleted
708 KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
709 }
710
711 if (d->wasStorageAdded) {
712 if (KisResourceLocator::instance()->hasStorage(d->linkedResourcesStorageID)) {
713 KisResourceLocator::instance()->removeStorage(d->linkedResourcesStorageID);
714 }
715 if (KisResourceLocator::instance()->hasStorage(d->embeddedResourcesStorageID)) {
716 KisResourceLocator::instance()->removeStorage(d->embeddedResourcesStorageID);
717 }
718 }
719
720 delete d;
721}
722
724{
725 return d->embeddedResourcesStorageID;
726}
727
729{
730 return d->linkedResourcesStorageID;
731}
732
734{
735 return new KisDocument(*this, addStorage);
736}
737
738bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting)
739{
740 // ANDROID NOTES (other comments in this file reference this one!)
741 //
742 // The Android file system doesn't work like on a real operating system.
743 // Instead of normal file paths, we get to deal with "content URIs", which
744 // can have various kinds of storage providers behind them. For example,
745 // there's a "normal" storage provider, a slightly less normal documents
746 // provider, a Google Drive provider and various kinds of third-party
747 // providers that are mostly just good at losing the data you give them.
748 // For example, we have reports of compression programs that provide a
749 // storage provider to write to ZIP or RAR archives or something. Except
750 // that they don't seem to work at all, they just accept the data with no
751 // error and throw it on the floor.
752 //
753 // The providers are particularly unreliable with regards to permissions,
754 // so calling "isWritable" will just always return false on some of them.
755 // This includes the default provider on some devices, which means checking
756 // whether a file is writable will mean that the user can't save anything!
757 // So, all the Android code skips over the writability check and assumes the
758 // files are writable. If they're not, we'll notice later anyway, by the
759 // fact that writing to them fails.
760 //
761 // Another issue is that it's only possible to overwrite files using File >
762 // Save. Using File > Save As or File > Export can't replace existing
763 // files. The reason for this is that we have to go through the operating
764 // system to request access to a file and the only things you can ask for
765 // is to open an existing file or to create a new file. Out of necessity,
766 // Save As and Export use the latter. If the user selects an existing file,
767 // the operating system "helpfully" appends a number to the path, *after*
768 // the file extension of course, because it hates the living. So that's
769 // another reason we can't go with the "safer" option of assuming that files
770 // aren't writable in some cases, since that would prevent the user from
771 // saving them normally and end up with a lot of "kiki.kra (2)".
772 //
773 // Also, when you request a file from the operating system, it always
774 // creates an empty file. That means checking whether a file exists will
775 // pretty much always succeed, so to know whether a file actually exists
776 // you have to check whether it isn't empty. Of course providers may fail to
777 // implement this correctly, but I'm not aware of any of them botching it
778 // that hard. Well, at least none of the ones that actually save files, as
779 // mentioned above some of them just seem to lose whatever you give them.
780
781 QFileInfo filePathInfo(job.filePath);
782 bool fileExists = filePathInfo.exists();
783#ifdef Q_OS_ANDROID
784 if (fileExists) {
785 fileExists = filePathInfo.size() > 0;
786 }
787#else
788 if (fileExists && !filePathInfo.isWritable()) {
790 i18n("%1 cannot be written to. Please save under a different name.", job.filePath),
791 "");
792 return false;
793 }
794#endif
795
796 KisConfig cfg(true);
797 if (cfg.backupFile() && fileExists) {
798
799 QString backupDir;
800
801 switch(cfg.readEntry<int>("backupfilelocation", 0)) {
802 case 1:
803 backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
804 break;
805 case 2:
806 backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
807 break;
808 default:
809#ifdef Q_OS_ANDROID
810 // We deal with URIs, there may or may not be a "directory"
812 QDir().mkpath(backupDir);
813#endif
814
815#ifdef Q_OS_MACOS
817 if (bookmarkmngr->isSandboxed()) {
818 // If the user does not have directory permission force backup
819 // files to be inside Container tmp
820 QUrl fileUrl = QUrl::fromLocalFile(job.filePath);
821 if( !bookmarkmngr->parentDirHasPermissions(fileUrl.path()) ) {
822 backupDir = QDir::tempPath();
823 }
824 }
825#endif
826
827 // Do nothing: the empty string is user file location
828 break;
829 }
830
831 int numOfBackupsKept = cfg.readEntry<int>("numberofbackupfiles", 1);
832 QString suffix = cfg.readEntry<QString>("backupfilesuffix", "~");
833
834 if (numOfBackupsKept == 1) {
835 if (!KisBackup::simpleBackupFile(job.filePath, backupDir, suffix)) {
836 qWarning() << "Failed to create simple backup file!" << job.filePath << backupDir << suffix;
837 KisUsageLogger::log(QString("Failed to create a simple backup for %1 in %2.")
838 .arg(job.filePath, backupDir.isEmpty()
839 ? "the same location as the file"
840 : backupDir));
841 slotCompleteSavingDocument(job, ImportExportCodes::ErrorWhileWriting, i18nc("Saving error message", "Failed to create a backup file"), "");
842 return false;
843 }
844 else {
845 KisUsageLogger::log(QString("Create a simple backup for %1 in %2.")
846 .arg(job.filePath, backupDir.isEmpty()
847 ? "the same location as the file"
848 : backupDir));
849 }
850 }
851 else if (numOfBackupsKept > 1) {
852 if (!KisBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept)) {
853 qWarning() << "Failed to create numbered backup file!" << job.filePath << backupDir << suffix;
854 KisUsageLogger::log(QString("Failed to create a numbered backup for %2.")
855 .arg(job.filePath, backupDir.isEmpty()
856 ? "the same location as the file"
857 : backupDir));
858 slotCompleteSavingDocument(job, ImportExportCodes::ErrorWhileWriting, i18nc("Saving error message", "Failed to create a numbered backup file"), "");
859 return false;
860 }
861 else {
862 KisUsageLogger::log(QString("Create a simple backup for %1 in %2.")
863 .arg(job.filePath, backupDir.isEmpty()
864 ? "the same location as the file"
865 : backupDir));
866 }
867 }
868 }
869
870 //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
871 if (job.mimeType.isEmpty()) {
873 slotCompleteSavingDocument(job, error, error.errorMessage(), "");
874 return false;
875
876 }
877
878 const QString actionName =
880 i18n("Exporting Document...") :
881 i18n("Saving Document...");
882
886 job, exportConfiguration, isAdvancedExporting);
887
889 QString errorShortLog;
890 QString errorMessage;
892
893 switch (result) {
895 errorShortLog = "another save operation is in progress";
896 errorMessage = i18n("Could not start saving %1. Wait until the current save operation has finished.", job.filePath);
897 errorCode = ImportExportCodes::Failure;
898 break;
900 errorShortLog = "failed to lock and clone the image";
901 errorMessage = i18n("Could not start saving %1. Image is busy", job.filePath);
902 errorCode = ImportExportCodes::Busy;
903 break;
905 errorShortLog = "failed to start background saving";
906 errorMessage = i18n("Could not start saving %1. Unknown failure has happened", job.filePath);
907 errorCode = ImportExportCodes::Failure;
908 break;
911 break;
913 // noop, not possible
914 break;
915 }
916
917 KisUsageLogger::log(QString("Failed to initiate saving %1 in background: %2").arg(job.filePath).arg(errorShortLog));
918
919 slotCompleteSavingDocument(job, errorCode,
921 "");
922 return false;
923 }
924
926}
927
928bool KisDocument::exportDocument(const QString &path, const QByteArray &mimeType, bool isAdvancedExporting, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
929{
930 using namespace KritaUtils;
931
932 SaveFlags flags = SaveIsExporting;
933 if (showWarnings) {
934 flags |= SaveShowWarnings;
935 }
936
937 KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 "
938 "framerate. Export configuration: %8")
939 .arg(path, QString::fromLatin1(mimeType), QString::number(d->image->width()),
940 QString::number(d->image->height()), QString::number(d->image->nlayers()),
941 QString::number(d->image->animationInterface()->totalLength()),
942 QString::number(d->image->animationInterface()->framerate()),
943 (exportConfiguration ? exportConfiguration->toXML() : "No configuration")));
944
946 mimeType,
947 flags),
948 exportConfiguration, isAdvancedExporting);
949}
950
951bool KisDocument::saveAs(const QString &_path, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
952{
953 using namespace KritaUtils;
954
955 KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, "
956 "%7 framerate. Export configuration: %8")
957 .arg(_path, QString::fromLatin1(mimeType), QString::number(d->image->width()),
958 QString::number(d->image->height()), QString::number(d->image->nlayers()),
959 QString::number(d->image->animationInterface()->totalLength()),
960 QString::number(d->image->animationInterface()->framerate()),
961 (exportConfiguration ? exportConfiguration->toXML() : "No configuration"),
962 path()));
963
964 // Check whether it's an existing resource were are saving to
965 if (resourceSavingFilter(_path, mimeType, exportConfiguration)) {
966 return true;
967 }
968
970 mimeType,
971 showWarnings ? SaveShowWarnings : SaveNone),
972 exportConfiguration);
973}
974
975bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
976{
977 return saveAs(path(), mimeType(), showWarnings, exportConfiguration);
978}
979
981{
982 QBuffer buffer;
983
985 filter->setBatchMode(true);
986 filter->setMimeType(nativeFormatMimeType());
987
988 Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
989 if (!locker.successfullyLocked()) {
990 return buffer.data();
991 }
992
993 d->savingImage = d->image;
994
995 if (!filter->convert(this, &buffer).isOk()) {
996 qWarning() << "serializeToByteArray():: Could not export to our native format";
997 }
998
999 return buffer.data();
1000}
1001
1002class DlgLoadMessages : public QMessageBox
1003{
1004public:
1005 DlgLoadMessages(const QString &title,
1006 const QString &message,
1007 const QStringList &warnings = {},
1008 const QString &details = {})
1009 : QMessageBox(QMessageBox::Warning, title, message, QMessageBox::Ok, qApp->activeWindow())
1010 {
1011 if (!details.isEmpty()) {
1012 setInformativeText(details);
1013 }
1014 if (!warnings.isEmpty()) {
1015 setDetailedText(warnings);
1016 }
1017 }
1018
1019private:
1021 {
1022 QMessageBox::setDetailedText(text.first());
1023
1024 QTextEdit *messageBox = findChild<QTextEdit *>();
1025
1026 if (messageBox) {
1027 messageBox->setAcceptRichText(true);
1028
1029 QString warning = "<html><body><ul>";
1030 Q_FOREACH (const QString &i, text) {
1031 warning += "\n<li>" + i + "</li>";
1032 }
1033 warning += "</ul></body></html>";
1034
1035 messageBox->setText(warning);
1036 }
1037 }
1038};
1039
1040void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
1041{
1042 if (status.isCancelled())
1043 return;
1044
1045 const QString fileName = QFileInfo(job.filePath).fileName();
1046
1047 if (!status.isOk()) {
1048 Q_EMIT statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
1049 "Error during saving %1: %2",
1050 fileName,
1051 errorMessage), errorMessageTimeout);
1052
1053
1054 if (!fileBatchMode()) {
1055 DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
1056 i18n("Could not save %1.", job.filePath),
1057 errorMessage.split("\n", Qt::SkipEmptyParts)
1058 + warningMessage.split("\n", Qt::SkipEmptyParts),
1059 status.errorMessage());
1060
1061 dlg.exec();
1062 }
1063 }
1064 else {
1065 if (!fileBatchMode() && !warningMessage.isEmpty()) {
1066
1067 QStringList reasons = warningMessage.split("\n", Qt::SkipEmptyParts);
1068
1069 DlgLoadMessages dlg(
1070 i18nc("@title:window", "Krita"),
1071 i18nc("dialog box shown to the user if there were warnings while saving the document, "
1072 "%1 is the file path",
1073 "%1 has been saved but is incomplete.",
1074 job.filePath),
1075 reasons,
1076 reasons.isEmpty()
1077 ? ""
1078 : i18nc("dialog box shown to the user if there were warnings while saving the document",
1079 "Some problems were encountered when saving."));
1080 dlg.exec();
1081 }
1082
1083
1084 if (!(job.flags & KritaUtils::SaveIsExporting)) {
1085 const QString existingAutoSaveBaseName = localFilePath();
1086 const bool wasRecovered = isRecovered();
1087
1088 d->updateDocumentMetadataOnSaving(job.filePath, job.mimeType);
1089
1090 removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered);
1091 }
1092
1093 Q_EMIT completed();
1094 Q_EMIT sigSavingFinished(job.filePath);
1095
1096 Q_EMIT statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
1097 }
1098}
1099
1100void KisDocument::Private::updateDocumentMetadataOnSaving(const QString &filePath, const QByteArray &mimeType)
1101{
1102 q->setPath(filePath);
1103 q->setLocalFilePath(filePath);
1104 q->setMimeType(mimeType);
1105 q->updateEditingTime(true);
1106
1107#ifdef Q_OS_ANDROID
1108 // See the comment titled "ANDROID NOTES" in this file for an explanation of
1109 // what this is about. (This is not that comment.)
1110 q->setReadWrite(true);
1111#else
1112 QFileInfo fi(filePath);
1113 q->setReadWrite(fi.isWritable());
1114#endif
1115
1116 if (!modifiedWhileSaving) {
1123 if (undoStack->isClean()) {
1124 q->setModified(false);
1125 } else {
1126 imageModifiedWithoutUndo = false;
1127 undoStack->setClean();
1128 }
1129 }
1130 q->setRecovered(false);
1131}
1132
1133QByteArray KisDocument::mimeType() const
1134{
1135 return d->mimeType;
1136}
1137
1138void KisDocument::setMimeType(const QByteArray & mimeType)
1139{
1140 d->mimeType = mimeType;
1141}
1142
1144{
1145 return d->batchMode;
1146}
1147
1148void KisDocument::setFileBatchMode(const bool batchMode)
1149{
1150 d->batchMode = batchMode;
1151}
1152
1153void KisDocument::Private::uploadLinkedResourcesFromLayersToStorage()
1154{
1159
1160 KisDocument *doc = q;
1161
1163 [doc] (KisNodeSP node) {
1164 if (KisNodeFilterInterface *layer = dynamic_cast<KisNodeFilterInterface*>(node.data())) {
1165 KisFilterConfigurationSP filterConfig = layer->filter();
1166 if (!filterConfig) return;
1167
1168 QList<KoResourceLoadResult> linkedResources = filterConfig->linkedResources(KisGlobalResourcesInterface::instance());
1169
1170 Q_FOREACH (const KoResourceLoadResult &result, linkedResources) {
1171 KIS_SAFE_ASSERT_RECOVER(result.type() != KoResourceLoadResult::EmbeddedResource) { continue; }
1172
1173 KoResourceSP resource = result.resource();
1174
1175 if (!resource) {
1176 qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to fetch a resource" << result.signature();
1177 continue;
1178 }
1179
1180 QBuffer buf;
1181 buf.open(QBuffer::WriteOnly);
1182
1183 KisResourceModel model(resource->resourceType().first);
1184 bool res = model.exportResource(resource, &buf);
1185
1186 buf.close();
1187
1188 if (!res) {
1189 qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to export resource" << result.signature();
1190 continue;
1191 }
1192
1193 buf.open(QBuffer::ReadOnly);
1194
1195 res = doc->d->linkedResourceStorage->importResource(resource->resourceType().first + "/" + resource->filename(), &buf);
1196
1197 buf.close();
1198
1199 if (!res) {
1200 qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to import resource" << result.signature();
1201 continue;
1202 }
1203 }
1204
1205 }
1206 });
1207}
1208
1209KisDocument *KisDocument::Private::lockAndCloneImpl(bool fetchResourcesFromLayers)
1210{
1211 // force update of all the asynchronous nodes before cloning
1212 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1214
1216 if (window) {
1217 if (window->viewManager()) {
1218 if (!window->viewManager()->blockUntilOperationsFinished(image)) {
1219 return 0;
1220 }
1221 }
1222 }
1223
1224 Private::StrippedSafeSavingLocker locker(&savingMutex, image);
1225 if (!locker.successfullyLocked()) {
1226 return 0;
1227 }
1228
1229 KisDocument *doc = new KisDocument(*this->q, false);
1230
1231 if (fetchResourcesFromLayers) {
1232 doc->d->uploadLinkedResourcesFromLayersToStorage();
1233 }
1234
1235 return doc;
1236}
1237
1239{
1240 return d->lockAndCloneImpl(true);
1241}
1242
1244{
1245 return d->lockAndCloneImpl(false);
1246}
1247
1252
1254{
1255 if (policy == REPLACE) {
1256 d->decorationsSyncingDisabled = true;
1257 d->copyFrom(*(rhs.d), this);
1258 d->decorationsSyncingDisabled = false;
1259
1260 d->undoStack->clear();
1261 } else {
1262 // in CONSTRUCT mode, d should be already initialized
1263 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
1264 connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
1265 connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
1266
1267 d->shapeController = new KisShapeController(d->nserver, d->undoStack, this);
1268 d->koShapeController = new KoShapeController(0, d->shapeController);
1269 }
1270
1271 setObjectName(rhs.objectName());
1272
1274
1275 if (rhs.d->image) {
1276 if (policy == REPLACE) {
1277 d->image->barrierLock(/* readOnly = */ false);
1278 rhs.d->image->barrierLock(/* readOnly = */ true);
1279 d->image->copyFromImage(*(rhs.d->image));
1280 d->image->unlock();
1281 rhs.d->image->unlock();
1282
1283 setCurrentImage(d->image, /* forceInitialUpdate = */ true);
1284 } else {
1285 // clone the image with keeping the GUIDs of the layers intact
1286 // NOTE: we expect the image to be locked!
1287 setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false);
1288 }
1289 }
1290
1291 if (policy == REPLACE) {
1292 d->syncDecorationsWrapperLayerState();
1293 }
1294
1295 if (rhs.d->preActivatedNode) {
1296 QQueue<KisNodeSP> linearizedNodes;
1297 KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(),
1298 [&linearizedNodes](KisNodeSP node) {
1299 linearizedNodes.enqueue(node);
1300 });
1302 [&linearizedNodes, &rhs, this](KisNodeSP node) {
1303 KisNodeSP refNode = linearizedNodes.dequeue();
1304 if (rhs.d->preActivatedNode.data() == refNode.data()) {
1305 d->preActivatedNode = node;
1306 }
1307 });
1308 }
1309
1310 // reinitialize references' signal connection
1311 KisReferenceImagesLayerSP referencesLayer = this->referenceImagesLayer();
1312 if (referencesLayer) {
1313 d->referenceLayerConnections.clear();
1314 d->referenceLayerConnections.addConnection(
1315 referencesLayer, SIGNAL(sigUpdateCanvas(QRectF)),
1316 this, SIGNAL(sigReferenceImagesChanged()));
1317
1318 Q_EMIT sigReferenceImagesLayerChanged(referencesLayer);
1319 Q_EMIT sigReferenceImagesChanged();
1320 }
1321
1322 KisDecorationsWrapperLayerSP decorationsLayer =
1323 KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(d->image->root());
1324 if (decorationsLayer) {
1325 decorationsLayer->setDocument(this);
1326 }
1327
1328
1329 if (policy == REPLACE) {
1330 setModified(true);
1331 }
1332}
1333
1334bool KisDocument::exportDocumentSync(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
1335{
1336 {
1343 Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
1344 if (!locker.successfullyLocked()) {
1345 return false;
1346 }
1347 }
1348
1349 d->savingImage = d->image;
1350
1352 d->importExportManager->
1353 exportDocument(path, path, mimeType, false, exportConfiguration);
1354
1355 d->savingImage = 0;
1356
1357 return status.isOk();
1358}
1359
1360
1362 const QObject *receiverObject, const char *receiverMethod,
1363 const KritaUtils::ExportFileJob &job,
1364 KisPropertiesConfigurationSP exportConfiguration,bool isAdvancedExporting)
1365{
1366 return initiateSavingInBackground(actionName, receiverObject, receiverMethod,
1367 job, exportConfiguration, std::unique_ptr<KisDocument>(), isAdvancedExporting);
1368}
1369
1371 const QObject *receiverObject, const char *receiverMethod,
1372 const KritaUtils::ExportFileJob &job,
1373 KisPropertiesConfigurationSP exportConfiguration,
1374 std::unique_ptr<KisDocument> &&optionalClonedDocument,bool isAdvancedExporting)
1375{
1377
1378 QScopedPointer<KisDocument> clonedDocument;
1379
1380 if (!optionalClonedDocument) {
1381 clonedDocument.reset(lockAndCloneForSaving());
1382 } else {
1383 clonedDocument.reset(optionalClonedDocument.release());
1384 }
1385
1386 if (!d->savingMutex.tryLock()){
1388 }
1389
1394 std::unique_lock<QMutex> savingMutexLock(d->savingMutex, std::adopt_lock);
1395
1396 if (!clonedDocument) {
1398 }
1399
1400 auto waitForImage = [] (KisImageSP image) {
1402 if (window) {
1403 if (window->viewManager()) {
1405 }
1406 }
1407 };
1408
1409 {
1410 KisNodeSP newRoot = clonedDocument->image()->root();
1413 waitForImage(clonedDocument->image());
1414 }
1415 }
1416
1417 if (clonedDocument->image()->hasOverlaySelectionMask()) {
1418 clonedDocument->image()->setOverlaySelectionMask(0);
1419 waitForImage(clonedDocument->image());
1420 }
1421
1422 KisConfig cfg(true);
1423 if (cfg.trimKra()) {
1424 clonedDocument->image()->cropImage(clonedDocument->image()->bounds());
1425 clonedDocument->image()->purgeUnusedData(false);
1426 waitForImage(clonedDocument->image());
1427 }
1428
1429 KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) {
1430 waitForImage(clonedDocument->image());
1431 }
1432
1435
1445 savingMutexLock.release();
1446
1447 d->backgroundSaveDocument.reset(clonedDocument.take());
1448 d->backgroundSaveJob = job;
1449 d->modifiedWhileSaving = false;
1450
1451 if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
1452 d->backgroundSaveDocument->d->isAutosaving = true;
1453 }
1454
1455 connect(d->backgroundSaveDocument.data(),
1456 SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString, QString)),
1457 this,
1459
1460
1461 if (d->completeSavingConnection) {
1462 disconnect(d->completeSavingConnection);
1463 }
1464 d->completeSavingConnection = connect(
1466 receiverObject, receiverMethod);
1467
1469 d->backgroundSaveDocument->startExportInBackground(actionName,
1470 job.filePath,
1471 job.filePath,
1472 job.mimeType,
1474 exportConfiguration, isAdvancedExporting);
1475 if (!error.isOk()) {
1476 // the state should have been deinitialized in slotChildCompletedSavingInBackground()
1477 KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) {
1478 d->backgroundSaveDocument.take()->deleteLater();
1479 d->savingMutex.unlock();
1480 d->backgroundSaveJob = KritaUtils::ExportFileJob();
1481 }
1482 if (error.isCancelled()) {
1484 }
1486 }
1487
1489}
1490
1491
1492void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
1493{
1495
1501 std::unique_lock<QMutex> savingMutexLock(d->savingMutex, std::adopt_lock);
1502
1503 KIS_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument);
1504
1505 if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
1506 d->backgroundSaveDocument->d->isAutosaving = false;
1507 }
1508
1509 d->backgroundSaveDocument.take()->deleteLater();
1510
1511 KIS_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid());
1512
1513 const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
1514 d->backgroundSaveJob = KritaUtils::ExportFileJob();
1515
1516 // unlock at the very end
1517 savingMutexLock.unlock();
1518
1519 QFileInfo fi(job.filePath);
1520 KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3. Warning: %4. Size: %5")
1521 .arg(job.filePath, QString::fromLatin1(job.mimeType),
1522 (!status.isOk() ? errorMessage : "OK"), warningMessage,
1523 QString::number(fi.size())));
1524
1526 // One-shot: disconnect immediately after firing so future save completions
1527 // don't invoke a stale or unrelated slot.
1528 disconnect(d->completeSavingConnection);
1529 d->completeSavingConnection = {};
1530}
1531
1532void KisDocument::slotAutoSaveImpl(std::unique_ptr<KisDocument> &&optionalClonedDocument)
1533{
1534 if (!d->modified || !d->modifiedAfterAutosave) return;
1535 const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
1536
1537 Q_EMIT statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout);
1538
1539 KisUsageLogger::log(QString("Autosaving: %1").arg(autoSaveFileName));
1540
1541 const bool hadClonedDocument = bool(optionalClonedDocument);
1543
1544 if (d->image->isIdle() || hadClonedDocument) {
1545 result = initiateSavingInBackground(i18n("Autosaving..."),
1548 0,
1549 std::move(optionalClonedDocument));
1550 } else {
1551 Q_EMIT statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout);
1552 }
1553
1554 if (result != KritaUtils::BackgroudSavingStartResult::Success && !hadClonedDocument && d->autoSaveFailureCount >= 3) {
1556 connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)),
1558 Qt::BlockingQueuedConnection);
1559 connect(stroke, SIGNAL(sigCloningCancelled()),
1560 this, SLOT(slotDocumentCloningCancelled()),
1561 Qt::BlockingQueuedConnection);
1562
1563 KisStrokeId strokeId = d->image->startStroke(stroke);
1564 d->image->endStroke(strokeId);
1565
1567
1570 } else {
1571 d->modifiedAfterAutosave = false;
1572 }
1573}
1574
1575bool KisDocument::resourceSavingFilter(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
1576{
1577 if (QFileInfo(path).absolutePath().startsWith(KisResourceLocator::instance()->resourceLocationBase())) {
1578
1579 QStringList pathParts = QFileInfo(path).absolutePath().split('/');
1580 if (pathParts.size() > 0) {
1581 QString resourceType = pathParts.last();
1582 if (KisResourceLoaderRegistry::instance()->resourceTypes().contains(resourceType)) {
1583
1584 KisResourceModel model(resourceType);
1586
1587 QString tempFileName = QDir::tempPath() + "/" + QFileInfo(path).fileName();
1588
1589 if (QFileInfo(path).exists()) {
1590
1591 int outResourceId;
1592 KoResourceSP res;
1593 if (KisResourceCacheDb::getResourceIdFromVersionedFilename(QFileInfo(path).fileName(), resourceType, "", outResourceId)) {
1594 res = model.resourceForId(outResourceId);
1595 }
1596
1597 if (res) {
1598 d->modifiedWhileSaving = false;
1599
1600 if (!exportConfiguration) {
1601 QScopedPointer<KisImportExportFilter> filter(
1603 if (filter) {
1604 exportConfiguration = filter->defaultConfiguration(nativeFormatMimeType(), mimeType);
1605 }
1606 }
1607
1608 if (exportConfiguration) {
1609 // make sure the name of the resource doesn't change
1610 exportConfiguration->setProperty("name", res->name());
1611 }
1612
1613 if (exportDocumentSync(tempFileName, mimeType, exportConfiguration)) {
1614 QFile f2(tempFileName);
1615 f2.open(QFile::ReadOnly);
1616
1617 QByteArray ba = f2.readAll();
1618
1619 QBuffer buf(&ba);
1620 buf.open(QBuffer::ReadOnly);
1621
1622
1623
1624 if (res->loadFromDevice(&buf, KisGlobalResourcesInterface::instance())) {
1625 if (model.updateResource(res)) {
1626 const QString filePath =
1628
1629 d->updateDocumentMetadataOnSaving(filePath, mimeType);
1630
1631 return true;
1632 }
1633 }
1634 }
1635 }
1636 }
1637 else {
1638 d->modifiedWhileSaving = false;
1639 if (exportDocumentSync(tempFileName, mimeType, exportConfiguration)) {
1640 KoResourceSP res = model.importResourceFile(tempFileName, false);
1641 if (res) {
1642 const QString filePath =
1644
1645 d->updateDocumentMetadataOnSaving(filePath, mimeType);
1646
1647 return true;
1648 }
1649 }
1650 }
1651 }
1652 }
1653 }
1654 return false;
1655}
1656
1658{
1659 slotAutoSaveImpl(std::unique_ptr<KisDocument>());
1660}
1661
1663{
1664 slotAutoSaveImpl(std::unique_ptr<KisDocument>(clonedDocument));
1665}
1666
1671
1673{
1674 d->image->explicitRegenerateLevelOfDetail();
1675
1676
1680
1681 // d->image->purgeUnusedData(true);
1682}
1683
1684void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
1685{
1686 Q_UNUSED(job);
1687 Q_UNUSED(warningMessage);
1688
1689 const QString fileName = QFileInfo(job.filePath).fileName();
1690
1691 if (!status.isOk()) {
1693 Q_EMIT statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
1694 "Error during autosaving %1: %2",
1695 fileName,
1696 exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
1697 } else {
1698 KisConfig cfg(true);
1699 d->autoSaveDelay = cfg.autoSaveInterval();
1700
1701 if (!d->modifiedWhileSaving) {
1702 d->autoSaveTimer->stop(); // until the next change
1703 d->autoSaveFailureCount = 0;
1704 } else {
1706 }
1707
1708 Q_EMIT statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout);
1709 }
1710}
1711
1713 const QString &location,
1714 const QString &realLocation,
1715 const QByteArray &mimeType,
1716 bool showWarnings,
1717 KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting)
1718{
1719 d->savingImage = d->image;
1720
1722 if (window) {
1723 if (window->viewManager()) {
1724 d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName);
1725 d->importExportManager->setUpdater(d->savingUpdater);
1726 }
1727 }
1728
1729 KisImportExportErrorCode initializationStatus(ImportExportCodes::OK);
1730 d->childSavingFuture =
1731 d->importExportManager->exportDocumentAsync(location,
1732 realLocation,
1733 mimeType,
1734 initializationStatus,
1735 showWarnings,
1736 exportConfiguration,
1737 isAdvancedExporting);
1738
1739 if (!initializationStatus.isOk()) {
1740 if (d->savingUpdater) {
1741 d->savingUpdater->cancel();
1742 }
1743 d->savingImage.clear();
1744 Q_EMIT sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage(), "");
1745 return initializationStatus;
1746 }
1747
1748 typedef QFutureWatcher<KisImportExportErrorCode> StatusWatcher;
1749 StatusWatcher *watcher = new StatusWatcher();
1750 watcher->setFuture(d->childSavingFuture);
1751
1752 connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground()));
1753 connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
1754
1755 return initializationStatus;
1756}
1757
1759{
1760 KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) {
1762 return;
1763 }
1764
1765 KisImportExportErrorCode status = d->childSavingFuture.result();
1766 QString errorMessage = status.errorMessage();
1767 QString warningMessage = d->lastWarningMessage;
1768
1769 if (!d->lastErrorMessage.isEmpty()) {
1771 errorMessage = d->lastErrorMessage;
1772 } else {
1773 errorMessage += "\n" + d->lastErrorMessage;
1774 }
1775 }
1776
1777 d->savingImage.clear();
1778 d->childSavingFuture = QFuture<KisImportExportErrorCode>();
1779 d->lastErrorMessage.clear();
1780 d->lastWarningMessage.clear();
1781
1782 if (d->savingUpdater) {
1783 d->savingUpdater->setProgress(100);
1784 }
1785
1787}
1788
1789void KisDocument::setReadWrite(bool readwrite)
1790{
1791 const bool changed = readwrite != d->readwrite;
1792
1793 d->readwrite = readwrite;
1794
1795 if (changed) {
1797 }
1798}
1799
1800void KisDocument::setAutoSaveActive(bool autoSaveActive)
1801{
1802 const bool changed = autoSaveActive != d->autoSaveActive;
1803
1804 if (changed) {
1805 d->autoSaveActive = autoSaveActive;
1807 }
1808}
1809
1811{
1812 if (isReadWrite() && delay > 0 && d->autoSaveActive) {
1813 d->autoSaveTimer->start(delay * 1000);
1814 } else {
1815 d->autoSaveTimer->stop();
1816 }
1817}
1818
1820{
1821 setAutoSaveDelay(d->autoSaveDelay);
1822 d->autoSaveFailureCount = 0;
1823}
1824
1826{
1827 const int emergencyAutoSaveInterval = 10; /* sec */
1828 setAutoSaveDelay(emergencyAutoSaveInterval);
1829 d->autoSaveFailureCount++;
1830}
1831
1836
1838{
1839 return d->autoSaveActive;
1840}
1841
1843{
1844 return d->docInfo;
1845}
1846
1848{
1849 return d->modified;
1850}
1851
1852QPixmap KisDocument::generatePreview(const QSize& size)
1853{
1854 KisImageSP image = d->image;
1855 if (d->savingImage) image = d->savingImage;
1856
1857 if (image) {
1858 QRect bounds = image->bounds();
1859 QSize originalSize = bounds.size();
1860 // QSize may round down one dimension to zero on extreme aspect rations, so ensure 1px minimum
1861 QSize newSize = originalSize.scaled(size, Qt::KeepAspectRatio).expandedTo({1, 1});
1862
1863 bool pixelArt = false;
1864 // determine if the image is pixel art or not
1865 if (originalSize.width() < size.width() && originalSize.height() < size.height()) {
1866 // the image must be smaller than the requested preview
1867 // the scale must be integer
1868 if (newSize.height()%originalSize.height() == 0 && newSize.width()%originalSize.width() == 0) {
1869 pixelArt = true;
1870 }
1871 }
1872
1873 QPixmap px;
1874 if (pixelArt) {
1875 // do not scale while converting (because it uses Bicubic)
1876 QImage original = image->convertToQImage(originalSize, 0);
1877 // scale using FastTransformation, which is probably Nearest neighbour, suitable for pixel art
1878 QImage scaled = original.scaled(newSize, Qt::KeepAspectRatio, Qt::FastTransformation);
1879 px = QPixmap::fromImage(scaled);
1880 } else {
1881 px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
1882 }
1883 if (px.size() == QSize(0,0)) {
1884 px = QPixmap(newSize);
1885 QPainter gc(&px);
1886 QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
1887 gc.fillRect(px.rect(), checkBrush);
1888 gc.end();
1889 }
1890 return px;
1891 }
1892 return QPixmap(size);
1893}
1894
1895QString KisDocument::generateAutoSaveFileName(const QString & path) const
1896{
1897 QString retval;
1898
1899 // Using the extension allows to avoid relying on the mime magic when opening
1900 const QString extension (".kra");
1901 QString prefix = KisConfig(true).readEntry<bool>("autosavefileshidden") ? QString(".") : QString();
1902 QRegularExpression autosavePattern1("^\\..+-autosave.kra$");
1903 QRegularExpression autosavePattern2("^.+-autosave.kra$");
1904
1905 QFileInfo fi(path);
1906 QString dir = fi.absolutePath();
1907
1908#ifdef Q_OS_ANDROID
1909 // URIs may or may not have a directory backing them, so we save to our default autosave location
1910 if (path.startsWith("content://")) {
1912 QDir().mkpath(dir);
1913 }
1914#endif
1915
1916 QString filename = fi.fileName();
1917
1918 if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) {
1919 // Never saved?
1920 retval = QString("%1%2%3%4-%5-%6-autosave%7")
1922 .arg('/')
1923 .arg(prefix)
1924 .arg("krita")
1925 .arg(qApp->applicationPid())
1926 .arg(objectName())
1927 .arg(extension);
1928 } else {
1929 // Beware: don't reorder arguments
1930 // otherwise in case of filename = '1-file.kra' it will become '.-file.kra-autosave.kra' instead of '.1-file.kra-autosave.kra'
1931 retval = QString("%1%2%3%4-autosave%5").arg(dir).arg('/').arg(prefix).arg(filename).arg(extension);
1932 }
1933
1934 //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
1935 return retval;
1936}
1937
1938bool KisDocument::importDocument(const QString &_path)
1939{
1940 bool ret;
1941
1942 dbgUI << "path=" << _path;
1943
1944 // open...
1945 ret = openPath(_path);
1946
1947 // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
1948 // File --> Import
1949 if (ret) {
1950 dbgUI << "success, resetting url";
1951 resetPath();
1952 }
1953
1954 return ret;
1955}
1956
1957
1958bool KisDocument::openPath(const QString &_path, OpenFlags flags)
1959{
1960 dbgUI << "path=" << _path;
1961 d->lastErrorMessage.clear();
1962
1963 // Reimplemented, to add a check for autosave files and to improve error reporting
1964 if (_path.isEmpty()) {
1965 d->lastErrorMessage = i18n("Malformed Path\n%1", _path); // ## used anywhere ?
1966 return false;
1967 }
1968
1969 QString path = _path;
1970 QString original = "";
1971 bool autosaveOpened = false;
1972 if (!fileBatchMode()) {
1973 QString file = path;
1974 QString asf = generateAutoSaveFileName(file);
1975 if (QFile::exists(asf)) {
1976 KisApplication *kisApp = static_cast<KisApplication*>(qApp);
1977 kisApp->hideSplashScreen();
1978 //qDebug() <<"asf=" << asf;
1979 // ## TODO compare timestamps ?
1980 KisRecoverNamedAutosaveDialog dlg(0, file, asf);
1981 dlg.exec();
1982 int res = dlg.result();
1983
1984 switch (res) {
1986 original = file;
1987 path = asf;
1988 autosaveOpened = true;
1989 break;
1991 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
1992 QFile::remove(asf);
1993 break;
1994 default: // Cancel
1995 return false;
1996 }
1997 }
1998 }
1999
2000 bool ret = openPathInternal(path);
2001
2002 if (autosaveOpened || flags & RecoveryFile) {
2003 setReadWrite(true); // enable save button
2004 setModified(true);
2005 setRecovered(true);
2006
2007 setPath(original); // since it was an autosave, it will be a local file
2008 setLocalFilePath(original);
2009 }
2010 else {
2011 if (ret) {
2012
2013 if (!(flags & DontAddToRecent)) {
2014 KisPart::instance()->addRecentURLToAllMainWindows(QUrl::fromLocalFile(_path));
2015 }
2016
2017#ifdef Q_OS_ANDROID
2018 // See the comment titled "ANDROID NOTES" in this file for an
2019 // explanation of what this is about. (This is not that comment.)
2020 setReadWrite(true);
2021#else
2022 QFileInfo fi(_path);
2023 setReadWrite(fi.isWritable());
2024#endif
2025 }
2026
2027 setRecovered(false);
2028 }
2029
2030 return ret;
2031}
2032
2034{
2035 //dbgUI <<"for" << localFilePath();
2036 if (!QFile::exists(localFilePath()) && !fileBatchMode()) {
2037 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
2038 return false;
2039 }
2040
2041 QString filename = localFilePath();
2042 QString typeName = mimeType();
2043
2044 if (typeName.isEmpty()) {
2045 typeName = KisMimeDatabase::mimeTypeForFile(filename);
2046 }
2047
2048 // Allow to open backup files, don't keep the mimeType application/x-trash.
2049 if (typeName == "application/x-trash") {
2050 QString path = filename;
2051 while (path.length() > 0) {
2052 path.chop(1);
2053 typeName = KisMimeDatabase::mimeTypeForFile(path);
2054 //qDebug() << "\t" << path << typeName;
2055 if (!typeName.isEmpty()) {
2056 break;
2057 }
2058 }
2059 //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
2060 }
2061 dbgUI << localFilePath() << "type:" << typeName;
2062
2064 KoUpdaterPtr updater;
2065 if (window && window->viewManager()) {
2066 updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
2067 d->importExportManager->setUpdater(updater);
2068 }
2069
2070 KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName);
2071
2072 if (!status.isOk()) {
2073 if (window && window->viewManager()) {
2074 updater->cancel();
2075 }
2076 QString msg = status.errorMessage();
2077 KisUsageLogger::log(QString("Loading %1 failed: %2").arg(prettyPath(), msg));
2078
2079 if (!msg.isEmpty() && !fileBatchMode()) {
2080 DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
2081 i18n("Could not open %1.", prettyPath()),
2082 errorMessage().split("\n", Qt::SkipEmptyParts)
2083 + warningMessage().split("\n", Qt::SkipEmptyParts),
2084 msg);
2085
2086 dlg.exec();
2087 }
2088 return false;
2089 }
2090 else if (!warningMessage().isEmpty() && !fileBatchMode()) {
2091 DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
2092 i18n("There were problems opening %1.", prettyPath()),
2093 warningMessage().split("\n", Qt::SkipEmptyParts));
2094
2095 dlg.exec();
2096 setPath(QString());
2097 }
2098
2099 setMimeTypeAfterLoading(typeName);
2100 d->syncDecorationsWrapperLayerState();
2101 Q_EMIT sigLoadingFinished();
2102
2103 undoStack()->clear();
2104
2105 return true;
2106}
2107
2109{
2110 if (!d->modified || !d->modifiedAfterAutosave)
2111 return;
2112
2113 const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
2114
2115 bool started = exportDocumentSync(autoSaveFileName, nativeFormatMimeType());
2116
2117 if (started)
2118 {
2119 d->modifiedAfterAutosave = false;
2120 dbgAndroid << "autoSaveOnPause successful";
2121 }
2122 else
2123 {
2124 qWarning() << "Could not auto-save when paused";
2125 }
2126}
2127
2128// shared between openFile and koMainWindow's "create new empty document" code
2129void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
2130{
2131 d->mimeType = mimeType.toLatin1();
2132 d->outputMimeType = d->mimeType;
2133}
2134
2135
2136bool KisDocument::loadNativeFormat(const QString & file_)
2137{
2138 return openPath(file_);
2139}
2140
2142{
2143 if (mod) {
2144 updateEditingTime(false);
2145 }
2146
2151 if (d->isAutosaving || d->documentIsClosing)
2152 return;
2153
2154 //dbgUI<<" url:" << url.path();
2155 //dbgUI<<" mod="<<mod<<" MParts mod="<<KisParts::ReadWritePart::isModified()<<" isModified="<<isModified();
2156
2157 if (mod && !d->autoSaveTimer->isActive()) {
2158 // First change since last autosave -> start the autosave timer
2160 }
2161 d->modifiedAfterAutosave = mod;
2162 d->modifiedWhileSaving = mod;
2163
2164 if (!mod) {
2165 d->imageModifiedWithoutUndo = mod;
2166 }
2167
2168 if (mod == isModified())
2169 return;
2170
2171 d->modified = mod;
2172
2173 if (mod) {
2175 }
2176
2177 Q_EMIT modified(mod);
2178}
2179
2181{
2182 const bool changed = value != d->isRecovered;
2183
2184 d->isRecovered = value;
2185
2186 if (changed) {
2187 Q_EMIT sigRecoveredChanged(value);
2188 }
2189}
2190
2191bool KisDocument::isRecovered() const
2192{
2193 return d->isRecovered;
2194}
2195
2196void KisDocument::updateEditingTime(bool forceStoreElapsed)
2197{
2198 QDateTime now = QDateTime::currentDateTime();
2199 int firstModDelta = d->firstMod.secsTo(now);
2200 int lastModDelta = d->lastMod.secsTo(now);
2201
2202 if (lastModDelta > 30) {
2203 d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
2204 d->firstMod = now;
2205 } else if (firstModDelta > 60 || forceStoreElapsed) {
2206 d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
2207 d->firstMod = now;
2208 }
2209
2210 d->lastMod = now;
2211}
2212
2214{
2215 QString _url(path());
2216#ifdef Q_OS_WIN
2217 _url = QDir::toNativeSeparators(_url);
2218#endif
2219 return _url;
2220}
2221
2222// Get caption from document info (title(), in about page)
2224{
2225 QString c;
2226 const QString _url(QFileInfo(path()).fileName());
2227
2228 // if URL is empty...it is probably an unsaved file
2229 if (_url.isEmpty()) {
2230 c = " [" + i18n("Not Saved") + "] ";
2231 } else {
2232 c = _url; // Fall back to document URL
2233 }
2234
2235 return c;
2236}
2237
2238QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
2239{
2240 return createDomDocument("krita", tagName, version);
2241}
2242
2243//static
2244QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
2245{
2246 QDomImplementation impl;
2247 QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
2248 QDomDocumentType dtype = impl.createDocumentType(tagName,
2249 QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
2250 url);
2251 // The namespace URN doesn't need to include the version number.
2252 QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
2253 QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
2254 doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
2255 return doc;
2256}
2257
2258bool KisDocument::isNativeFormat(const QByteArray& mimeType) const
2259{
2261 return true;
2262 return extraNativeMimeTypes().contains(mimeType);
2263}
2264
2265void KisDocument::setErrorMessage(const QString& errMsg)
2266{
2267 d->lastErrorMessage = errMsg;
2268}
2269
2271{
2272 return d->lastErrorMessage;
2273}
2274
2275void KisDocument::setWarningMessage(const QString& warningMsg)
2276{
2277 d->lastWarningMessage = warningMsg;
2278}
2279
2281{
2282 return d->lastWarningMessage;
2283}
2284
2285
2286void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered)
2287{
2288 // Eliminate any auto-save file
2289 QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir
2290 if (QFile::exists(asf)) {
2291 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
2292 QFile::remove(asf);
2293 }
2294 asf = generateAutoSaveFileName(QString()); // and the one in $HOME
2295
2296 if (QFile::exists(asf)) {
2297 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
2298 QFile::remove(asf);
2299 }
2300
2301 QList<QRegularExpression> expressions;
2302
2303 expressions << QRegularExpression("^\\..+-autosave.kra$")
2304 << QRegularExpression("^.+-autosave.kra$");
2305
2306 Q_FOREACH(const QRegularExpression &rex, expressions) {
2307 if (wasRecovered &&
2308 !autosaveBaseName.isEmpty() &&
2309 rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() &&
2310 QFile::exists(autosaveBaseName)) {
2311
2312 KisUsageLogger::log(QString("Removing autosave file: %1").arg(autosaveBaseName));
2313 QFile::remove(autosaveBaseName);
2314 }
2315 }
2316}
2317
2319{
2320 return d->unit;
2321}
2322
2324{
2325 if (d->unit != unit) {
2326 d->unit = unit;
2327 Q_EMIT unitChanged(unit);
2328 }
2329}
2330
2332{
2333 return d->undoStack;
2334}
2335
2337{
2338 return d->importExportManager;
2339}
2340
2342{
2343 setModified(!value || d->imageModifiedWithoutUndo);
2344}
2345
2347{
2348 KisConfig cfg(true);
2349
2350 if (d->undoStack->undoLimit() != cfg.undoStackLimit()) {
2351 if (!d->undoStack->isClean()) {
2352 d->undoStack->clear();
2353 // we set this because the document *has* changed, even though the
2354 // undo history was purged.
2356 }
2357 d->undoStack->setUndoLimit(cfg.undoStackLimit());
2358 }
2359 d->undoStack->setUseCumulativeUndoRedo(cfg.useCumulativeUndoRedo());
2360 d->undoStack->setCumulativeUndoData(cfg.cumulativeUndoData());
2361
2362 d->autoSaveDelay = cfg.autoSaveInterval();
2364}
2365
2367{
2368 d->syncDecorationsWrapperLayerState();
2369}
2370
2372{
2373 d->undoStack->clear();
2374}
2375
2377{
2378 return d->gridConfig;
2379}
2380
2382{
2383 if (d->gridConfig != config) {
2384 d->gridConfig = config;
2385 d->syncDecorationsWrapperLayerState();
2386 Q_EMIT sigGridConfigChanged(config);
2387
2388 // Store last assigned value as future default...
2389 KisConfig cfg(false);
2390 cfg.setDefaultGridSpacing(config.spacing());
2391 }
2392}
2393
2395{
2397 if (!d->linkedResourceStorage) {
2398 return result;
2399 }
2400
2401 Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) {
2402 QSharedPointer<KisResourceStorage::ResourceIterator> iter = d->linkedResourceStorage->resources(resourceType);
2403 while (iter->hasNext()) {
2404 iter->next();
2405
2406 QBuffer buf;
2407 buf.open(QBuffer::WriteOnly);
2408 bool exportSuccessful =
2409 d->linkedResourceStorage->exportResource(iter->url(), &buf);
2410
2411 KoResourceSP resource = d->linkedResourceStorage->resource(iter->url());
2412 exportSuccessful &= bool(resource);
2413
2414 const QString name = resource ? resource->name() : QString();
2415 const QString fileName = QFileInfo(iter->url()).fileName();
2416 const KoResourceSignature signature(resourceType,
2417 KoMD5Generator::generateHash(buf.data()),
2418 fileName, name);
2419
2420 if (exportSuccessful) {
2421 result << KoEmbeddedResource(signature, buf.data());
2422 } else {
2423 result << signature;
2424 }
2425 }
2426 }
2427
2428 return result;
2429}
2430
2431void KisDocument::setPaletteList(const QList<KoColorSetSP > &paletteList, bool emitSignal)
2432{
2433 QList<KoColorSetSP> oldPaletteList;
2434 if (d->linkedResourceStorage) {
2435 QSharedPointer<KisResourceStorage::ResourceIterator> iter = d->linkedResourceStorage->resources(ResourceType::Palettes);
2436 while (iter->hasNext()) {
2437 iter->next();
2438 KoResourceSP resource = iter->resource();
2439 if (resource && resource->valid()) {
2440 oldPaletteList << resource.dynamicCast<KoColorSet>();
2441 }
2442 }
2443 if (oldPaletteList != paletteList) {
2445 Q_FOREACH(KoColorSetSP palette, oldPaletteList) {
2446 if (!paletteList.contains(palette)) {
2447 resourceModel.setResourceInactive(resourceModel.indexForResource(palette));
2448 }
2449 }
2450 Q_FOREACH(KoColorSetSP palette, paletteList) {
2451 if (!oldPaletteList.contains(palette)) {
2452 resourceModel.addResource(palette, d->linkedResourcesStorageID);
2453 }
2454 else {
2455 palette->setStorageLocation(d->linkedResourcesStorageID);
2456 resourceModel.updateResource(palette);
2457 }
2458 }
2459 if (emitSignal) {
2460 Q_EMIT sigPaletteListChanged(oldPaletteList, paletteList);
2461 }
2462 }
2463 }
2464}
2465
2467{
2468 return d->m_storyboardItemList;
2469}
2470
2471void KisDocument::setStoryboardItemList(const StoryboardItemList &storyboardItemList, bool emitSignal)
2472{
2473 d->m_storyboardItemList = storyboardItemList;
2474 if (emitSignal) {
2476 }
2477}
2478
2480{
2481 return d->m_storyboardCommentList;
2482}
2483
2484void KisDocument::setStoryboardCommentList(const QVector<StoryboardComment> &storyboardCommentList, bool emitSignal)
2485{
2486 d->m_storyboardCommentList = storyboardCommentList;
2487 if (emitSignal) {
2489 }
2490}
2491
2493 return d->audioTracks;
2494}
2495
2497{
2498 d->audioTracks = f;
2499 Q_EMIT sigAudioTracksChanged();
2500}
2501
2503{
2504 d->audioLevel = level;
2505 Q_EMIT sigAudioLevelChanged(level);
2506}
2507
2509{
2510 return d->audioLevel;
2511}
2512
2514{
2515 return d->guidesConfig;
2516}
2517
2519{
2520 if (d->guidesConfig == data) return;
2521
2522 d->guidesConfig = data;
2523 d->syncDecorationsWrapperLayerState();
2524 Q_EMIT sigGuidesConfigChanged(d->guidesConfig);
2525}
2526
2527
2529{
2530 return d->mirrorAxisConfig;
2531}
2532
2534{
2535 if (d->mirrorAxisConfig == config) {
2536 return;
2537 }
2538
2539 d->mirrorAxisConfig = config;
2540 if (d->image) {
2541 d->image->setMirrorAxesCenter(KisAlgebra2D::absoluteToRelative(d->mirrorAxisConfig.axisPosition(),
2542 d->image->bounds()));
2543 }
2544 setModified(true);
2545
2547}
2548
2550 setPath(QString());
2551 setLocalFilePath(QString());
2552}
2553
2555{
2556 return new KoDocumentInfoDlg(parent, docInfo);
2557}
2558
2560{
2561 return d->readwrite;
2562}
2563
2564QString KisDocument::path() const
2565{
2566 return d->m_path;
2567}
2568
2569bool KisDocument::closePath(bool promptToSave)
2570{
2571 if (promptToSave) {
2572 if ( isReadWrite() && isModified()) {
2573 Q_FOREACH (KisView *view, KisPart::instance()->views()) {
2574 if (view && view->document() == this) {
2575 if (!view->queryClose()) {
2576 return false;
2577 }
2578 }
2579 }
2580 }
2581 }
2582 // Not modified => ok and delete temp file.
2583 d->mimeType = QByteArray();
2584
2585 // It always succeeds for a read-only part,
2586 // but the return value exists for reimplementations
2587 // (e.g. pressing cancel for a modified read-write part)
2588 return true;
2589}
2590
2591
2592
2593void KisDocument::setPath(const QString &path)
2594{
2595 const bool changed = path != d->m_path;
2596
2597 d->m_path = path;
2598
2599 if (changed) {
2600 Q_EMIT sigPathChanged(path);
2601 }
2602}
2603
2605{
2606 return d->m_file;
2607}
2608
2609
2610void KisDocument::setLocalFilePath( const QString &localFilePath )
2611{
2612 d->m_file = localFilePath;
2613}
2614
2615bool KisDocument::openPathInternal(const QString &path)
2616{
2617 if ( path.isEmpty() ) {
2618 return false;
2619 }
2620
2621 if (d->m_bAutoDetectedMime) {
2622 d->mimeType = QByteArray();
2623 d->m_bAutoDetectedMime = false;
2624 }
2625
2626 QByteArray mimeType = d->mimeType;
2627
2628 if ( !closePath() ) {
2629 return false;
2630 }
2631
2632 d->mimeType = mimeType;
2633 setPath(path);
2634
2635 d->m_file.clear();
2636
2637 d->m_file = d->m_path;
2638
2639 bool ret = false;
2640 // set the mimeType only if it was not already set (for example, by the host application)
2641 if (d->mimeType.isEmpty()) {
2642 // get the mimeType of the file
2643 // using findByUrl() to avoid another string -> url conversion
2644 QString mime = KisMimeDatabase::mimeTypeForFile(d->m_path);
2645 d->mimeType = mime.toLocal8Bit();
2646 d->m_bAutoDetectedMime = true;
2647 }
2648
2649 setPath(d->m_path);
2650 ret = openFile();
2651
2652 if (ret) {
2653 Q_EMIT completed();
2654 }
2655 else {
2656 Q_EMIT canceled(QString());
2657 }
2658 return ret;
2659}
2660
2661bool KisDocument::newImage(const QString& name,
2662 qint32 width, qint32 height,
2663 const KoColorSpace* cs,
2664 const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle,
2665 int numberOfLayers,
2666 const QString &description, const double imageResolution)
2667{
2668 Q_ASSERT(cs);
2669
2671
2672 if (!cs) return false;
2673
2674 KisCursorOverrideLock cursorLock(Qt::BusyCursor);
2675
2676 image = new KisImage(createUndoStore(), width, height, cs, name);
2677
2678 Q_CHECK_PTR(image);
2679
2680 connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
2681 connect(image, SIGNAL(sigImageModifiedWithoutUndo()), this, SLOT(setImageModifiedWithoutUndo()), Qt::UniqueConnection);
2682 image->setResolution(imageResolution, imageResolution);
2683
2685 image->waitForDone();
2686
2687 documentInfo()->setAboutInfo("title", name);
2688 documentInfo()->setAboutInfo("abstract", description);
2689
2690 KisConfig cfg(false);
2691 cfg.defImageWidth(width);
2692 cfg.defImageHeight(height);
2693 cfg.defImageResolution(imageResolution);
2694 if (!cfg.useDefaultColorSpace())
2695 {
2699 }
2700
2701 bool autopin = cfg.autoPinLayersToTimeline();
2702
2703 KisLayerSP bgLayer;
2704 if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) {
2705 KoColor strippedAlpha = bgColor;
2706 strippedAlpha.setOpacity(OPACITY_OPAQUE_U8);
2707
2708 if (bgStyle == KisConfig::RASTER_LAYER) {
2709 bgLayer = new KisPaintLayer(image.data(), i18nc("Name for the bottom-most layer in the layerstack", "Background"), OPACITY_OPAQUE_U8, cs);
2710 bgLayer->paintDevice()->setDefaultPixel(strippedAlpha);
2711 bgLayer->setPinnedToTimeline(autopin);
2712 } else if (bgStyle == KisConfig::FILL_LAYER) {
2714 filter_config->setProperty("color", strippedAlpha.toQColor());
2715 filter_config->createLocalResourcesSnapshot();
2716 bgLayer = new KisGeneratorLayer(image.data(), i18nc("Name of automatically created background color fill layer", "Background Fill"), filter_config, image->globalSelection());
2717 }
2718
2719 bgLayer->setOpacity(bgColor.opacityU8());
2720
2721 if (numberOfLayers > 1) {
2722 //Lock bg layer if others are present.
2723 bgLayer->setUserLocked(true);
2724 }
2725 }
2726 else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer).
2728 bgLayer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
2729 }
2730
2731 Q_CHECK_PTR(bgLayer);
2732 image->addNode(bgLayer.data(), image->rootLayer().data());
2733 bgLayer->setDirty(QRect(0, 0, width, height));
2734
2735 // reset mirror axis to default:
2736 d->mirrorAxisConfig.setAxisPosition(QRectF(image->bounds()).center());
2738
2739 for(int i = 1; i < numberOfLayers; ++i) {
2741 layer->setPinnedToTimeline(autopin);
2742 image->addNode(layer, image->root(), i);
2743 layer->setDirty(QRect(0, 0, width, height));
2744 }
2745
2746 {
2748 if (window) {
2753 }
2754 }
2755
2757 QString("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8")
2758 .arg(name, QString::number(width), QString::number(height),
2759 QString::number(imageResolution * 72.0), image->colorSpace()->colorModelId().name(),
2761 QString::number(numberOfLayers)));
2762
2763 return true;
2764}
2765
2767{
2768 const bool result = d->savingMutex.tryLock();
2769 if (result) {
2770 d->savingMutex.unlock();
2771 }
2772 return !result;
2773}
2774
2776{
2777 if (isSaving()) {
2778 KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0);
2779 f.waitForMutex(d->savingMutex);
2780 }
2781}
2782
2784{
2785 return d->shapeController;
2786}
2787
2789{
2790 return d->shapeController->shapeForNode(layer);
2791}
2792
2794{
2795 return d->assistants;
2796}
2797
2799{
2800 if (d->assistants != value) {
2801 d->assistants = value;
2802 d->syncDecorationsWrapperLayerState();
2803 Q_EMIT sigAssistantsChanged();
2804 }
2805}
2806
2808{
2809 if (!d->image) return KisReferenceImagesLayerSP();
2810
2811 KisReferenceImagesLayerSP referencesLayer =
2812 KisLayerUtils::findNodeByType<KisReferenceImagesLayer>(d->image->root());
2813
2814 return referencesLayer;
2815}
2816
2818{
2819 KisReferenceImagesLayerSP currentReferenceLayer = referenceImagesLayer();
2820
2821 // updateImage=false inherently means we are not changing the
2822 // reference images layer, but just would like to update its signals.
2823 if (currentReferenceLayer == layer && updateImage) {
2824 return;
2825 }
2826
2827 d->referenceLayerConnections.clear();
2828
2829 if (updateImage) {
2830 if (currentReferenceLayer) {
2831 d->image->removeNode(currentReferenceLayer);
2832 }
2833
2834 if (layer) {
2835 d->image->addNode(layer);
2836 }
2837 }
2838
2839 currentReferenceLayer = layer;
2840
2841 if (currentReferenceLayer) {
2842 d->referenceLayerConnections.addConnection(
2843 currentReferenceLayer, SIGNAL(sigUpdateCanvas(QRectF)),
2844 this, SIGNAL(sigReferenceImagesChanged()));
2845 }
2846
2847 Q_EMIT sigReferenceImagesLayerChanged(layer);
2849}
2850
2852{
2853 d->preActivatedNode = activatedNode;
2854}
2855
2857{
2858 return d->preActivatedNode;
2859}
2860
2862{
2863 return d->image;
2864}
2865
2867{
2868 return d->savingImage;
2869}
2870
2871
2872void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate, KisNodeSP preActivatedNode)
2873{
2874 if (d->image) {
2875 // Disconnect existing sig/slot connections
2876 d->image->setUndoStore(new KisDumbUndoStore());
2877 d->image->disconnect(this);
2878 d->shapeController->setImage(0);
2879 d->image = 0;
2880 }
2881
2882 if (!image) return;
2883
2884 if (d->linkedResourceStorage){
2885 d->linkedResourceStorage->setMetaData(KisResourceStorage::s_meta_name, image->objectName());
2886 }
2887
2888 d->setImageAndInitIdleWatcher(image);
2889 d->image->setUndoStore(new KisDocumentUndoStore(this));
2890 d->shapeController->setImage(image, preActivatedNode);
2891 d->image->setMirrorAxesCenter(KisAlgebra2D::absoluteToRelative(d->mirrorAxisConfig.axisPosition(), image->bounds()));
2892 setModified(false);
2893 connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
2894 connect(d->image, SIGNAL(sigImageModifiedWithoutUndo()), this, SLOT(setImageModifiedWithoutUndo()), Qt::UniqueConnection);
2895 connect(d->image, SIGNAL(sigLayersChangedAsync()), this, SLOT(slotImageRootChanged()));
2896
2897 if (forceInitialUpdate) {
2898 d->image->initialRefreshGraph();
2899 }
2900}
2901
2903{
2905
2906 // we set image without connecting idle-watcher, because loading
2907 // hasn't been finished yet
2908 d->image = image;
2909 d->shapeController->setImage(image);
2910}
2911
2913{
2914 // we only set as modified if undo stack is not at clean state
2915 setModified(d->imageModifiedWithoutUndo || !d->undoStack->isClean());
2916}
2917
2919{
2920 d->imageModifiedWithoutUndo = true;
2922}
2923
2924
2929
2930bool KisDocument::isAutosaving() const
2931{
2932 return d->isAutosaving;
2933}
2934
2935QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage)
2936{
2937 return errorMessage.isEmpty() ? status.errorMessage() : errorMessage;
2938}
2939
2941{
2942 d->globalAssistantsColor = color;
2943}
2944
2946{
2947 return d->globalAssistantsColor;
2948}
2949
2951{
2952 return d->colorHistory;
2953}
2954
2956{
2957 QRectF bounds = d->image->bounds();
2958
2960
2961 if (referenceImagesLayer) {
2963 }
2964
2965 return bounds;
2966}
2967
2969{
2970 d->colorHistory = colors;
2971}
float value(const T *src, size_t ch)
KisSharedPtr< KisReferenceImagesLayer > KisReferenceImagesLayerSP
const quint8 OPACITY_OPAQUE_U8
void setDetailedText(const QStringList &text)
DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings={}, const QString &details={})
virtual void setIndex(int idx)
virtual void undo()
virtual void redo()
bool setResourceInactive(const QModelIndex &index)
Base class for the Krita app.
static bool simpleBackupFile(const QString &filename, const QString &backupDir=QString(), const QString &backupExtension=QStringLiteral("~"))
Definition KisBackup.cpp:24
static bool numberedBackupFile(const QString &filename, const QString &backupDir=QString(), const QString &backupExtension=QStringLiteral("~"), const uint maxBackups=10)
Definition KisBackup.cpp:38
static QImage createCheckersImage(qint32 checkSize=-1)
static KisConfigNotifier * instance()
bool backupFile(bool defaultValue=false) const
qint32 defImageHeight(bool defaultValue=false) const
qint32 defImageWidth(bool defaultValue=false) const
bool useDefaultColorSpace(bool defaultvalue=false) const
void setDefaultColorDepth(const QString &depth) const
void setDefaultGridSpacing(QPoint gridSpacing)
qreal defImageResolution(bool defaultValue=false) const
bool useCumulativeUndoRedo(bool defaultValue=false) const
QString defColorProfile(bool defaultValue=false) const
KisCumulativeUndoData cumulativeUndoData(bool defaultValue=false) const
bool autoPinLayersToTimeline(bool defaultValue=false) const
bool trimKra(bool defaultValue=false) const
T readEntry(const QString &name, const T &defaultValue=T())
Definition kis_config.h:835
int autoSaveInterval(bool defaultValue=false) const
QString defColorModel(bool defaultValue=false) const
int undoStackLimit(bool defaultValue=false) const
StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
QScopedPointer< KisSignalAutoConnection > imageIdleConnection
void slotImageRootChanged()
void completed()
void setAssistants(const QList< KisPaintingAssistantSP > &value)
@replace the current list of assistants with
void waitForSavingToComplete()
QRectF documentBounds() const
QString warningMessage() const
void copyFrom(const Private &rhs, KisDocument *q)
QMutex savingMutex
void removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered)
KisUndoStore * createUndoStore()
bool isReadWrite() const
void slotInitiateAsyncAutosaving(KisDocument *clonedDocument)
void updateDocumentMetadataOnSaving(const QString &filePath, const QByteArray &mimeType)
QList< KoColor > colorHistory
KisNameServer * nserver
void sigStoryboardCommentListChanged()
QFuture< KisImportExportErrorCode > childSavingFuture
void setEmergencyAutoSaveInterval()
void setFileBatchMode(const bool batchMode)
KisMirrorAxisConfig mirrorAxisConfig
KisSharedPtr< KisReferenceImagesLayer > referenceImagesLayer() const
void clearUndoHistory()
KUndo2Stack * undoStack
bool exportDocument(const QString &path, const QByteArray &mimeType, bool isAdvancedExporting=false, bool showWarnings=false, KisPropertiesConfigurationSP exportConfiguration=0)
KoDocumentInfo * documentInfo() const
void syncDecorationsWrapperLayerState()
void hackPreliminarySetImage(KisImageSP image)
void sigBackgroundSavingFinished(KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
QTimer * autoSaveTimer
KisImageSP image
QString linkedResourcesStorageId() const
void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy)
QDateTime lastMod
QPixmap generatePreview(const QSize &size)
Generates a preview picture of the document.
KisShapeController * shapeController
static QStringList extraNativeMimeTypes()
KisSignalAutoConnectionsStore referenceLayerConnections
KritaUtils::BackgroudSavingStartResult initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr< KisDocument > &&optionalClonedDocument, bool isAdvancedExporting=false)
bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
KisImportExportManager * importExportManager
void statusBarMessage(const QString &text, int timeout=0)
bool isNativeFormat(const QByteArray &mimeType) const
Checks whether a given mimeType can be handled natively.
void setAudioTracks(QVector< QFileInfo > f)
void sigAudioLevelChanged(qreal level)
KisDocument * lockAndCloneImpl(bool fetchResourcesFromLayers)
void uploadLinkedResourcesFromLayersToStorage()
void slotUndoStackCleanChanged(bool value)
QString localFilePath() const
void sigAssistantsChanged()
void setErrorMessage(const QString &errMsg)
void setMirrorAxisConfig(const KisMirrorAxisConfig &config)
KoDocumentInfoDlg * createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
void sigStoryboardItemListChanged()
bool isModified() const
KisImportExportErrorCode startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting=false)
QString exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage)
void setAssistantsGlobalColor(QColor color)
bool loadNativeFormat(const QString &file)
void updateEditingTime(bool forceStoreElapsed)
void slotAutoSave()
void sigPaletteListChanged(const QList< KoColorSetSP > &oldPaletteList, const QList< KoColorSetSP > &newPaletteList)
void setModified(bool _mod)
void setMimeType(const QByteArray &mimeType)
Sets the mime type for the document.
QDomDocument createDomDocument(const QString &tagName, const QString &version) const
void sigLoadingFinished()
KisDocument * lockAndCloneForSaving()
try to clone the image. This method handles all the locking for you. If locking has failed,...
void sigReadWriteChanged(bool value)
void finishExportInBackground()
void copyFromDocument(const KisDocument &rhs)
void slotDocumentCloningCancelled()
void setWarningMessage(const QString &warningMsg)
QDateTime firstMod
qreal getAudioLevel()
void setUnit(const KoUnit &unit)
void setImageModified()
void setPath(const QString &path)
bool saveAs(const QString &path, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration=0)
bool newImage(const QString &name, qint32 width, qint32 height, const KoColorSpace *cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &imageDescription, const double imageResolution)
void setLocalFilePath(const QString &localFilePath)
QList< KoResourceLoadResult > linkedDocumentResources()
linkedDocumentResources List returns all the resources linked to the document, such as palettes
KisDocument * lockAndCreateSnapshot()
void setAutoSaveDelay(int delay)
void setPreActivatedNode(KisNodeSP activatedNode)
QString lastWarningMessage
void sigGuidesConfigChanged(const KisGuidesConfig &config)
void setRecovered(bool value)
QString caption() const
KisGridConfig gridConfig
void slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
Private(const Private &rhs, KisDocument *_q)
bool importDocument(const QString &path)
QString errorMessage() const
KritaUtils::ExportFileJob backgroundSaveJob
Private(KisDocument *_q)
QColor globalAssistantsColor
KisResourceStorageSP embeddedResourceStorage
static QByteArray nativeFormatMimeType()
void sigAudioTracksChanged()
void setReferenceImagesLayer(KisSharedPtr< KisReferenceImagesLayer > layer, bool updateImage)
bool resourceSavingFilter(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
KisDocument(bool addStorage=true)
void canceled(const QString &)
QString path() const
void setReadWrite(bool readwrite=true)
Sets whether the document can be edited or is read only.
bool isSaving() const
void setImageModifiedWithoutUndo()
Private *const d
void sigGridConfigChanged(const KisGridConfig &config)
QColor assistantsGlobalColor()
QPointer< KoUpdater > savingUpdater
void autoSaveOnPause()
Start saving when android activity is pushed to the background.
StoryboardItemList getStoryboardItemList()
returns the list of pointers to storyboard Items for the document
QString embeddedResourcesStorageId() const
QString linkedResourcesStorageID
void sigSavingFinished(const QString &filePath)
QString generateAutoSaveFileName(const QString &path) const
KisResourceStorageSP linkedResourceStorage
QMetaObject::Connection completeSavingConnection
KisNodeWSP preActivatedNode
KisImageSP savingImage
void setNormalAutoSaveInterval()
void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
void unitChanged(const KoUnit &unit)
KoShapeLayer * shapeForNode(KisNodeSP layer) const
QByteArray mimeType
bool exportDocumentSync(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration=0)
void setGridConfig(const KisGridConfig &config)
bool closePath(bool promptToSave=true)
QString newObjectName()
void sigPathChanged(const QString &path)
QByteArray serializeToNativeByteArray()
serializeToNativeByteArray daves the document into a .kra file written to a memory-based byte-array
QList< KisPaintingAssistantSP > assistants
void setImageAndInitIdleWatcher(KisImageSP _image)
void setAudioVolume(qreal level)
void setAutoSaveActive(bool autoSaveIsActive)
@ CONSTRUCT
we are copy-constructing a new KisDocument
@ REPLACE
we are replacing the current KisDocument with another
void setGuidesConfig(const KisGuidesConfig &data)
bool openPathInternal(const QString &path)
KisIdleWatcher imageIdleWatcher
bool fileBatchMode() const
QByteArray outputMimeType
QVector< StoryboardComment > getStoryboardCommentsList()
returns the list of comments for the storyboard docker in the document
void setMimeTypeAfterLoading(const QString &mimeType)
void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
void copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy)
QScopedPointer< KisDocument > backgroundSaveDocument
void sigReferenceImagesLayerChanged(KisSharedPtr< KisReferenceImagesLayer > layer)
void slotPerformIdleRoutines()
StoryboardItemList m_storyboardItemList
void slotAutoSaveImpl(std::unique_ptr< KisDocument > &&optionalClonedDocument)
void setCurrentImage(KisImageSP image, bool forceInitialUpdate=true, KisNodeSP preActivatedNode=nullptr)
void sigRecoveredChanged(bool value)
QVector< StoryboardComment > m_storyboardCommentList
QVector< QFileInfo > getAudioTracks() const
bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting=false)
void setStoryboardCommentList(const QVector< StoryboardComment > &storyboardCommentList, bool emitSignal=false)
sets the list of comments for the storyboard docker in the document, emits empty signal if emitSignal...
void setPaletteList(const QList< KoColorSetSP > &paletteList, bool emitSignal=false)
setPaletteList replaces the palettes in the document's local resource storage with the list of palett...
void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage)
void slotConfigChanged()
KisDocument * clone(bool addStorage=false)
creates a clone of the document and returns it. Please make sure that you hold all the necessary lock...
void setInfiniteAutoSaveInterval()
void sigReferenceImagesChanged()
QString lastErrorMessage
KoDocumentInfo * docInfo
QString embeddedResourcesStorageID
void setStoryboardItemList(const StoryboardItemList &storyboardItemList, bool emitSignal=false)
sets the storyboardItemList in the document, emits empty signal if emitSignal is true.
bool openPath(const QString &path, OpenFlags flags=None)
openPath Open a Path
KisGuidesConfig guidesConfig
bool isAutoSaveActive()
void setColorHistory(const QList< KoColor > &colors)
QVector< QFileInfo > audioTracks
void sigMirrorAxisConfigChanged()
QString prettyPath() const
The KisDumbUndoStore class doesn't actually save commands, so you cannot undo or redo!
static KisGeneratorRegistry * instance()
static KisResourcesInterfaceSP instance()
QPoint spacing() const
void setTrackedImage(KisImageSP image)
QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile *profile)
void waitForDone()
KisGroupLayerSP rootLayer() const
UndoResult tryUndoUnfinishedLod0Stroke()
const KoColorSpace * colorSpace() const
void unlock()
Definition kis_image.cc:805
void requestUndoDuringStroke()
QString nextLayerName(const QString &baseName="") const
Definition kis_image.cc:715
bool assignImageProfile(const KoColorProfile *profile, bool blockAllUpdates=false)
KisImage * clone(bool exactCopy=false)
Definition kis_image.cc:405
void setDefaultProjectionColor(const KoColor &color)
void requestStrokeCancellation()
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override
void requestStrokeEnd()
KisSelectionSP globalSelection() const
Definition kis_image.cc:695
QRect bounds() const override
bool tryBarrierLock(bool readOnly=false)
Tries to lock the image without waiting for the jobs to finish.
Definition kis_image.cc:771
void endStroke(KisStrokeId id) override
void requestRedoDuringStroke()
void setResolution(double xres, double yres)
The class managing all the filters.
static KisImportExportFilter * filterForMimeType(const QString &mimetype, Direction direction)
filterForMimeType loads the relevant import/export plugin and returns it. The caller is responsible f...
static KisMacosSecurityBookmarkManager * instance()
Main window for Krita.
KisViewManager * viewManager
static QString mimeTypeForFile(const QString &file, bool checkExistingFiles=true)
Find the mimetype for the given filename. The filename must include a suffix.
The KisMirrorAxisConfig class stores configuration for the KisMirrorAxis canvas decoration....
void setDefaultPixel(const KoColor &defPixel)
static MemoryReleaseObject * createMemoryReleaseObject()
static QList< KisPaintingAssistantSP > cloneAssistantList(const QList< KisPaintingAssistantSP > &list)
void addRecentURLToAllMainWindows(QUrl url, QUrl oldUrl=QUrl())
Definition KisPart.cpp:574
static KisPart * instance()
Definition KisPart.cpp:131
KisMainWindow * currentMainwindow() const
Definition KisPart.cpp:459
The KisRecoverNamedAutosaveDialog class is a dialog to recover already existing files from autosave.
static bool getResourceIdFromVersionedFilename(QString filename, QString resourceType, QString storageLocation, int &outResourceId)
Note that here you can put even the original filename - any filename from the versioned_resources - a...
static KisResourceLoaderRegistry * instance()
bool removeStorage(const QString &storageLocation)
removeStorage removes the temporary storage from the database
bool addStorage(const QString &storageLocation, KisResourceStorageSP storage)
addStorage Adds a new resource storage to the database. The storage is will be marked as not pre-inst...
QString filePathForResource(KoResourceSP resource)
static KisResourceLocator * instance()
The KisResourceModel class provides the main access to resources. It is possible to filter the resour...
KoResourceSP resourceForId(int id) const
KoResourceSP importResourceFile(const QString &filename, const bool allowOverwrite, const QString &storageId=QString("")) override
importResourceFile
bool updateResource(KoResourceSP resource) override
updateResource creates a new version of the resource in the storage and in the database....
bool addResource(KoResourceSP resource, const QString &storageId=QString("")) override
addResource adds the given resource to the database and storage. If the resource already exists in th...
void setResourceFilter(ResourceFilter filter) override
QModelIndex indexForResource(KoResourceSP resource) const override
indexFromResource
static KisResourceServerProvider * instance()
static const QString s_meta_name
void enableJob(JobType type, bool enable=true, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
void setClearsRedoOnStart(bool value)
void setRequestsOtherStrokesToEnd(bool value)
static void log(const QString &message)
Logs with date/time.
bool blockUntilOperationsFinished(KisImageSP image)
blockUntilOperationsFinished blocks the GUI of the application until execution of actions on image is...
QPointer< KoUpdater > createThreadedUpdater(const QString &name)
void blockUntilOperationsFinishedForced(KisImageSP image)
blockUntilOperationsFinished blocks the GUI of the application until execution of actions on image is...
QPointer< KoUpdater > createUnthreadedUpdater(const QString &name)
create a new progress updater
KisCanvasResourceProvider * canvasResourceProvider()
QPointer< KisDocument > document
Definition KisView.cpp:121
bool queryClose()
Definition KisView.cpp:1151
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