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