File indexing completed on 2024-05-19 04:29:12
0001 /* This file is part of the Krita project 0002 * 0003 * SPDX-FileCopyrightText: 2014 Boudewijn Rempt <boud@valdyas.org> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "KisMainWindow.h" // XXX: remove 0009 #include <QMessageBox> 0010 0011 #include <KisMimeDatabase.h> 0012 0013 #include <KoCanvasBase.h> 0014 #include <KoColor.h> 0015 #include <KoColorProfile.h> 0016 #include <KoColorSpaceEngine.h> 0017 #include <KoColorSpace.h> 0018 #include <KoColorSpaceRegistry.h> 0019 #include <KoDocumentInfoDlg.h> 0020 #include <KoDocumentInfo.h> 0021 #include <KoUnit.h> 0022 #include <KoID.h> 0023 #include <KoProgressProxy.h> 0024 #include <KoProgressUpdater.h> 0025 #include <KoSelection.h> 0026 #include <KoShape.h> 0027 #include <KoShapeController.h> 0028 #include <KoStore.h> 0029 #include <KoUpdater.h> 0030 #include <KoXmlWriter.h> 0031 #include <KoStoreDevice.h> 0032 #include <KoDialog.h> 0033 #include <KisImportExportErrorCode.h> 0034 #include <KoDocumentResourceManager.h> 0035 #include <KoMD5Generator.h> 0036 #include <KisResourceStorage.h> 0037 #include <KisResourceLocator.h> 0038 #include <KisResourceTypes.h> 0039 #include <KisGlobalResourcesInterface.h> 0040 #include <KisResourceLoaderRegistry.h> 0041 #include <KisResourceModelProvider.h> 0042 #include <KisResourceCacheDb.h> 0043 #include <KoEmbeddedResource.h> 0044 #include <KisUsageLogger.h> 0045 #include <klocalizedstring.h> 0046 #include "kis_scratch_pad.h" 0047 #include <kis_debug.h> 0048 #include <kis_generator_layer.h> 0049 #include <kis_generator_registry.h> 0050 #include <KisAutoSaveRecoveryDialog.h> 0051 #include <kdesktopfile.h> 0052 #include <kconfiggroup.h> 0053 #include <KisBackup.h> 0054 #include <KisView.h> 0055 0056 #include <QTextBrowser> 0057 #include <QApplication> 0058 #include <QBuffer> 0059 #include <QStandardPaths> 0060 #include <QDir> 0061 #include <QDomDocument> 0062 #include <QDomElement> 0063 #include <QFileInfo> 0064 #include <QImage> 0065 #include <QList> 0066 #include <QPainter> 0067 #include <QRect> 0068 #include <QScopedPointer> 0069 #include <QSize> 0070 #include <QStringList> 0071 #include <QtGlobal> 0072 #include <QTimer> 0073 #include <QWidget> 0074 #include <QFuture> 0075 #include <QFutureWatcher> 0076 #include <QUuid> 0077 0078 // Krita Image 0079 #include <kis_image_animation_interface.h> 0080 #include <kis_config.h> 0081 #include <flake/kis_shape_layer.h> 0082 #include <kis_group_layer.h> 0083 #include <kis_image.h> 0084 #include <kis_layer.h> 0085 #include <kis_name_server.h> 0086 #include <kis_paint_layer.h> 0087 #include <kis_painter.h> 0088 #include <kis_selection.h> 0089 #include <kis_fill_painter.h> 0090 #include <kis_document_undo_store.h> 0091 #include <kis_idle_watcher.h> 0092 #include <kis_signal_auto_connection.h> 0093 #include <kis_canvas_widget_base.h> 0094 #include "kis_layer_utils.h" 0095 #include "kis_selection_mask.h" 0096 0097 // Local 0098 #include "KisViewManager.h" 0099 #include "kis_clipboard.h" 0100 #include "widgets/kis_custom_image_widget.h" 0101 #include "canvas/kis_canvas2.h" 0102 #include "flake/kis_shape_controller.h" 0103 #include "widgets/kis_progress_widget.h" 0104 #include "kis_canvas_resource_provider.h" 0105 #include "KisResourceServerProvider.h" 0106 #include "kis_node_manager.h" 0107 #include "KisPart.h" 0108 #include "KisApplication.h" 0109 #include "KisDocument.h" 0110 #include "KisImportExportManager.h" 0111 #include "KisView.h" 0112 #include "kis_grid_config.h" 0113 #include "kis_guides_config.h" 0114 #include "KisImageBarrierLock.h" 0115 #include "KisReferenceImagesLayer.h" 0116 #include "dialogs/KisRecoverNamedAutosaveDialog.h" 0117 0118 #include <mutex> 0119 #include "kis_config_notifier.h" 0120 #include "kis_async_action_feedback.h" 0121 #include "KisCloneDocumentStroke.h" 0122 0123 #include <kis_algebra_2d.h> 0124 #include <KisMirrorAxisConfig.h> 0125 #include <KisDecorationsWrapperLayer.h> 0126 #include "kis_simple_stroke_strategy.h" 0127 #include <KisCursorOverrideLock.h> 0128 0129 // Define the protocol used here for embedded documents' URL 0130 // This used to "store" but QUrl didn't like it, 0131 // so let's simply make it "tar" ! 0132 #define STORE_PROTOCOL "tar" 0133 // The internal path is a hack to make QUrl happy and for document children 0134 #define INTERNAL_PROTOCOL "intern" 0135 #define INTERNAL_PREFIX "intern:/" 0136 // Warning, keep it sync in koStore.cc 0137 0138 #include <unistd.h> 0139 0140 #ifdef Q_OS_MACOS 0141 #include "KisMacosSecurityBookmarkManager.h" 0142 #endif 0143 0144 using namespace std; 0145 0146 namespace { 0147 constexpr int errorMessageTimeout = 5000; 0148 constexpr int successMessageTimeout = 1000; 0149 } 0150 0151 0152 /********************************************************** 0153 * 0154 * KisDocument 0155 * 0156 **********************************************************/ 0157 0158 //static 0159 QString KisDocument::newObjectName() 0160 { 0161 static int s_docIFNumber = 0; 0162 QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); 0163 return name; 0164 } 0165 0166 0167 class UndoStack : public KUndo2Stack 0168 { 0169 public: 0170 UndoStack(KisDocument *doc) 0171 : KUndo2Stack(doc), 0172 m_doc(doc) 0173 { 0174 } 0175 0176 void setIndex(int idx) override { 0177 m_postponedJobs.append({PostponedJob::SetIndex, idx}); 0178 processPostponedJobs(); 0179 } 0180 0181 void notifySetIndexChangedOneCommand() override { 0182 KisImageWSP image = this->image(); 0183 image->unlock(); 0184 0185 /** 0186 * Some very weird commands may emit blocking signals to 0187 * the GUI (e.g. KisGuiContextCommand). Here is the best thing 0188 * we can do to avoid the deadlock 0189 */ 0190 while(!image->tryBarrierLock()) { 0191 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); 0192 } 0193 } 0194 0195 void undo() override { 0196 m_postponedJobs.append({PostponedJob::Undo, 0}); 0197 processPostponedJobs(); 0198 } 0199 0200 0201 void redo() override { 0202 m_postponedJobs.append({PostponedJob::Redo, 0}); 0203 processPostponedJobs(); 0204 } 0205 0206 private: 0207 KisImageWSP image() { 0208 KisImageWSP currentImage = m_doc->image(); 0209 Q_ASSERT(currentImage); 0210 return currentImage; 0211 } 0212 0213 void setIndexImpl(int idx) { 0214 KisImageWSP image = this->image(); 0215 image->requestStrokeCancellation(); 0216 if(image->tryBarrierLock()) { 0217 KUndo2Stack::setIndex(idx); 0218 image->unlock(); 0219 } 0220 } 0221 0222 void undoImpl() { 0223 KisImageWSP image = this->image(); 0224 image->requestUndoDuringStroke(); 0225 0226 if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { 0227 return; 0228 } 0229 0230 if(image->tryBarrierLock()) { 0231 KUndo2Stack::undo(); 0232 image->unlock(); 0233 } 0234 } 0235 0236 void redoImpl() { 0237 KisImageWSP image = this->image(); 0238 image->requestRedoDuringStroke(); 0239 0240 if(image->tryBarrierLock()) { 0241 KUndo2Stack::redo(); 0242 image->unlock(); 0243 } 0244 } 0245 0246 void processPostponedJobs() { 0247 /** 0248 * Some undo commands may call QApplication::processEvents(), 0249 * see notifySetIndexChangedOneCommand(). That may cause 0250 * recursive calls to the undo stack methods when used from 0251 * the Undo History docker. Here we try to handle that gracefully 0252 * by accumulating all the requests and executing them at the 0253 * topmost level of recursion. 0254 */ 0255 if (m_recursionCounter > 0) return; 0256 0257 m_recursionCounter++; 0258 0259 while (!m_postponedJobs.isEmpty()) { 0260 PostponedJob job = m_postponedJobs.dequeue(); 0261 switch (job.type) { 0262 case PostponedJob::SetIndex: 0263 setIndexImpl(job.index); 0264 break; 0265 case PostponedJob::Redo: 0266 redoImpl(); 0267 break; 0268 case PostponedJob::Undo: 0269 undoImpl(); 0270 break; 0271 } 0272 } 0273 0274 m_recursionCounter--; 0275 } 0276 0277 private: 0278 int m_recursionCounter = 0; 0279 0280 struct PostponedJob { 0281 enum Type { 0282 Undo = 0, 0283 Redo, 0284 SetIndex 0285 }; 0286 Type type = Undo; 0287 int index = 0; 0288 }; 0289 QQueue<PostponedJob> m_postponedJobs; 0290 0291 KisDocument *m_doc; 0292 }; 0293 0294 class Q_DECL_HIDDEN KisDocument::Private 0295 { 0296 public: 0297 Private(KisDocument *_q) 0298 : q(_q) 0299 , docInfo(new KoDocumentInfo(_q)) // deleted by QObject 0300 , importExportManager(new KisImportExportManager(_q)) // deleted manually 0301 , autoSaveTimer(new QTimer(_q)) 0302 , undoStack(new UndoStack(_q)) // deleted by QObject 0303 , m_bAutoDetectedMime(false) 0304 , modified(false) 0305 , readwrite(true) 0306 , firstMod(QDateTime::currentDateTime()) 0307 , lastMod(firstMod) 0308 , nserver(new KisNameServer(1)) 0309 , imageIdleWatcher(2000 /*ms*/) 0310 , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) 0311 , batchMode(false) 0312 { 0313 if (QLocale().measurementSystem() == QLocale::ImperialSystem) { 0314 unit = KoUnit::Inch; 0315 } else { 0316 unit = KoUnit::Centimeter; 0317 } 0318 connect(&imageIdleWatcher, SIGNAL(startedIdleMode()), q, SLOT(slotPerformIdleRoutines())); 0319 } 0320 0321 Private(const Private &rhs, KisDocument *_q) 0322 : q(_q) 0323 , docInfo(new KoDocumentInfo(*rhs.docInfo, _q)) 0324 , importExportManager(new KisImportExportManager(_q)) 0325 , autoSaveTimer(new QTimer(_q)) 0326 , undoStack(new UndoStack(_q)) 0327 , nserver(new KisNameServer(*rhs.nserver)) 0328 , preActivatedNode(0) // the node is from another hierarchy! 0329 , imageIdleWatcher(2000 /*ms*/) 0330 { 0331 copyFromImpl(rhs, _q, CONSTRUCT); 0332 connect(&imageIdleWatcher, SIGNAL(startedIdleMode()), q, SLOT(slotPerformIdleRoutines())); 0333 } 0334 0335 ~Private() { 0336 // Don't delete m_d->shapeController because it's in a QObject hierarchy. 0337 delete nserver; 0338 } 0339 0340 KisDocument *q = 0; 0341 KoDocumentInfo *docInfo = 0; 0342 0343 KoUnit unit; 0344 0345 KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] 0346 0347 QByteArray mimeType; // The actual mimeType of the document 0348 QByteArray outputMimeType; // The mimeType to use when saving 0349 0350 QTimer *autoSaveTimer; 0351 QString lastErrorMessage; // see openFile() 0352 QString lastWarningMessage; 0353 int autoSaveDelay = 300; // in seconds, 0 to disable. 0354 bool modifiedAfterAutosave = false; 0355 bool isAutosaving = false; 0356 bool disregardAutosaveFailure = false; 0357 int autoSaveFailureCount = 0; 0358 0359 KUndo2Stack *undoStack = 0; 0360 0361 KisGuidesConfig guidesConfig; 0362 KisMirrorAxisConfig mirrorAxisConfig; 0363 0364 bool m_bAutoDetectedMime = false; // whether the mimeType in the arguments was detected by the part itself 0365 QString m_path; // local url - the one displayed to the user. 0366 QString m_file; // Local file - the only one the part implementation should deal with. 0367 0368 QMutex savingMutex; 0369 0370 bool modified = false; 0371 bool readwrite = false; 0372 0373 QDateTime firstMod; 0374 QDateTime lastMod; 0375 0376 KisNameServer *nserver; 0377 0378 KisImageSP image; 0379 KisImageSP savingImage; 0380 0381 KisNodeWSP preActivatedNode; 0382 KisShapeController* shapeController = 0; 0383 KoShapeController* koShapeController = 0; 0384 KisIdleWatcher imageIdleWatcher; 0385 QScopedPointer<KisSignalAutoConnection> imageIdleConnection; 0386 0387 QList<KisPaintingAssistantSP> assistants; 0388 0389 StoryboardItemList m_storyboardItemList; 0390 QVector<StoryboardComment> m_storyboardCommentList; 0391 0392 QVector<QFileInfo> audioTracks; 0393 qreal audioLevel = 1.0; 0394 0395 QColor globalAssistantsColor; 0396 0397 KisGridConfig gridConfig; 0398 0399 bool imageModifiedWithoutUndo = false; 0400 bool modifiedWhileSaving = false; 0401 QScopedPointer<KisDocument> backgroundSaveDocument; 0402 QPointer<KoUpdater> savingUpdater; 0403 QFuture<KisImportExportErrorCode> childSavingFuture; 0404 KritaUtils::ExportFileJob backgroundSaveJob; 0405 KisSignalAutoConnectionsStore referenceLayerConnections; 0406 0407 bool isRecovered = false; 0408 0409 bool batchMode { false }; 0410 bool decorationsSyncingDisabled = false; 0411 bool wasStorageAdded = false; 0412 0413 0414 // Resources saved in the .kra document 0415 QString linkedResourcesStorageID; 0416 KisResourceStorageSP linkedResourceStorage; 0417 0418 // Resources saved into other components of the kra file 0419 QString embeddedResourcesStorageID; 0420 KisResourceStorageSP embeddedResourceStorage; 0421 0422 void syncDecorationsWrapperLayerState(); 0423 0424 void setImageAndInitIdleWatcher(KisImageSP _image) { 0425 image = _image; 0426 0427 imageIdleWatcher.setTrackedImage(image); 0428 } 0429 0430 void copyFrom(const Private &rhs, KisDocument *q); 0431 void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy); 0432 0433 void uploadLinkedResourcesFromLayersToStorage(); 0434 KisDocument* lockAndCloneImpl(bool fetchResourcesFromLayers); 0435 0436 void updateDocumentMetadataOnSaving(const QString &filePath, const QByteArray &mimeType); 0437 0438 /// clones the palette list oldList 0439 /// the ownership of the returned KoColorSet * belongs to the caller 0440 class StrippedSafeSavingLocker; 0441 }; 0442 0443 0444 void KisDocument::Private::syncDecorationsWrapperLayerState() 0445 { 0446 if (!this->image || this->decorationsSyncingDisabled) return; 0447 0448 KisImageSP image = this->image; 0449 KisDecorationsWrapperLayerSP decorationsLayer = 0450 KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(image->root()); 0451 0452 const bool needsDecorationsWrapper = 0453 gridConfig.showGrid() || (guidesConfig.showGuides() && guidesConfig.hasGuides()) || !assistants.isEmpty(); 0454 0455 struct SyncDecorationsWrapperStroke : public KisSimpleStrokeStrategy { 0456 SyncDecorationsWrapperStroke(KisDocument *document, bool needsDecorationsWrapper) 0457 : KisSimpleStrokeStrategy(QLatin1String("sync-decorations-wrapper"), 0458 kundo2_noi18n("start-isolated-mode")), 0459 m_document(document), 0460 m_needsDecorationsWrapper(needsDecorationsWrapper) 0461 { 0462 this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); 0463 setClearsRedoOnStart(false); 0464 setRequestsOtherStrokesToEnd(false); 0465 } 0466 0467 void initStrokeCallback() override { 0468 KisDecorationsWrapperLayerSP decorationsLayer = 0469 KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(m_document->image()->root()); 0470 0471 if (m_needsDecorationsWrapper && !decorationsLayer) { 0472 m_document->image()->addNode(new KisDecorationsWrapperLayer(m_document)); 0473 } else if (!m_needsDecorationsWrapper && decorationsLayer) { 0474 m_document->image()->removeNode(decorationsLayer); 0475 } 0476 } 0477 0478 private: 0479 KisDocument *m_document = 0; 0480 bool m_needsDecorationsWrapper = false; 0481 }; 0482 0483 KisStrokeId id = image->startStroke(new SyncDecorationsWrapperStroke(q, needsDecorationsWrapper)); 0484 image->endStroke(id); 0485 } 0486 0487 void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q) 0488 { 0489 copyFromImpl(rhs, q, KisDocument::REPLACE); 0490 } 0491 0492 void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy) 0493 { 0494 if (policy == REPLACE) { 0495 delete docInfo; 0496 } 0497 docInfo = (new KoDocumentInfo(*rhs.docInfo, q)); 0498 unit = rhs.unit; 0499 mimeType = rhs.mimeType; 0500 outputMimeType = rhs.outputMimeType; 0501 0502 if (policy == REPLACE) { 0503 q->setGuidesConfig(rhs.guidesConfig); 0504 q->setMirrorAxisConfig(rhs.mirrorAxisConfig); 0505 q->setModified(rhs.modified); 0506 q->setAssistants(KisPaintingAssistant::cloneAssistantList(rhs.assistants)); 0507 q->setStoryboardItemList(StoryboardItem::cloneStoryboardItemList(rhs.m_storyboardItemList)); 0508 q->setStoryboardCommentList(rhs.m_storyboardCommentList); 0509 q->setAudioTracks(rhs.audioTracks); 0510 q->setAudioVolume(rhs.audioLevel); 0511 q->setGridConfig(rhs.gridConfig); 0512 } else { 0513 // in CONSTRUCT mode, we cannot use the functions of KisDocument 0514 // because KisDocument does not yet have a pointer to us. 0515 guidesConfig = rhs.guidesConfig; 0516 mirrorAxisConfig = rhs.mirrorAxisConfig; 0517 modified = rhs.modified; 0518 assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants); 0519 m_storyboardItemList = StoryboardItem::cloneStoryboardItemList(rhs.m_storyboardItemList); 0520 m_storyboardCommentList = rhs.m_storyboardCommentList; 0521 audioTracks = rhs.audioTracks; 0522 audioLevel = rhs.audioLevel; 0523 gridConfig = rhs.gridConfig; 0524 } 0525 imageModifiedWithoutUndo = rhs.imageModifiedWithoutUndo; 0526 m_bAutoDetectedMime = rhs.m_bAutoDetectedMime; 0527 m_path = rhs.m_path; 0528 m_file = rhs.m_file; 0529 readwrite = rhs.readwrite; 0530 firstMod = rhs.firstMod; 0531 lastMod = rhs.lastMod; 0532 // XXX: the display properties will be shared between different snapshots 0533 globalAssistantsColor = rhs.globalAssistantsColor; 0534 batchMode = rhs.batchMode; 0535 0536 0537 if (rhs.linkedResourceStorage) { 0538 linkedResourceStorage = rhs.linkedResourceStorage->clone(); 0539 } 0540 0541 if (rhs.embeddedResourceStorage) { 0542 embeddedResourceStorage = rhs.embeddedResourceStorage->clone(); 0543 } 0544 0545 } 0546 0547 class KisDocument::Private::StrippedSafeSavingLocker { 0548 public: 0549 StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) 0550 : m_locked(false) 0551 , m_image(image) 0552 , m_savingLock(savingMutex) 0553 , m_imageLock(image) 0554 0555 { 0556 /** 0557 * Initial try to lock both objects. Locking the image guards 0558 * us from any image composition threads running in the 0559 * background, while savingMutex guards us from entering the 0560 * saving code twice by autosave and main threads. 0561 * 0562 * Since we are trying to lock multiple objects, so we should 0563 * do it in a safe manner. 0564 */ 0565 m_locked = std::try_lock(m_imageLock, *m_savingLock) < 0; 0566 0567 if (!m_locked) { 0568 m_image->requestStrokeEnd(); 0569 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); 0570 0571 // one more try... 0572 m_locked = std::try_lock(m_imageLock, *m_savingLock) < 0; 0573 } 0574 } 0575 0576 ~StrippedSafeSavingLocker() { 0577 if (m_locked) { 0578 m_imageLock.unlock(); 0579 m_savingLock->unlock(); 0580 } 0581 } 0582 0583 bool successfullyLocked() const { 0584 return m_locked; 0585 } 0586 0587 private: 0588 Q_DISABLE_COPY_MOVE(StrippedSafeSavingLocker) 0589 0590 bool m_locked; 0591 KisImageSP m_image; 0592 QMutex *m_savingLock; 0593 KisImageReadOnlyBarrierLockAdapter m_imageLock; 0594 }; 0595 0596 KisDocument::KisDocument(bool addStorage) 0597 : d(new Private(this)) 0598 { 0599 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); 0600 connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); 0601 connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); 0602 setObjectName(newObjectName()); 0603 0604 #ifdef Q_OS_MACOS 0605 KisMacosSecurityBookmarkManager *bookmarkmngr = KisMacosSecurityBookmarkManager::instance(); 0606 if (bookmarkmngr->isSandboxed()) { 0607 connect(this, SIGNAL(sigSavingFinished(const QString&)), bookmarkmngr, SLOT(slotCreateBookmark(const QString&))); 0608 } 0609 #endif 0610 0611 0612 if (addStorage) { 0613 d->linkedResourcesStorageID = QUuid::createUuid().toString(); 0614 d->linkedResourceStorage.reset(new KisResourceStorage(d->linkedResourcesStorageID)); 0615 KisResourceLocator::instance()->addStorage(d->linkedResourcesStorageID, d->linkedResourceStorage); 0616 0617 d->embeddedResourcesStorageID = QUuid::createUuid().toString(); 0618 d->embeddedResourceStorage.reset(new KisResourceStorage(d->embeddedResourcesStorageID)); 0619 KisResourceLocator::instance()->addStorage(d->embeddedResourcesStorageID, d->embeddedResourceStorage); 0620 0621 d->wasStorageAdded = true; 0622 } 0623 0624 // preload the krita resources 0625 KisResourceServerProvider::instance(); 0626 0627 d->shapeController = new KisShapeController(d->nserver, d->undoStack, this); 0628 d->koShapeController = new KoShapeController(0, d->shapeController); 0629 d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); 0630 0631 slotConfigChanged(); 0632 } 0633 0634 KisDocument::KisDocument(const KisDocument &rhs, bool addStorage) 0635 : QObject(), 0636 d(new Private(*rhs.d, this)) 0637 { 0638 copyFromDocumentImpl(rhs, CONSTRUCT); 0639 0640 if (addStorage) { 0641 KisResourceLocator::instance()->addStorage(d->linkedResourcesStorageID, d->linkedResourceStorage); 0642 KisResourceLocator::instance()->addStorage(d->embeddedResourcesStorageID, d->embeddedResourceStorage); 0643 d->wasStorageAdded = true; 0644 } 0645 } 0646 0647 KisDocument::~KisDocument() 0648 { 0649 // wait until all the pending operations are in progress 0650 waitForSavingToComplete(); 0651 d->imageIdleWatcher.setTrackedImage(0); 0652 0653 /** 0654 * Push a timebomb, which will try to release the memory after 0655 * the document has been deleted 0656 */ 0657 KisPaintDevice::createMemoryReleaseObject()->deleteLater(); 0658 0659 d->autoSaveTimer->disconnect(this); 0660 d->autoSaveTimer->stop(); 0661 0662 delete d->importExportManager; 0663 0664 // Despite being QObject they needs to be deleted before the image 0665 delete d->shapeController; 0666 0667 delete d->koShapeController; 0668 0669 if (d->image) { 0670 d->image->notifyAboutToBeDeleted(); 0671 0672 /** 0673 * WARNING: We should wait for all the internal image jobs to 0674 * finish before entering KisImage's destructor. The problem is, 0675 * while execution of KisImage::~KisImage, all the weak shared 0676 * pointers pointing to the image enter an inconsistent 0677 * state(!). The shared counter is already zero and destruction 0678 * has started, but the weak reference doesn't know about it, 0679 * because KisShared::~KisShared hasn't been executed yet. So all 0680 * the threads running in background and having weak pointers will 0681 * enter the KisImage's destructor as well. 0682 */ 0683 0684 d->image->requestStrokeCancellation(); 0685 d->image->waitForDone(); 0686 0687 // clear undo commands that can still point to the image 0688 d->undoStack->clear(); 0689 d->image->waitForDone(); 0690 0691 KisImageWSP sanityCheckPointer = d->image; 0692 Q_UNUSED(sanityCheckPointer); 0693 0694 // The following line trigger the deletion of the image 0695 d->image.clear(); 0696 0697 // check if the image has actually been deleted 0698 KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); 0699 } 0700 if (d->wasStorageAdded) { 0701 if (KisResourceLocator::instance()->hasStorage(d->linkedResourcesStorageID)) { 0702 KisResourceLocator::instance()->removeStorage(d->linkedResourcesStorageID); 0703 } 0704 if (KisResourceLocator::instance()->hasStorage(d->embeddedResourcesStorageID)) { 0705 KisResourceLocator::instance()->removeStorage(d->embeddedResourcesStorageID); 0706 } 0707 } 0708 0709 delete d; 0710 } 0711 0712 QString KisDocument::embeddedResourcesStorageId() const 0713 { 0714 return d->embeddedResourcesStorageID; 0715 } 0716 0717 QString KisDocument::linkedResourcesStorageId() const 0718 { 0719 return d->linkedResourcesStorageID; 0720 } 0721 0722 KisDocument *KisDocument::clone(bool addStorage) 0723 { 0724 return new KisDocument(*this, addStorage); 0725 } 0726 0727 bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting) 0728 { 0729 QFileInfo filePathInfo(job.filePath); 0730 0731 if (filePathInfo.exists() && !filePathInfo.isWritable()) { 0732 slotCompleteSavingDocument(job, ImportExportCodes::NoAccessToWrite, 0733 i18n("%1 cannot be written to. Please save under a different name.", job.filePath), 0734 ""); 0735 return false; 0736 } 0737 0738 KisConfig cfg(true); 0739 if (cfg.backupFile() && filePathInfo.exists()) { 0740 0741 QString backupDir; 0742 0743 switch(cfg.readEntry<int>("backupfilelocation", 0)) { 0744 case 1: 0745 backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); 0746 break; 0747 case 2: 0748 backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); 0749 break; 0750 default: 0751 #ifdef Q_OS_ANDROID 0752 // We deal with URIs, there may or may not be a "directory" 0753 backupDir = KisAutoSaveRecoveryDialog::autoSaveLocation(); 0754 QDir().mkpath(backupDir); 0755 #endif 0756 0757 #ifdef Q_OS_MACOS 0758 KisMacosSecurityBookmarkManager *bookmarkmngr = KisMacosSecurityBookmarkManager::instance(); 0759 if (bookmarkmngr->isSandboxed()) { 0760 // If the user does not have directory permission force backup 0761 // files to be inside Container tmp 0762 QUrl fileUrl = QUrl::fromLocalFile(job.filePath); 0763 if( !bookmarkmngr->parentDirHasPermissions(fileUrl.path()) ) { 0764 backupDir = QDir::tempPath(); 0765 } 0766 } 0767 #endif 0768 0769 // Do nothing: the empty string is user file location 0770 break; 0771 } 0772 0773 int numOfBackupsKept = cfg.readEntry<int>("numberofbackupfiles", 1); 0774 QString suffix = cfg.readEntry<QString>("backupfilesuffix", "~"); 0775 0776 if (numOfBackupsKept == 1) { 0777 if (!KisBackup::simpleBackupFile(job.filePath, backupDir, suffix)) { 0778 qWarning() << "Failed to create simple backup file!" << job.filePath << backupDir << suffix; 0779 KisUsageLogger::log(QString("Failed to create a simple backup for %1 in %2.") 0780 .arg(job.filePath, backupDir.isEmpty() 0781 ? "the same location as the file" 0782 : backupDir)); 0783 slotCompleteSavingDocument(job, ImportExportCodes::ErrorWhileWriting, i18nc("Saving error message", "Failed to create a backup file"), ""); 0784 return false; 0785 } 0786 else { 0787 KisUsageLogger::log(QString("Create a simple backup for %1 in %2.") 0788 .arg(job.filePath, backupDir.isEmpty() 0789 ? "the same location as the file" 0790 : backupDir)); 0791 } 0792 } 0793 else if (numOfBackupsKept > 1) { 0794 if (!KisBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept)) { 0795 qWarning() << "Failed to create numbered backup file!" << job.filePath << backupDir << suffix; 0796 KisUsageLogger::log(QString("Failed to create a numbered backup for %2.") 0797 .arg(job.filePath, backupDir.isEmpty() 0798 ? "the same location as the file" 0799 : backupDir)); 0800 slotCompleteSavingDocument(job, ImportExportCodes::ErrorWhileWriting, i18nc("Saving error message", "Failed to create a numbered backup file"), ""); 0801 return false; 0802 } 0803 else { 0804 KisUsageLogger::log(QString("Create a simple backup for %1 in %2.") 0805 .arg(job.filePath, backupDir.isEmpty() 0806 ? "the same location as the file" 0807 : backupDir)); 0808 } 0809 } 0810 } 0811 0812 //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); 0813 if (job.mimeType.isEmpty()) { 0814 KisImportExportErrorCode error = ImportExportCodes::FileFormatNotSupported; 0815 slotCompleteSavingDocument(job, error, error.errorMessage(), ""); 0816 return false; 0817 0818 } 0819 0820 const QString actionName = 0821 job.flags & KritaUtils::SaveIsExporting ? 0822 i18n("Exporting Document...") : 0823 i18n("Saving Document..."); 0824 0825 KritaUtils::JobResult result = 0826 initiateSavingInBackground(actionName, 0827 this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString, QString)), 0828 job, exportConfiguration, isAdvancedExporting); 0829 0830 if (result == KritaUtils::JobResult::Busy) { 0831 KisUsageLogger::log(QString("Failed to initiate saving %1 in background.").arg(job.filePath)); 0832 slotCompleteSavingDocument(job, ImportExportCodes::Busy, 0833 i18n("Could not start saving %1. Wait until the current save operation has finished.", job.filePath), 0834 ""); 0835 return false; 0836 } 0837 0838 return (result == KritaUtils::JobResult::Success); 0839 } 0840 0841 bool KisDocument::exportDocument(const QString &path, const QByteArray &mimeType, bool isAdvancedExporting, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) 0842 { 0843 using namespace KritaUtils; 0844 0845 SaveFlags flags = SaveIsExporting; 0846 if (showWarnings) { 0847 flags |= SaveShowWarnings; 0848 } 0849 0850 KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 " 0851 "framerate. Export configuration: %8") 0852 .arg(path, QString::fromLatin1(mimeType), QString::number(d->image->width()), 0853 QString::number(d->image->height()), QString::number(d->image->nlayers()), 0854 QString::number(d->image->animationInterface()->totalLength()), 0855 QString::number(d->image->animationInterface()->framerate()), 0856 (exportConfiguration ? exportConfiguration->toXML() : "No configuration"))); 0857 0858 return exportDocumentImpl(KritaUtils::ExportFileJob(path, 0859 mimeType, 0860 flags), 0861 exportConfiguration, isAdvancedExporting); 0862 } 0863 0864 bool KisDocument::saveAs(const QString &_path, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) 0865 { 0866 using namespace KritaUtils; 0867 0868 KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, " 0869 "%7 framerate. Export configuration: %8") 0870 .arg(_path, QString::fromLatin1(mimeType), QString::number(d->image->width()), 0871 QString::number(d->image->height()), QString::number(d->image->nlayers()), 0872 QString::number(d->image->animationInterface()->totalLength()), 0873 QString::number(d->image->animationInterface()->framerate()), 0874 (exportConfiguration ? exportConfiguration->toXML() : "No configuration"), 0875 path())); 0876 0877 // Check whether it's an existing resource were are saving to 0878 if (resourceSavingFilter(_path, mimeType, exportConfiguration)) { 0879 return true; 0880 } 0881 0882 return exportDocumentImpl(ExportFileJob(_path, 0883 mimeType, 0884 showWarnings ? SaveShowWarnings : SaveNone), 0885 exportConfiguration); 0886 } 0887 0888 bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) 0889 { 0890 return saveAs(path(), mimeType(), showWarnings, exportConfiguration); 0891 } 0892 0893 QByteArray KisDocument::serializeToNativeByteArray() 0894 { 0895 QBuffer buffer; 0896 0897 QScopedPointer<KisImportExportFilter> filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); 0898 filter->setBatchMode(true); 0899 filter->setMimeType(nativeFormatMimeType()); 0900 0901 Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); 0902 if (!locker.successfullyLocked()) { 0903 return buffer.data(); 0904 } 0905 0906 d->savingImage = d->image; 0907 0908 if (!filter->convert(this, &buffer).isOk()) { 0909 qWarning() << "serializeToByteArray():: Could not export to our native format"; 0910 } 0911 0912 return buffer.data(); 0913 } 0914 0915 class DlgLoadMessages : public QMessageBox 0916 { 0917 public: 0918 DlgLoadMessages(const QString &title, 0919 const QString &message, 0920 const QStringList &warnings = {}, 0921 const QString &details = {}) 0922 : QMessageBox(QMessageBox::Warning, title, message, QMessageBox::Ok, qApp->activeWindow()) 0923 { 0924 if (!details.isEmpty()) { 0925 setInformativeText(details); 0926 } 0927 if (!warnings.isEmpty()) { 0928 setDetailedText(warnings); 0929 } 0930 } 0931 0932 private: 0933 void setDetailedText(const QStringList &text) 0934 { 0935 QMessageBox::setDetailedText(text.first()); 0936 0937 QTextEdit *messageBox = findChild<QTextEdit *>(); 0938 0939 if (messageBox) { 0940 messageBox->setAcceptRichText(true); 0941 0942 QString warning = "<html><body><ul>"; 0943 Q_FOREACH (const QString &i, text) { 0944 warning += "\n<li>" + i + "</li>"; 0945 } 0946 warning += "</ul></body></html>"; 0947 0948 messageBox->setText(warning); 0949 } 0950 } 0951 }; 0952 0953 void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage) 0954 { 0955 if (status.isCancelled()) 0956 return; 0957 0958 const QString fileName = QFileInfo(job.filePath).fileName(); 0959 0960 if (!status.isOk()) { 0961 emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", 0962 "Error during saving %1: %2", 0963 fileName, 0964 errorMessage), errorMessageTimeout); 0965 0966 0967 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 0968 if (!fileBatchMode()) { 0969 DlgLoadMessages dlg(i18nc("@title:window", "Krita"), 0970 i18n("Could not save %1.", job.filePath), 0971 errorMessage.split("\n", Qt::SkipEmptyParts) 0972 + warningMessage.split("\n", Qt::SkipEmptyParts), 0973 status.errorMessage()); 0974 #else 0975 if (!fileBatchMode()) { 0976 DlgLoadMessages dlg(i18nc("@title:window", "Krita"), 0977 i18n("Could not save %1.", job.filePath), 0978 errorMessage.split("\n", QString::SkipEmptyParts) 0979 + warningMessage.split("\n", QString::SkipEmptyParts), 0980 status.errorMessage()); 0981 #endif 0982 0983 dlg.exec(); 0984 } 0985 } 0986 else { 0987 if (!fileBatchMode() && !warningMessage.isEmpty()) { 0988 0989 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 0990 QStringList reasons = warningMessage.split("\n", Qt::SkipEmptyParts); 0991 #else 0992 QStringList reasons = warningMessage.split("\n", QString::SkipEmptyParts); 0993 #endif 0994 0995 DlgLoadMessages dlg( 0996 i18nc("@title:window", "Krita"), 0997 i18nc("dialog box shown to the user if there were warnings while saving the document, " 0998 "%1 is the file path", 0999 "%1 has been saved but is incomplete.", 1000 job.filePath), 1001 reasons, 1002 reasons.isEmpty() 1003 ? "" 1004 : i18nc("dialog box shown to the user if there were warnings while saving the document", 1005 "Some problems were encountered when saving.")); 1006 dlg.exec(); 1007 } 1008 1009 1010 if (!(job.flags & KritaUtils::SaveIsExporting)) { 1011 const QString existingAutoSaveBaseName = localFilePath(); 1012 const bool wasRecovered = isRecovered(); 1013 1014 d->updateDocumentMetadataOnSaving(job.filePath, job.mimeType); 1015 1016 removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered); 1017 } 1018 1019 emit completed(); 1020 emit sigSavingFinished(job.filePath); 1021 1022 emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); 1023 } 1024 } 1025 1026 void KisDocument::Private::updateDocumentMetadataOnSaving(const QString &filePath, const QByteArray &mimeType) 1027 { 1028 q->setPath(filePath); 1029 q->setLocalFilePath(filePath); 1030 q->setMimeType(mimeType); 1031 q->updateEditingTime(true); 1032 1033 if (!modifiedWhileSaving) { 1034 /** 1035 * If undo stack is already clean/empty, it doesn't emit any 1036 * signals, so we might forget update document modified state 1037 * (which was set, e.g. while recovering an autosave file) 1038 */ 1039 1040 if (undoStack->isClean()) { 1041 q->setModified(false); 1042 } else { 1043 imageModifiedWithoutUndo = false; 1044 undoStack->setClean(); 1045 } 1046 } 1047 q->setRecovered(false); 1048 } 1049 1050 QByteArray KisDocument::mimeType() const 1051 { 1052 return d->mimeType; 1053 } 1054 1055 void KisDocument::setMimeType(const QByteArray & mimeType) 1056 { 1057 d->mimeType = mimeType; 1058 } 1059 1060 bool KisDocument::fileBatchMode() const 1061 { 1062 return d->batchMode; 1063 } 1064 1065 void KisDocument::setFileBatchMode(const bool batchMode) 1066 { 1067 d->batchMode = batchMode; 1068 } 1069 1070 void KisDocument::Private::uploadLinkedResourcesFromLayersToStorage() 1071 { 1072 /// Fetch resources from KisAdjustmentLayer, KisFilterMask and 1073 /// KisGeneratorLayer and put them into the cloned storage. This must be 1074 /// done in the context of the GUI thread, otherwise we will not be able to 1075 /// access resources database 1076 1077 KisDocument *doc = q; 1078 1079 KisLayerUtils::recursiveApplyNodes(doc->image()->root(), 1080 [doc] (KisNodeSP node) { 1081 if (KisNodeFilterInterface *layer = dynamic_cast<KisNodeFilterInterface*>(node.data())) { 1082 KisFilterConfigurationSP filterConfig = layer->filter(); 1083 if (!filterConfig) return; 1084 1085 QList<KoResourceLoadResult> linkedResources = filterConfig->linkedResources(KisGlobalResourcesInterface::instance()); 1086 1087 Q_FOREACH (const KoResourceLoadResult &result, linkedResources) { 1088 KIS_SAFE_ASSERT_RECOVER(result.type() != KoResourceLoadResult::EmbeddedResource) { continue; } 1089 1090 KoResourceSP resource = result.resource(); 1091 1092 if (!resource) { 1093 qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to fetch a resource" << result.signature(); 1094 continue; 1095 } 1096 1097 QBuffer buf; 1098 buf.open(QBuffer::WriteOnly); 1099 1100 KisResourceModel model(resource->resourceType().first); 1101 bool res = model.exportResource(resource, &buf); 1102 1103 buf.close(); 1104 1105 if (!res) { 1106 qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to export resource" << result.signature(); 1107 continue; 1108 } 1109 1110 buf.open(QBuffer::ReadOnly); 1111 1112 res = doc->d->linkedResourceStorage->importResource(resource->resourceType().first + "/" + resource->filename(), &buf); 1113 1114 buf.close(); 1115 1116 if (!res) { 1117 qWarning() << "WARNING: KisDocument::lockAndCloneForSaving failed to import resource" << result.signature(); 1118 continue; 1119 } 1120 } 1121 1122 } 1123 }); 1124 } 1125 1126 KisDocument *KisDocument::Private::lockAndCloneImpl(bool fetchResourcesFromLayers) 1127 { 1128 // force update of all the asynchronous nodes before cloning 1129 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); 1130 KisLayerUtils::forceAllDelayedNodesUpdate(image->root()); 1131 1132 KisMainWindow *window = KisPart::instance()->currentMainwindow(); 1133 if (window) { 1134 if (window->viewManager()) { 1135 if (!window->viewManager()->blockUntilOperationsFinished(image)) { 1136 return 0; 1137 } 1138 } 1139 } 1140 1141 Private::StrippedSafeSavingLocker locker(&savingMutex, image); 1142 if (!locker.successfullyLocked()) { 1143 return 0; 1144 } 1145 1146 KisDocument *doc = new KisDocument(*this->q, false); 1147 1148 if (fetchResourcesFromLayers) { 1149 doc->d->uploadLinkedResourcesFromLayersToStorage(); 1150 } 1151 1152 return doc; 1153 } 1154 1155 KisDocument* KisDocument::lockAndCloneForSaving() 1156 { 1157 return d->lockAndCloneImpl(true); 1158 } 1159 1160 KisDocument *KisDocument::lockAndCreateSnapshot() 1161 { 1162 return d->lockAndCloneImpl(false); 1163 } 1164 1165 void KisDocument::copyFromDocument(const KisDocument &rhs) 1166 { 1167 copyFromDocumentImpl(rhs, REPLACE); 1168 } 1169 1170 void KisDocument::copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy) 1171 { 1172 if (policy == REPLACE) { 1173 d->decorationsSyncingDisabled = true; 1174 d->copyFrom(*(rhs.d), this); 1175 d->decorationsSyncingDisabled = false; 1176 1177 d->undoStack->clear(); 1178 } else { 1179 // in CONSTRUCT mode, d should be already initialized 1180 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); 1181 connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); 1182 connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); 1183 1184 d->shapeController = new KisShapeController(d->nserver, d->undoStack, this); 1185 d->koShapeController = new KoShapeController(0, d->shapeController); 1186 d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); 1187 } 1188 1189 setObjectName(rhs.objectName()); 1190 1191 slotConfigChanged(); 1192 1193 if (rhs.d->image) { 1194 if (policy == REPLACE) { 1195 d->image->barrierLock(/* readOnly = */ false); 1196 rhs.d->image->barrierLock(/* readOnly = */ true); 1197 d->image->copyFromImage(*(rhs.d->image)); 1198 d->image->unlock(); 1199 rhs.d->image->unlock(); 1200 1201 setCurrentImage(d->image, /* forceInitialUpdate = */ true); 1202 } else { 1203 // clone the image with keeping the GUIDs of the layers intact 1204 // NOTE: we expect the image to be locked! 1205 setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false); 1206 } 1207 } 1208 1209 if (policy == REPLACE) { 1210 d->syncDecorationsWrapperLayerState(); 1211 } 1212 1213 if (rhs.d->preActivatedNode) { 1214 QQueue<KisNodeSP> linearizedNodes; 1215 KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(), 1216 [&linearizedNodes](KisNodeSP node) { 1217 linearizedNodes.enqueue(node); 1218 }); 1219 KisLayerUtils::recursiveApplyNodes(d->image->root(), 1220 [&linearizedNodes, &rhs, this](KisNodeSP node) { 1221 KisNodeSP refNode = linearizedNodes.dequeue(); 1222 if (rhs.d->preActivatedNode.data() == refNode.data()) { 1223 d->preActivatedNode = node; 1224 } 1225 }); 1226 } 1227 1228 // reinitialize references' signal connection 1229 KisReferenceImagesLayerSP referencesLayer = this->referenceImagesLayer(); 1230 if (referencesLayer) { 1231 d->referenceLayerConnections.clear(); 1232 d->referenceLayerConnections.addConnection( 1233 referencesLayer, SIGNAL(sigUpdateCanvas(QRectF)), 1234 this, SIGNAL(sigReferenceImagesChanged())); 1235 1236 emit sigReferenceImagesLayerChanged(referencesLayer); 1237 } 1238 1239 KisDecorationsWrapperLayerSP decorationsLayer = 1240 KisLayerUtils::findNodeByType<KisDecorationsWrapperLayer>(d->image->root()); 1241 if (decorationsLayer) { 1242 decorationsLayer->setDocument(this); 1243 } 1244 1245 1246 if (policy == REPLACE) { 1247 setModified(true); 1248 } 1249 } 1250 1251 bool KisDocument::exportDocumentSync(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) 1252 { 1253 { 1254 /** 1255 * The caller guarantees that no one else uses the document (usually, 1256 * it is a temporary document created specifically for exporting), so 1257 * we don't need to copy or lock the document. Instead we should just 1258 * ensure the barrier lock is synced and then released. 1259 */ 1260 Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); 1261 if (!locker.successfullyLocked()) { 1262 return false; 1263 } 1264 } 1265 1266 d->savingImage = d->image; 1267 1268 KisImportExportErrorCode status = 1269 d->importExportManager-> 1270 exportDocument(path, path, mimeType, false, exportConfiguration); 1271 1272 d->savingImage = 0; 1273 1274 return status.isOk(); 1275 } 1276 1277 1278 KritaUtils::JobResult KisDocument::initiateSavingInBackground(const QString actionName, 1279 const QObject *receiverObject, const char *receiverMethod, 1280 const KritaUtils::ExportFileJob &job, 1281 KisPropertiesConfigurationSP exportConfiguration,bool isAdvancedExporting) 1282 { 1283 return initiateSavingInBackground(actionName, receiverObject, receiverMethod, 1284 job, exportConfiguration, std::unique_ptr<KisDocument>(), isAdvancedExporting); 1285 } 1286 1287 KritaUtils::JobResult KisDocument::initiateSavingInBackground(const QString actionName, 1288 const QObject *receiverObject, const char *receiverMethod, 1289 const KritaUtils::ExportFileJob &job, 1290 KisPropertiesConfigurationSP exportConfiguration, 1291 std::unique_ptr<KisDocument> &&optionalClonedDocument,bool isAdvancedExporting) 1292 { 1293 KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), KritaUtils::JobResult::Failure); 1294 1295 QScopedPointer<KisDocument> clonedDocument; 1296 1297 if (!optionalClonedDocument) { 1298 clonedDocument.reset(lockAndCloneForSaving()); 1299 } else { 1300 clonedDocument.reset(optionalClonedDocument.release()); 1301 } 1302 1303 if (!d->savingMutex.tryLock()){ 1304 return KritaUtils::JobResult::Busy; 1305 } 1306 1307 if (!clonedDocument) { 1308 return KritaUtils::JobResult::Failure; 1309 } 1310 1311 auto waitForImage = [] (KisImageSP image) { 1312 KisMainWindow *window = KisPart::instance()->currentMainwindow(); 1313 if (window) { 1314 if (window->viewManager()) { 1315 window->viewManager()->blockUntilOperationsFinishedForced(image); 1316 } 1317 } 1318 }; 1319 1320 { 1321 KisNodeSP newRoot = clonedDocument->image()->root(); 1322 KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) { 1323 KisLayerUtils::forceAllDelayedNodesUpdate(newRoot); 1324 waitForImage(clonedDocument->image()); 1325 } 1326 } 1327 1328 if (clonedDocument->image()->hasOverlaySelectionMask()) { 1329 clonedDocument->image()->setOverlaySelectionMask(0); 1330 waitForImage(clonedDocument->image()); 1331 } 1332 1333 KisConfig cfg(true); 1334 if (cfg.trimKra()) { 1335 clonedDocument->image()->cropImage(clonedDocument->image()->bounds()); 1336 clonedDocument->image()->purgeUnusedData(false); 1337 waitForImage(clonedDocument->image()); 1338 } 1339 1340 KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) { 1341 waitForImage(clonedDocument->image()); 1342 } 1343 1344 KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, KritaUtils::JobResult::Failure); 1345 KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), KritaUtils::JobResult::Failure); 1346 d->backgroundSaveDocument.reset(clonedDocument.take()); 1347 d->backgroundSaveJob = job; 1348 d->modifiedWhileSaving = false; 1349 1350 if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { 1351 d->backgroundSaveDocument->d->isAutosaving = true; 1352 } 1353 1354 connect(d->backgroundSaveDocument.data(), 1355 SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString, QString)), 1356 this, 1357 SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString, QString))); 1358 1359 1360 connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString, QString)), 1361 receiverObject, receiverMethod, Qt::UniqueConnection); 1362 1363 bool started = 1364 d->backgroundSaveDocument->startExportInBackground(actionName, 1365 job.filePath, 1366 job.filePath, 1367 job.mimeType, 1368 job.flags & KritaUtils::SaveShowWarnings, 1369 exportConfiguration, isAdvancedExporting); 1370 1371 if (!started) { 1372 // the state should have been deinitialized in slotChildCompletedSavingInBackground() 1373 KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { 1374 d->backgroundSaveDocument.take()->deleteLater(); 1375 d->savingMutex.unlock(); 1376 d->backgroundSaveJob = KritaUtils::ExportFileJob(); 1377 } 1378 return KritaUtils::JobResult::Failure; 1379 } 1380 1381 return KritaUtils::JobResult::Success; 1382 } 1383 1384 1385 void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage) 1386 { 1387 KIS_ASSERT_RECOVER_RETURN(isSaving()); 1388 1389 KIS_ASSERT_RECOVER(d->backgroundSaveDocument) { 1390 d->savingMutex.unlock(); 1391 return; 1392 } 1393 1394 if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { 1395 d->backgroundSaveDocument->d->isAutosaving = false; 1396 } 1397 1398 d->backgroundSaveDocument.take()->deleteLater(); 1399 1400 KIS_ASSERT_RECOVER(d->backgroundSaveJob.isValid()) { 1401 d->savingMutex.unlock(); 1402 return; 1403 } 1404 1405 const KritaUtils::ExportFileJob job = d->backgroundSaveJob; 1406 d->backgroundSaveJob = KritaUtils::ExportFileJob(); 1407 1408 // unlock at the very end 1409 d->savingMutex.unlock(); 1410 1411 QFileInfo fi(job.filePath); 1412 KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3. Warning: %4. Size: %5") 1413 .arg(job.filePath, QString::fromLatin1(job.mimeType), 1414 (!status.isOk() ? errorMessage : "OK"), warningMessage, 1415 QString::number(fi.size()))); 1416 1417 emit sigCompleteBackgroundSaving(job, status, errorMessage, warningMessage); 1418 } 1419 1420 void KisDocument::slotAutoSaveImpl(std::unique_ptr<KisDocument> &&optionalClonedDocument) 1421 { 1422 if (!d->modified || !d->modifiedAfterAutosave) return; 1423 const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); 1424 1425 emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); 1426 1427 KisUsageLogger::log(QString("Autosaving: %1").arg(autoSaveFileName)); 1428 1429 const bool hadClonedDocument = bool(optionalClonedDocument); 1430 KritaUtils::JobResult result = KritaUtils::JobResult::Failure; 1431 1432 if (d->image->isIdle() || hadClonedDocument) { 1433 result = initiateSavingInBackground(i18n("Autosaving..."), 1434 this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString, QString)), 1435 KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 1436 0, 1437 std::move(optionalClonedDocument)); 1438 } else { 1439 emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); 1440 } 1441 1442 if (result != KritaUtils::JobResult::Success && !hadClonedDocument && d->autoSaveFailureCount >= 3) { 1443 KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); 1444 connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), 1445 this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), 1446 Qt::BlockingQueuedConnection); 1447 connect(stroke, SIGNAL(sigCloningCancelled()), 1448 this, SLOT(slotDocumentCloningCancelled()), 1449 Qt::BlockingQueuedConnection); 1450 1451 KisStrokeId strokeId = d->image->startStroke(stroke); 1452 d->image->endStroke(strokeId); 1453 1454 setInfiniteAutoSaveInterval(); 1455 1456 } else if (result != KritaUtils::JobResult::Success) { 1457 setEmergencyAutoSaveInterval(); 1458 } else { 1459 d->modifiedAfterAutosave = false; 1460 } 1461 } 1462 1463 bool KisDocument::resourceSavingFilter(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) 1464 { 1465 if (QFileInfo(path).absolutePath().startsWith(KisResourceLocator::instance()->resourceLocationBase())) { 1466 1467 QStringList pathParts = QFileInfo(path).absolutePath().split('/'); 1468 if (pathParts.size() > 0) { 1469 QString resourceType = pathParts.last(); 1470 if (KisResourceLoaderRegistry::instance()->resourceTypes().contains(resourceType)) { 1471 1472 KisResourceModel model(resourceType); 1473 model.setResourceFilter(KisResourceModel::ShowAllResources); 1474 1475 QString tempFileName = QDir::tempPath() + "/" + QFileInfo(path).fileName(); 1476 1477 if (QFileInfo(path).exists()) { 1478 1479 int outResourceId; 1480 KoResourceSP res; 1481 if (KisResourceCacheDb::getResourceIdFromVersionedFilename(QFileInfo(path).fileName(), resourceType, "", outResourceId)) { 1482 res = model.resourceForId(outResourceId); 1483 } 1484 1485 if (res) { 1486 d->modifiedWhileSaving = false; 1487 1488 if (!exportConfiguration) { 1489 QScopedPointer<KisImportExportFilter> filter( 1490 KisImportExportManager::filterForMimeType(mimeType, KisImportExportManager::Export)); 1491 if (filter) { 1492 exportConfiguration = filter->defaultConfiguration(nativeFormatMimeType(), mimeType); 1493 } 1494 } 1495 1496 if (exportConfiguration) { 1497 // make sure the name of the resource doesn't change 1498 exportConfiguration->setProperty("name", res->name()); 1499 } 1500 1501 if (exportDocumentSync(tempFileName, mimeType, exportConfiguration)) { 1502 QFile f2(tempFileName); 1503 f2.open(QFile::ReadOnly); 1504 1505 QByteArray ba = f2.readAll(); 1506 1507 QBuffer buf(&ba); 1508 buf.open(QBuffer::ReadOnly); 1509 1510 1511 1512 if (res->loadFromDevice(&buf, KisGlobalResourcesInterface::instance())) { 1513 if (model.updateResource(res)) { 1514 const QString filePath = 1515 KisResourceLocator::instance()->filePathForResource(res); 1516 1517 d->updateDocumentMetadataOnSaving(filePath, mimeType); 1518 1519 return true; 1520 } 1521 } 1522 } 1523 } 1524 } 1525 else { 1526 d->modifiedWhileSaving = false; 1527 if (exportDocumentSync(tempFileName, mimeType, exportConfiguration)) { 1528 KoResourceSP res = model.importResourceFile(tempFileName, false); 1529 if (res) { 1530 const QString filePath = 1531 KisResourceLocator::instance()->filePathForResource(res); 1532 1533 d->updateDocumentMetadataOnSaving(filePath, mimeType); 1534 1535 return true; 1536 } 1537 } 1538 } 1539 } 1540 } 1541 } 1542 return false; 1543 } 1544 1545 void KisDocument::slotAutoSave() 1546 { 1547 slotAutoSaveImpl(std::unique_ptr<KisDocument>()); 1548 } 1549 1550 void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) 1551 { 1552 slotAutoSaveImpl(std::unique_ptr<KisDocument>(clonedDocument)); 1553 } 1554 1555 void KisDocument::slotDocumentCloningCancelled() 1556 { 1557 setEmergencyAutoSaveInterval(); 1558 } 1559 1560 void KisDocument::slotPerformIdleRoutines() 1561 { 1562 d->image->explicitRegenerateLevelOfDetail(); 1563 1564 1565 /// TODO: automatic purging is disabled for now: it modifies 1566 /// data managers without creating a transaction, which breaks 1567 /// undo. 1568 1569 // d->image->purgeUnusedData(true); 1570 } 1571 1572 void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage, const QString &warningMessage) 1573 { 1574 Q_UNUSED(job); 1575 Q_UNUSED(warningMessage); 1576 1577 const QString fileName = QFileInfo(job.filePath).fileName(); 1578 1579 if (!status.isOk()) { 1580 setEmergencyAutoSaveInterval(); 1581 emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", 1582 "Error during autosaving %1: %2", 1583 fileName, 1584 exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); 1585 } else { 1586 KisConfig cfg(true); 1587 d->autoSaveDelay = cfg.autoSaveInterval(); 1588 1589 if (!d->modifiedWhileSaving) { 1590 d->autoSaveTimer->stop(); // until the next change 1591 d->autoSaveFailureCount = 0; 1592 } else { 1593 setNormalAutoSaveInterval(); 1594 } 1595 1596 emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); 1597 } 1598 } 1599 1600 bool KisDocument::startExportInBackground(const QString &actionName, 1601 const QString &location, 1602 const QString &realLocation, 1603 const QByteArray &mimeType, 1604 bool showWarnings, 1605 KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting) 1606 { 1607 d->savingImage = d->image; 1608 1609 KisMainWindow *window = KisPart::instance()->currentMainwindow(); 1610 if (window) { 1611 if (window->viewManager()) { 1612 d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); 1613 d->importExportManager->setUpdater(d->savingUpdater); 1614 } 1615 } 1616 1617 KisImportExportErrorCode initializationStatus(ImportExportCodes::OK); 1618 d->childSavingFuture = 1619 d->importExportManager->exportDocumentAsync(location, 1620 realLocation, 1621 mimeType, 1622 initializationStatus, 1623 showWarnings, 1624 exportConfiguration, 1625 isAdvancedExporting); 1626 1627 if (!initializationStatus.isOk()) { 1628 if (d->savingUpdater) { 1629 d->savingUpdater->cancel(); 1630 } 1631 d->savingImage.clear(); 1632 emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage(), ""); 1633 return false; 1634 } 1635 1636 typedef QFutureWatcher<KisImportExportErrorCode> StatusWatcher; 1637 StatusWatcher *watcher = new StatusWatcher(); 1638 watcher->setFuture(d->childSavingFuture); 1639 1640 connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); 1641 connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); 1642 1643 return true; 1644 } 1645 1646 void KisDocument::finishExportInBackground() 1647 { 1648 KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { 1649 emit sigBackgroundSavingFinished(ImportExportCodes::InternalError, "", ""); 1650 return; 1651 } 1652 1653 KisImportExportErrorCode status = d->childSavingFuture.result(); 1654 QString errorMessage = status.errorMessage(); 1655 QString warningMessage = d->lastWarningMessage; 1656 1657 if (!d->lastErrorMessage.isEmpty()) { 1658 if (status == ImportExportCodes::InternalError || status == ImportExportCodes::Failure) { 1659 errorMessage = d->lastErrorMessage; 1660 } else { 1661 errorMessage += "\n" + d->lastErrorMessage; 1662 } 1663 } 1664 1665 d->savingImage.clear(); 1666 d->childSavingFuture = QFuture<KisImportExportErrorCode>(); 1667 d->lastErrorMessage.clear(); 1668 d->lastWarningMessage.clear(); 1669 1670 if (d->savingUpdater) { 1671 d->savingUpdater->setProgress(100); 1672 } 1673 1674 emit sigBackgroundSavingFinished(status, errorMessage, warningMessage); 1675 } 1676 1677 void KisDocument::setReadWrite(bool readwrite) 1678 { 1679 const bool changed = readwrite != d->readwrite; 1680 1681 d->readwrite = readwrite; 1682 setNormalAutoSaveInterval(); 1683 1684 Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { 1685 mainWindow->setReadWrite(readwrite); 1686 } 1687 1688 if (changed) { 1689 Q_EMIT sigReadWriteChanged(readwrite); 1690 } 1691 } 1692 1693 void KisDocument::setAutoSaveDelay(int delay) 1694 { 1695 if (isReadWrite() && delay > 0) { 1696 d->autoSaveTimer->start(delay * 1000); 1697 } else { 1698 d->autoSaveTimer->stop(); 1699 } 1700 } 1701 1702 void KisDocument::setNormalAutoSaveInterval() 1703 { 1704 setAutoSaveDelay(d->autoSaveDelay); 1705 d->autoSaveFailureCount = 0; 1706 } 1707 1708 void KisDocument::setEmergencyAutoSaveInterval() 1709 { 1710 const int emergencyAutoSaveInterval = 10; /* sec */ 1711 setAutoSaveDelay(emergencyAutoSaveInterval); 1712 d->autoSaveFailureCount++; 1713 } 1714 1715 void KisDocument::setInfiniteAutoSaveInterval() 1716 { 1717 setAutoSaveDelay(-1); 1718 } 1719 1720 KoDocumentInfo *KisDocument::documentInfo() const 1721 { 1722 return d->docInfo; 1723 } 1724 1725 bool KisDocument::isModified() const 1726 { 1727 return d->modified; 1728 } 1729 1730 QPixmap KisDocument::generatePreview(const QSize& size) 1731 { 1732 KisImageSP image = d->image; 1733 if (d->savingImage) image = d->savingImage; 1734 1735 if (image) { 1736 QRect bounds = image->bounds(); 1737 QSize originalSize = bounds.size(); 1738 // QSize may round down one dimension to zero on extreme aspect rations, so ensure 1px minimum 1739 QSize newSize = originalSize.scaled(size, Qt::KeepAspectRatio).expandedTo({1, 1}); 1740 1741 bool pixelArt = false; 1742 // determine if the image is pixel art or not 1743 if (originalSize.width() < size.width() && originalSize.height() < size.height()) { 1744 // the image must be smaller than the requested preview 1745 // the scale must be integer 1746 if (newSize.height()%originalSize.height() == 0 && newSize.width()%originalSize.width() == 0) { 1747 pixelArt = true; 1748 } 1749 } 1750 1751 QPixmap px; 1752 if (pixelArt) { 1753 // do not scale while converting (because it uses Bicubic) 1754 QImage original = image->convertToQImage(originalSize, 0); 1755 // scale using FastTransformation, which is probably Nearest neighbour, suitable for pixel art 1756 QImage scaled = original.scaled(newSize, Qt::KeepAspectRatio, Qt::FastTransformation); 1757 px = QPixmap::fromImage(scaled); 1758 } else { 1759 px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); 1760 } 1761 if (px.size() == QSize(0,0)) { 1762 px = QPixmap(newSize); 1763 QPainter gc(&px); 1764 QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); 1765 gc.fillRect(px.rect(), checkBrush); 1766 gc.end(); 1767 } 1768 return px; 1769 } 1770 return QPixmap(size); 1771 } 1772 1773 QString KisDocument::generateAutoSaveFileName(const QString & path) const 1774 { 1775 QString retval; 1776 1777 // Using the extension allows to avoid relying on the mime magic when opening 1778 const QString extension (".kra"); 1779 QString prefix = KisConfig(true).readEntry<bool>("autosavefileshidden") ? QString(".") : QString(); 1780 QRegularExpression autosavePattern1("^\\..+-autosave.kra$"); 1781 QRegularExpression autosavePattern2("^.+-autosave.kra$"); 1782 1783 QFileInfo fi(path); 1784 QString dir = fi.absolutePath(); 1785 1786 #ifdef Q_OS_ANDROID 1787 // URIs may or may not have a directory backing them, so we save to our default autosave location 1788 if (path.startsWith("content://")) { 1789 dir = KisAutoSaveRecoveryDialog::autoSaveLocation(); 1790 QDir().mkpath(dir); 1791 } 1792 #endif 1793 1794 QString filename = fi.fileName(); 1795 1796 if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) { 1797 // Never saved? 1798 retval = QString("%1%2%3%4-%5-%6-autosave%7") 1799 .arg(KisAutoSaveRecoveryDialog::autoSaveLocation()) 1800 .arg('/') 1801 .arg(prefix) 1802 .arg("krita") 1803 .arg(qApp->applicationPid()) 1804 .arg(objectName()) 1805 .arg(extension); 1806 } else { 1807 // Beware: don't reorder arguments 1808 // otherwise in case of filename = '1-file.kra' it will become '.-file.kra-autosave.kra' instead of '.1-file.kra-autosave.kra' 1809 retval = QString("%1%2%3%4-autosave%5").arg(dir).arg('/').arg(prefix).arg(filename).arg(extension); 1810 } 1811 1812 //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; 1813 return retval; 1814 } 1815 1816 bool KisDocument::importDocument(const QString &_path) 1817 { 1818 bool ret; 1819 1820 dbgUI << "path=" << _path; 1821 1822 // open... 1823 ret = openPath(_path); 1824 1825 // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a 1826 // File --> Import 1827 if (ret) { 1828 dbgUI << "success, resetting url"; 1829 resetPath(); 1830 } 1831 1832 return ret; 1833 } 1834 1835 1836 bool KisDocument::openPath(const QString &_path, OpenFlags flags) 1837 { 1838 dbgUI << "path=" << _path; 1839 d->lastErrorMessage.clear(); 1840 1841 // Reimplemented, to add a check for autosave files and to improve error reporting 1842 if (_path.isEmpty()) { 1843 d->lastErrorMessage = i18n("Malformed Path\n%1", _path); // ## used anywhere ? 1844 return false; 1845 } 1846 1847 QString path = _path; 1848 QString original = ""; 1849 bool autosaveOpened = false; 1850 if (!fileBatchMode()) { 1851 QString file = path; 1852 QString asf = generateAutoSaveFileName(file); 1853 if (QFile::exists(asf)) { 1854 KisApplication *kisApp = static_cast<KisApplication*>(qApp); 1855 kisApp->hideSplashScreen(); 1856 //qDebug() <<"asf=" << asf; 1857 // ## TODO compare timestamps ? 1858 KisRecoverNamedAutosaveDialog dlg(0, file, asf); 1859 dlg.exec(); 1860 int res = dlg.result(); 1861 1862 switch (res) { 1863 case KisRecoverNamedAutosaveDialog::OpenAutosave : 1864 original = file; 1865 path = asf; 1866 autosaveOpened = true; 1867 break; 1868 case KisRecoverNamedAutosaveDialog::OpenMainFile : 1869 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); 1870 QFile::remove(asf); 1871 break; 1872 default: // Cancel 1873 return false; 1874 } 1875 } 1876 } 1877 1878 bool ret = openPathInternal(path); 1879 1880 if (autosaveOpened || flags & RecoveryFile) { 1881 setReadWrite(true); // enable save button 1882 setModified(true); 1883 setRecovered(true); 1884 1885 setPath(original); // since it was an autosave, it will be a local file 1886 setLocalFilePath(original); 1887 } 1888 else { 1889 if (ret) { 1890 1891 if (!(flags & DontAddToRecent)) { 1892 KisPart::instance()->addRecentURLToAllMainWindows(QUrl::fromLocalFile(_path)); 1893 } 1894 1895 QFileInfo fi(_path); 1896 setReadWrite(fi.isWritable()); 1897 } 1898 1899 setRecovered(false); 1900 } 1901 1902 return ret; 1903 } 1904 1905 bool KisDocument::openFile() 1906 { 1907 //dbgUI <<"for" << localFilePath(); 1908 if (!QFile::exists(localFilePath()) && !fileBatchMode()) { 1909 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); 1910 return false; 1911 } 1912 1913 QString filename = localFilePath(); 1914 QString typeName = mimeType(); 1915 1916 if (typeName.isEmpty()) { 1917 typeName = KisMimeDatabase::mimeTypeForFile(filename); 1918 } 1919 1920 // Allow to open backup files, don't keep the mimeType application/x-trash. 1921 if (typeName == "application/x-trash") { 1922 QString path = filename; 1923 while (path.length() > 0) { 1924 path.chop(1); 1925 typeName = KisMimeDatabase::mimeTypeForFile(path); 1926 //qDebug() << "\t" << path << typeName; 1927 if (!typeName.isEmpty()) { 1928 break; 1929 } 1930 } 1931 //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; 1932 } 1933 dbgUI << localFilePath() << "type:" << typeName; 1934 1935 KisMainWindow *window = KisPart::instance()->currentMainwindow(); 1936 KoUpdaterPtr updater; 1937 if (window && window->viewManager()) { 1938 updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); 1939 d->importExportManager->setUpdater(updater); 1940 } 1941 1942 KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName); 1943 1944 if (!status.isOk()) { 1945 if (window && window->viewManager()) { 1946 updater->cancel(); 1947 } 1948 QString msg = status.errorMessage(); 1949 KisUsageLogger::log(QString("Loading %1 failed: %2").arg(prettyPath(), msg)); 1950 1951 if (!msg.isEmpty() && !fileBatchMode()) { 1952 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 1953 DlgLoadMessages dlg(i18nc("@title:window", "Krita"), 1954 i18n("Could not open %1.", prettyPath()), 1955 errorMessage().split("\n", Qt::SkipEmptyParts) 1956 + warningMessage().split("\n", Qt::SkipEmptyParts), 1957 msg); 1958 #else 1959 DlgLoadMessages dlg(i18nc("@title:window", "Krita"), 1960 i18n("Could not open %1.", prettyPath()), 1961 errorMessage().split("\n", QString::SkipEmptyParts) 1962 + warningMessage().split("\n", QString::SkipEmptyParts), 1963 msg); 1964 #endif 1965 1966 dlg.exec(); 1967 } 1968 return false; 1969 } 1970 else if (!warningMessage().isEmpty() && !fileBatchMode()) { 1971 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 1972 DlgLoadMessages dlg(i18nc("@title:window", "Krita"), 1973 i18n("There were problems opening %1.", prettyPath()), 1974 warningMessage().split("\n", Qt::SkipEmptyParts)); 1975 #else 1976 DlgLoadMessages dlg(i18nc("@title:window", "Krita"), 1977 i18n("There were problems opening %1.", prettyPath()), 1978 warningMessage().split("\n", QString::SkipEmptyParts)); 1979 #endif 1980 1981 dlg.exec(); 1982 setPath(QString()); 1983 } 1984 1985 setMimeTypeAfterLoading(typeName); 1986 d->syncDecorationsWrapperLayerState(); 1987 emit sigLoadingFinished(); 1988 1989 undoStack()->clear(); 1990 1991 return true; 1992 } 1993 1994 void KisDocument::autoSaveOnPause() 1995 { 1996 if (!d->modified || !d->modifiedAfterAutosave) 1997 return; 1998 1999 const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); 2000 2001 bool started = exportDocumentSync(autoSaveFileName, nativeFormatMimeType()); 2002 2003 if (started) 2004 { 2005 d->modifiedAfterAutosave = false; 2006 dbgAndroid << "autoSaveOnPause successful"; 2007 } 2008 else 2009 { 2010 qWarning() << "Could not auto-save when paused"; 2011 } 2012 } 2013 2014 // shared between openFile and koMainWindow's "create new empty document" code 2015 void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) 2016 { 2017 d->mimeType = mimeType.toLatin1(); 2018 d->outputMimeType = d->mimeType; 2019 } 2020 2021 2022 bool KisDocument::loadNativeFormat(const QString & file_) 2023 { 2024 return openPath(file_); 2025 } 2026 2027 void KisDocument::setModified(bool mod) 2028 { 2029 if (mod) { 2030 updateEditingTime(false); 2031 } 2032 2033 if (d->isAutosaving) // ignore setModified calls due to autosaving 2034 return; 2035 2036 //dbgUI<<" url:" << url.path(); 2037 //dbgUI<<" mod="<<mod<<" MParts mod="<<KisParts::ReadWritePart::isModified()<<" isModified="<<isModified(); 2038 2039 if (mod && !d->autoSaveTimer->isActive()) { 2040 // First change since last autosave -> start the autosave timer 2041 setNormalAutoSaveInterval(); 2042 } 2043 d->modifiedAfterAutosave = mod; 2044 d->modifiedWhileSaving = mod; 2045 2046 if (!mod) { 2047 d->imageModifiedWithoutUndo = mod; 2048 } 2049 2050 if (mod == isModified()) 2051 return; 2052 2053 d->modified = mod; 2054 2055 if (mod) { 2056 documentInfo()->updateParameters(); 2057 } 2058 2059 emit modified(mod); 2060 } 2061 2062 void KisDocument::setRecovered(bool value) 2063 { 2064 const bool changed = value != d->isRecovered; 2065 2066 d->isRecovered = value; 2067 2068 if (changed) { 2069 Q_EMIT sigRecoveredChanged(value); 2070 } 2071 } 2072 2073 bool KisDocument::isRecovered() const 2074 { 2075 return d->isRecovered; 2076 } 2077 2078 void KisDocument::updateEditingTime(bool forceStoreElapsed) 2079 { 2080 QDateTime now = QDateTime::currentDateTime(); 2081 int firstModDelta = d->firstMod.secsTo(now); 2082 int lastModDelta = d->lastMod.secsTo(now); 2083 2084 if (lastModDelta > 30) { 2085 d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); 2086 d->firstMod = now; 2087 } else if (firstModDelta > 60 || forceStoreElapsed) { 2088 d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); 2089 d->firstMod = now; 2090 } 2091 2092 d->lastMod = now; 2093 } 2094 2095 QString KisDocument::prettyPath() const 2096 { 2097 QString _url(path()); 2098 #ifdef Q_OS_WIN 2099 _url = QDir::toNativeSeparators(_url); 2100 #endif 2101 return _url; 2102 } 2103 2104 // Get caption from document info (title(), in about page) 2105 QString KisDocument::caption() const 2106 { 2107 QString c; 2108 const QString _url(QFileInfo(path()).fileName()); 2109 2110 // if URL is empty...it is probably an unsaved file 2111 if (_url.isEmpty()) { 2112 c = " [" + i18n("Not Saved") + "] "; 2113 } else { 2114 c = _url; // Fall back to document URL 2115 } 2116 2117 return c; 2118 } 2119 2120 QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const 2121 { 2122 return createDomDocument("krita", tagName, version); 2123 } 2124 2125 //static 2126 QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) 2127 { 2128 QDomImplementation impl; 2129 QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); 2130 QDomDocumentType dtype = impl.createDocumentType(tagName, 2131 QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), 2132 url); 2133 // The namespace URN doesn't need to include the version number. 2134 QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); 2135 QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); 2136 doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); 2137 return doc; 2138 } 2139 2140 bool KisDocument::isNativeFormat(const QByteArray& mimeType) const 2141 { 2142 if (mimeType == nativeFormatMimeType()) 2143 return true; 2144 return extraNativeMimeTypes().contains(mimeType); 2145 } 2146 2147 void KisDocument::setErrorMessage(const QString& errMsg) 2148 { 2149 d->lastErrorMessage = errMsg; 2150 } 2151 2152 QString KisDocument::errorMessage() const 2153 { 2154 return d->lastErrorMessage; 2155 } 2156 2157 void KisDocument::setWarningMessage(const QString& warningMsg) 2158 { 2159 d->lastWarningMessage = warningMsg; 2160 } 2161 2162 QString KisDocument::warningMessage() const 2163 { 2164 return d->lastWarningMessage; 2165 } 2166 2167 2168 void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered) 2169 { 2170 // Eliminate any auto-save file 2171 QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir 2172 if (QFile::exists(asf)) { 2173 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); 2174 QFile::remove(asf); 2175 } 2176 asf = generateAutoSaveFileName(QString()); // and the one in $HOME 2177 2178 if (QFile::exists(asf)) { 2179 KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); 2180 QFile::remove(asf); 2181 } 2182 2183 QList<QRegularExpression> expressions; 2184 2185 expressions << QRegularExpression("^\\..+-autosave.kra$") 2186 << QRegularExpression("^.+-autosave.kra$"); 2187 2188 Q_FOREACH(const QRegularExpression &rex, expressions) { 2189 if (wasRecovered && 2190 !autosaveBaseName.isEmpty() && 2191 rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() && 2192 QFile::exists(autosaveBaseName)) { 2193 2194 KisUsageLogger::log(QString("Removing autosave file: %1").arg(autosaveBaseName)); 2195 QFile::remove(autosaveBaseName); 2196 } 2197 } 2198 } 2199 2200 KoUnit KisDocument::unit() const 2201 { 2202 return d->unit; 2203 } 2204 2205 void KisDocument::setUnit(const KoUnit &unit) 2206 { 2207 if (d->unit != unit) { 2208 d->unit = unit; 2209 emit unitChanged(unit); 2210 } 2211 } 2212 2213 KUndo2Stack *KisDocument::undoStack() 2214 { 2215 return d->undoStack; 2216 } 2217 2218 KisImportExportManager *KisDocument::importExportManager() const 2219 { 2220 return d->importExportManager; 2221 } 2222 2223 void KisDocument::slotUndoStackCleanChanged(bool value) 2224 { 2225 setModified(!value || d->imageModifiedWithoutUndo); 2226 } 2227 2228 void KisDocument::slotConfigChanged() 2229 { 2230 KisConfig cfg(true); 2231 2232 if (d->undoStack->undoLimit() != cfg.undoStackLimit()) { 2233 if (!d->undoStack->isClean()) { 2234 d->undoStack->clear(); 2235 // we set this because the document *has* changed, even though the 2236 // undo history was purged. 2237 setImageModifiedWithoutUndo(); 2238 } 2239 d->undoStack->setUndoLimit(cfg.undoStackLimit()); 2240 } 2241 d->undoStack->setUseCumulativeUndoRedo(cfg.useCumulativeUndoRedo()); 2242 d->undoStack->setCumulativeUndoData(cfg.cumulativeUndoData()); 2243 2244 d->autoSaveDelay = cfg.autoSaveInterval(); 2245 setNormalAutoSaveInterval(); 2246 } 2247 2248 void KisDocument::slotImageRootChanged() 2249 { 2250 d->syncDecorationsWrapperLayerState(); 2251 } 2252 2253 void KisDocument::clearUndoHistory() 2254 { 2255 d->undoStack->clear(); 2256 } 2257 2258 KisGridConfig KisDocument::gridConfig() const 2259 { 2260 return d->gridConfig; 2261 } 2262 2263 void KisDocument::setGridConfig(const KisGridConfig &config) 2264 { 2265 if (d->gridConfig != config) { 2266 d->gridConfig = config; 2267 d->syncDecorationsWrapperLayerState(); 2268 emit sigGridConfigChanged(config); 2269 2270 // Store last assigned value as future default... 2271 KisConfig cfg(false); 2272 cfg.setDefaultGridSpacing(config.spacing()); 2273 } 2274 } 2275 2276 QList<KoResourceLoadResult> KisDocument::linkedDocumentResources() 2277 { 2278 QList<KoResourceLoadResult> result; 2279 if (!d->linkedResourceStorage) { 2280 return result; 2281 } 2282 2283 Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { 2284 QSharedPointer<KisResourceStorage::ResourceIterator> iter = d->linkedResourceStorage->resources(resourceType); 2285 while (iter->hasNext()) { 2286 iter->next(); 2287 2288 QBuffer buf; 2289 buf.open(QBuffer::WriteOnly); 2290 bool exportSuccessful = 2291 d->linkedResourceStorage->exportResource(iter->url(), &buf); 2292 2293 KoResourceSP resource = d->linkedResourceStorage->resource(iter->url()); 2294 exportSuccessful &= bool(resource); 2295 2296 const QString name = resource ? resource->name() : QString(); 2297 const QString fileName = QFileInfo(iter->url()).fileName(); 2298 const KoResourceSignature signature(resourceType, 2299 KoMD5Generator::generateHash(buf.data()), 2300 fileName, name); 2301 2302 if (exportSuccessful) { 2303 result << KoEmbeddedResource(signature, buf.data()); 2304 } else { 2305 result << signature; 2306 } 2307 } 2308 } 2309 2310 return result; 2311 } 2312 2313 void KisDocument::setPaletteList(const QList<KoColorSetSP > &paletteList, bool emitSignal) 2314 { 2315 QList<KoColorSetSP> oldPaletteList; 2316 if (d->linkedResourceStorage) { 2317 QSharedPointer<KisResourceStorage::ResourceIterator> iter = d->linkedResourceStorage->resources(ResourceType::Palettes); 2318 while (iter->hasNext()) { 2319 iter->next(); 2320 KoResourceSP resource = iter->resource(); 2321 if (resource && resource->valid()) { 2322 oldPaletteList << resource.dynamicCast<KoColorSet>(); 2323 } 2324 } 2325 if (oldPaletteList != paletteList) { 2326 KisResourceModel resourceModel(ResourceType::Palettes); 2327 Q_FOREACH(KoColorSetSP palette, oldPaletteList) { 2328 if (!paletteList.contains(palette)) { 2329 resourceModel.setResourceInactive(resourceModel.indexForResource(palette)); 2330 } 2331 } 2332 Q_FOREACH(KoColorSetSP palette, paletteList) { 2333 if (!oldPaletteList.contains(palette)) { 2334 resourceModel.addResource(palette, d->linkedResourcesStorageID); 2335 } 2336 else { 2337 palette->setStorageLocation(d->linkedResourcesStorageID); 2338 resourceModel.updateResource(palette); 2339 } 2340 } 2341 if (emitSignal) { 2342 emit sigPaletteListChanged(oldPaletteList, paletteList); 2343 } 2344 } 2345 } 2346 } 2347 2348 StoryboardItemList KisDocument::getStoryboardItemList() 2349 { 2350 return d->m_storyboardItemList; 2351 } 2352 2353 void KisDocument::setStoryboardItemList(const StoryboardItemList &storyboardItemList, bool emitSignal) 2354 { 2355 d->m_storyboardItemList = storyboardItemList; 2356 if (emitSignal) { 2357 emit sigStoryboardItemListChanged(); 2358 } 2359 } 2360 2361 QVector<StoryboardComment> KisDocument::getStoryboardCommentsList() 2362 { 2363 return d->m_storyboardCommentList; 2364 } 2365 2366 void KisDocument::setStoryboardCommentList(const QVector<StoryboardComment> &storyboardCommentList, bool emitSignal) 2367 { 2368 d->m_storyboardCommentList = storyboardCommentList; 2369 if (emitSignal) { 2370 emit sigStoryboardCommentListChanged(); 2371 } 2372 } 2373 2374 QVector<QFileInfo> KisDocument::getAudioTracks() const { 2375 return d->audioTracks; 2376 } 2377 2378 void KisDocument::setAudioTracks(QVector<QFileInfo> f) 2379 { 2380 d->audioTracks = f; 2381 emit sigAudioTracksChanged(); 2382 } 2383 2384 void KisDocument::setAudioVolume(qreal level) 2385 { 2386 d->audioLevel = level; 2387 emit sigAudioLevelChanged(level); 2388 } 2389 2390 qreal KisDocument::getAudioLevel() 2391 { 2392 return d->audioLevel; 2393 } 2394 2395 const KisGuidesConfig& KisDocument::guidesConfig() const 2396 { 2397 return d->guidesConfig; 2398 } 2399 2400 void KisDocument::setGuidesConfig(const KisGuidesConfig &data) 2401 { 2402 if (d->guidesConfig == data) return; 2403 2404 d->guidesConfig = data; 2405 d->syncDecorationsWrapperLayerState(); 2406 emit sigGuidesConfigChanged(d->guidesConfig); 2407 } 2408 2409 2410 const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const 2411 { 2412 return d->mirrorAxisConfig; 2413 } 2414 2415 void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config) 2416 { 2417 if (d->mirrorAxisConfig == config) { 2418 return; 2419 } 2420 2421 d->mirrorAxisConfig = config; 2422 if (d->image) { 2423 d->image->setMirrorAxesCenter(KisAlgebra2D::absoluteToRelative(d->mirrorAxisConfig.axisPosition(), 2424 d->image->bounds())); 2425 } 2426 setModified(true); 2427 2428 emit sigMirrorAxisConfigChanged(); 2429 } 2430 2431 void KisDocument::resetPath() { 2432 setPath(QString()); 2433 setLocalFilePath(QString()); 2434 } 2435 2436 KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const 2437 { 2438 return new KoDocumentInfoDlg(parent, docInfo); 2439 } 2440 2441 bool KisDocument::isReadWrite() const 2442 { 2443 return d->readwrite; 2444 } 2445 2446 QString KisDocument::path() const 2447 { 2448 return d->m_path; 2449 } 2450 2451 bool KisDocument::closePath(bool promptToSave) 2452 { 2453 if (promptToSave) { 2454 if ( isReadWrite() && isModified()) { 2455 Q_FOREACH (KisView *view, KisPart::instance()->views()) { 2456 if (view && view->document() == this) { 2457 if (!view->queryClose()) { 2458 return false; 2459 } 2460 } 2461 } 2462 } 2463 } 2464 // Not modified => ok and delete temp file. 2465 d->mimeType = QByteArray(); 2466 2467 // It always succeeds for a read-only part, 2468 // but the return value exists for reimplementations 2469 // (e.g. pressing cancel for a modified read-write part) 2470 return true; 2471 } 2472 2473 2474 2475 void KisDocument::setPath(const QString &path) 2476 { 2477 const bool changed = path != d->m_path; 2478 2479 d->m_path = path; 2480 2481 if (changed) { 2482 Q_EMIT sigPathChanged(path); 2483 } 2484 } 2485 2486 QString KisDocument::localFilePath() const 2487 { 2488 return d->m_file; 2489 } 2490 2491 2492 void KisDocument::setLocalFilePath( const QString &localFilePath ) 2493 { 2494 d->m_file = localFilePath; 2495 } 2496 2497 bool KisDocument::openPathInternal(const QString &path) 2498 { 2499 if ( path.isEmpty() ) { 2500 return false; 2501 } 2502 2503 if (d->m_bAutoDetectedMime) { 2504 d->mimeType = QByteArray(); 2505 d->m_bAutoDetectedMime = false; 2506 } 2507 2508 QByteArray mimeType = d->mimeType; 2509 2510 if ( !closePath() ) { 2511 return false; 2512 } 2513 2514 d->mimeType = mimeType; 2515 setPath(path); 2516 2517 d->m_file.clear(); 2518 2519 d->m_file = d->m_path; 2520 2521 bool ret = false; 2522 // set the mimeType only if it was not already set (for example, by the host application) 2523 if (d->mimeType.isEmpty()) { 2524 // get the mimeType of the file 2525 // using findByUrl() to avoid another string -> url conversion 2526 QString mime = KisMimeDatabase::mimeTypeForFile(d->m_path); 2527 d->mimeType = mime.toLocal8Bit(); 2528 d->m_bAutoDetectedMime = true; 2529 } 2530 2531 setPath(d->m_path); 2532 ret = openFile(); 2533 2534 if (ret) { 2535 emit completed(); 2536 } 2537 else { 2538 emit canceled(QString()); 2539 } 2540 return ret; 2541 } 2542 2543 bool KisDocument::newImage(const QString& name, 2544 qint32 width, qint32 height, 2545 const KoColorSpace* cs, 2546 const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, 2547 int numberOfLayers, 2548 const QString &description, const double imageResolution) 2549 { 2550 Q_ASSERT(cs); 2551 2552 KisImageSP image; 2553 2554 if (!cs) return false; 2555 2556 KisCursorOverrideLock cursorLock(Qt::BusyCursor); 2557 2558 image = new KisImage(createUndoStore(), width, height, cs, name); 2559 2560 Q_CHECK_PTR(image); 2561 2562 connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); 2563 connect(image, SIGNAL(sigImageModifiedWithoutUndo()), this, SLOT(setImageModifiedWithoutUndo()), Qt::UniqueConnection); 2564 image->setResolution(imageResolution, imageResolution); 2565 2566 image->assignImageProfile(cs->profile()); 2567 image->waitForDone(); 2568 2569 documentInfo()->setAboutInfo("title", name); 2570 documentInfo()->setAboutInfo("abstract", description); 2571 2572 KisConfig cfg(false); 2573 cfg.defImageWidth(width); 2574 cfg.defImageHeight(height); 2575 cfg.defImageResolution(imageResolution); 2576 if (!cfg.useDefaultColorSpace()) 2577 { 2578 cfg.defColorModel(image->colorSpace()->colorModelId().id()); 2579 cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); 2580 cfg.defColorProfile(image->colorSpace()->profile()->name()); 2581 } 2582 2583 bool autopin = cfg.autoPinLayersToTimeline(); 2584 2585 KisLayerSP bgLayer; 2586 if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) { 2587 KoColor strippedAlpha = bgColor; 2588 strippedAlpha.setOpacity(OPACITY_OPAQUE_U8); 2589 2590 if (bgStyle == KisConfig::RASTER_LAYER) { 2591 bgLayer = new KisPaintLayer(image.data(), i18nc("Name for the bottom-most layer in the layerstack", "Background"), OPACITY_OPAQUE_U8, cs); 2592 bgLayer->paintDevice()->setDefaultPixel(strippedAlpha); 2593 bgLayer->setPinnedToTimeline(autopin); 2594 } else if (bgStyle == KisConfig::FILL_LAYER) { 2595 KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration(KisGlobalResourcesInterface::instance()); 2596 filter_config->setProperty("color", strippedAlpha.toQColor()); 2597 filter_config->createLocalResourcesSnapshot(); 2598 bgLayer = new KisGeneratorLayer(image.data(), i18nc("Name of automatically created background color fill layer", "Background Fill"), filter_config, image->globalSelection()); 2599 } 2600 2601 bgLayer->setOpacity(bgColor.opacityU8()); 2602 2603 if (numberOfLayers > 1) { 2604 //Lock bg layer if others are present. 2605 bgLayer->setUserLocked(true); 2606 } 2607 } 2608 else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer). 2609 image->setDefaultProjectionColor(bgColor); 2610 bgLayer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); 2611 } 2612 2613 Q_CHECK_PTR(bgLayer); 2614 image->addNode(bgLayer.data(), image->rootLayer().data()); 2615 bgLayer->setDirty(QRect(0, 0, width, height)); 2616 2617 // reset mirror axis to default: 2618 d->mirrorAxisConfig.setAxisPosition(QRectF(image->bounds()).center()); 2619 setCurrentImage(image); 2620 2621 for(int i = 1; i < numberOfLayers; ++i) { 2622 KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); 2623 layer->setPinnedToTimeline(autopin); 2624 image->addNode(layer, image->root(), i); 2625 layer->setDirty(QRect(0, 0, width, height)); 2626 } 2627 2628 KisUsageLogger::log( 2629 QString("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8") 2630 .arg(name, QString::number(width), QString::number(height), 2631 QString::number(imageResolution * 72.0), image->colorSpace()->colorModelId().name(), 2632 image->colorSpace()->colorDepthId().name(), image->colorSpace()->profile()->name(), 2633 QString::number(numberOfLayers))); 2634 2635 return true; 2636 } 2637 2638 bool KisDocument::isSaving() const 2639 { 2640 const bool result = d->savingMutex.tryLock(); 2641 if (result) { 2642 d->savingMutex.unlock(); 2643 } 2644 return !result; 2645 } 2646 2647 void KisDocument::waitForSavingToComplete() 2648 { 2649 if (isSaving()) { 2650 KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); 2651 f.waitForMutex(d->savingMutex); 2652 } 2653 } 2654 2655 KoShapeControllerBase *KisDocument::shapeController() const 2656 { 2657 return d->shapeController; 2658 } 2659 2660 KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const 2661 { 2662 return d->shapeController->shapeForNode(layer); 2663 } 2664 2665 QList<KisPaintingAssistantSP> KisDocument::assistants() const 2666 { 2667 return d->assistants; 2668 } 2669 2670 void KisDocument::setAssistants(const QList<KisPaintingAssistantSP> &value) 2671 { 2672 if (d->assistants != value) { 2673 d->assistants = value; 2674 d->syncDecorationsWrapperLayerState(); 2675 emit sigAssistantsChanged(); 2676 } 2677 } 2678 2679 KisReferenceImagesLayerSP KisDocument::referenceImagesLayer() const 2680 { 2681 if (!d->image) return KisReferenceImagesLayerSP(); 2682 2683 KisReferenceImagesLayerSP referencesLayer = 2684 KisLayerUtils::findNodeByType<KisReferenceImagesLayer>(d->image->root()); 2685 2686 return referencesLayer; 2687 } 2688 2689 void KisDocument::setReferenceImagesLayer(KisSharedPtr<KisReferenceImagesLayer> layer, bool updateImage) 2690 { 2691 KisReferenceImagesLayerSP currentReferenceLayer = referenceImagesLayer(); 2692 2693 // updateImage=false inherently means we are not changing the 2694 // reference images layer, but just would like to update its signals. 2695 if (currentReferenceLayer == layer && updateImage) { 2696 return; 2697 } 2698 2699 d->referenceLayerConnections.clear(); 2700 2701 if (updateImage) { 2702 if (currentReferenceLayer) { 2703 d->image->removeNode(currentReferenceLayer); 2704 } 2705 2706 if (layer) { 2707 d->image->addNode(layer); 2708 } 2709 } 2710 2711 currentReferenceLayer = layer; 2712 2713 if (currentReferenceLayer) { 2714 d->referenceLayerConnections.addConnection( 2715 currentReferenceLayer, SIGNAL(sigUpdateCanvas(QRectF)), 2716 this, SIGNAL(sigReferenceImagesChanged())); 2717 } 2718 2719 emit sigReferenceImagesLayerChanged(layer); 2720 } 2721 2722 void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) 2723 { 2724 d->preActivatedNode = activatedNode; 2725 } 2726 2727 KisNodeSP KisDocument::preActivatedNode() const 2728 { 2729 return d->preActivatedNode; 2730 } 2731 2732 KisImageWSP KisDocument::image() const 2733 { 2734 return d->image; 2735 } 2736 2737 KisImageSP KisDocument::savingImage() const 2738 { 2739 return d->savingImage; 2740 } 2741 2742 2743 void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) 2744 { 2745 if (d->image) { 2746 // Disconnect existing sig/slot connections 2747 d->image->setUndoStore(new KisDumbUndoStore()); 2748 d->image->disconnect(this); 2749 d->shapeController->setImage(0); 2750 d->image = 0; 2751 } 2752 2753 if (!image) return; 2754 2755 if (d->linkedResourceStorage){ 2756 d->linkedResourceStorage->setMetaData(KisResourceStorage::s_meta_name, image->objectName()); 2757 } 2758 2759 d->setImageAndInitIdleWatcher(image); 2760 d->image->setUndoStore(new KisDocumentUndoStore(this)); 2761 d->shapeController->setImage(image); 2762 d->image->setMirrorAxesCenter(KisAlgebra2D::absoluteToRelative(d->mirrorAxisConfig.axisPosition(), image->bounds())); 2763 setModified(false); 2764 connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); 2765 connect(d->image, SIGNAL(sigImageModifiedWithoutUndo()), this, SLOT(setImageModifiedWithoutUndo()), Qt::UniqueConnection); 2766 connect(d->image, SIGNAL(sigLayersChangedAsync()), this, SLOT(slotImageRootChanged())); 2767 2768 if (forceInitialUpdate) { 2769 d->image->initialRefreshGraph(); 2770 } 2771 } 2772 2773 void KisDocument::hackPreliminarySetImage(KisImageSP image) 2774 { 2775 KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); 2776 2777 // we set image without connecting idle-watcher, because loading 2778 // hasn't been finished yet 2779 d->image = image; 2780 d->shapeController->setImage(image); 2781 } 2782 2783 void KisDocument::setImageModified() 2784 { 2785 // we only set as modified if undo stack is not at clean state 2786 setModified(d->imageModifiedWithoutUndo || !d->undoStack->isClean()); 2787 } 2788 2789 void KisDocument::setImageModifiedWithoutUndo() 2790 { 2791 d->imageModifiedWithoutUndo = true; 2792 setImageModified(); 2793 } 2794 2795 2796 KisUndoStore* KisDocument::createUndoStore() 2797 { 2798 return new KisDocumentUndoStore(this); 2799 } 2800 2801 bool KisDocument::isAutosaving() const 2802 { 2803 return d->isAutosaving; 2804 } 2805 2806 QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage) 2807 { 2808 return errorMessage.isEmpty() ? status.errorMessage() : errorMessage; 2809 } 2810 2811 void KisDocument::setAssistantsGlobalColor(QColor color) 2812 { 2813 d->globalAssistantsColor = color; 2814 } 2815 2816 QColor KisDocument::assistantsGlobalColor() 2817 { 2818 return d->globalAssistantsColor; 2819 } 2820 2821 QRectF KisDocument::documentBounds() const 2822 { 2823 QRectF bounds = d->image->bounds(); 2824 2825 KisReferenceImagesLayerSP referenceImagesLayer = this->referenceImagesLayer(); 2826 2827 if (referenceImagesLayer) { 2828 bounds |= referenceImagesLayer->boundingImageRect(); 2829 } 2830 2831 return bounds; 2832 }