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