File indexing completed on 2024-05-19 04:29:13

0001 /*
0002  * SPDX-FileCopyrightText: 2016 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "KisImportExportManager.h"
0008 
0009 #include <QDir>
0010 #include <QFile>
0011 #include <QLabel>
0012 #include <QVBoxLayout>
0013 #include <QList>
0014 #include <QApplication>
0015 #include <QByteArray>
0016 #include <QPluginLoader>
0017 #include <QFileInfo>
0018 #include <QMessageBox>
0019 #include <QJsonObject>
0020 #include <QTextBrowser>
0021 #include <QCheckBox>
0022 #include <QSaveFile>
0023 #include <QGroupBox>
0024 #include <QFuture>
0025 #include <QtConcurrent>
0026 
0027 #include <klocalizedstring.h>
0028 #include <ksqueezedtextlabel.h>
0029 #include <kpluginfactory.h>
0030 
0031 #include <KisMimeDatabase.h>
0032 #include <KisPart.h>
0033 #include <KisPopupButton.h>
0034 #include <KisPreExportChecker.h>
0035 #include <KisUsageLogger.h>
0036 #include <KoColorProfile.h>
0037 #include <KoColorProfileConstants.h>
0038 #include <KoDialog.h>
0039 #include <KoFileDialog.h>
0040 #include <KoJsonTrader.h>
0041 #include <KoProgressUpdater.h>
0042 #include <kis_assert.h>
0043 #include <kis_config_widget.h>
0044 #include <kis_debug.h>
0045 #include <kis_icon_utils.h>
0046 #include <kis_image.h>
0047 #include <kis_iterator_ng.h>
0048 #include <kis_layer_utils.h>
0049 #include <kis_paint_layer.h>
0050 #include <kis_painter.h>
0051 
0052 #include "KisDocument.h"
0053 #include "KisImportExportErrorCode.h"
0054 #include "KisImportExportFilter.h"
0055 #include "KisMainWindow.h"
0056 #include "KisReferenceImagesLayer.h"
0057 #include "imagesize/dlg_imagesize.h"
0058 #include "kis_async_action_feedback.h"
0059 #include "kis_config.h"
0060 #include "kis_grid_config.h"
0061 #include "kis_guides_config.h"
0062 
0063 #include <KisImportUserFeedbackInterface.h>
0064 
0065 // static cache for import and export mimetypes
0066 QStringList KisImportExportManager::m_importMimeTypes;
0067 QStringList KisImportExportManager::m_exportMimeTypes;
0068 
0069 namespace {
0070 struct SynchronousUserFeedbackInterface : KisImportUserFeedbackInterface
0071 {
0072     SynchronousUserFeedbackInterface(QWidget *parent, bool batchMode)
0073         : m_parent(parent)
0074         , m_batchMode(batchMode)
0075     {
0076     }
0077 
0078     Result askUser(AskCallback callback) override
0079     {
0080         if (m_batchMode) return SuppressedByBatchMode;
0081         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_parent, SuppressedByBatchMode);
0082 
0083         return callback(m_parent) ? Success : UserCancelled;
0084     }
0085 
0086     QWidget *m_parent {nullptr};
0087     bool m_batchMode {false};
0088 };
0089 
0090 }
0091 
0092 
0093 class Q_DECL_HIDDEN KisImportExportManager::Private
0094 {
0095 public:
0096     KoUpdaterPtr updater;
0097 
0098     QString cachedExportFilterMimeType;
0099     QSharedPointer<KisImportExportFilter> cachedExportFilter;
0100 };
0101 
0102 struct KisImportExportManager::ConversionResult {
0103     ConversionResult()
0104     {
0105     }
0106 
0107     ConversionResult(const QFuture<KisImportExportErrorCode> &futureStatus)
0108         : m_isAsync(true),
0109           m_futureStatus(futureStatus)
0110     {
0111     }
0112 
0113     ConversionResult(KisImportExportErrorCode status)
0114         : m_isAsync(false),
0115           m_status(status)
0116     {
0117     }
0118 
0119     bool isAsync() const {
0120         return m_isAsync;
0121     }
0122 
0123     QFuture<KisImportExportErrorCode> futureStatus() const {
0124         // if the result is not async, then it means some failure happened,
0125         // just return a cancelled future
0126         KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || !m_status.isOk());
0127 
0128         return m_futureStatus;
0129     }
0130 
0131     KisImportExportErrorCode status() const {
0132         return m_status;
0133     }
0134 
0135     void setStatus(KisImportExportErrorCode value) {
0136         m_status = value;
0137     }
0138 private:
0139     bool m_isAsync = false;
0140     QFuture<KisImportExportErrorCode> m_futureStatus;
0141     KisImportExportErrorCode m_status = ImportExportCodes::InternalError;
0142 };
0143 
0144 
0145 KisImportExportManager::KisImportExportManager(KisDocument* document)
0146     : m_document(document)
0147     , d(new Private)
0148 {
0149 }
0150 
0151 KisImportExportManager::~KisImportExportManager()
0152 {
0153     delete d;
0154 }
0155 
0156 KisImportExportErrorCode KisImportExportManager::importDocument(const QString& location, const QString& mimeType)
0157 {
0158     ConversionResult result = convert(Import, location, location, mimeType, false, 0, false);
0159     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError);
0160 
0161     return result.status();
0162 }
0163 
0164 KisImportExportErrorCode KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting)
0165 {
0166     ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false, isAdvancedExporting);
0167     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError);
0168 
0169     return result.status();
0170 }
0171 
0172 QFuture<KisImportExportErrorCode> KisImportExportManager::exportDocumentAsync(const QString &location, const QString &realLocation, const QByteArray &mimeType,
0173                                                                             KisImportExportErrorCode &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting)
0174 {
0175     ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true, isAdvancedExporting);
0176     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() ||
0177                                          !result.status().isOk(), QFuture<KisImportExportErrorCode>());
0178 
0179     status = result.status();
0180     return result.futureStatus();
0181 }
0182 
0183 // The static method to figure out to which parts of the
0184 // graph this mimetype has a connection to.
0185 QStringList KisImportExportManager::supportedMimeTypes(Direction direction)
0186 {
0187     // Find the right mimetype by the extension
0188     QSet<QString> mimeTypes;
0189     //    mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster";
0190 
0191     if (direction == KisImportExportManager::Import) {
0192         if (m_importMimeTypes.isEmpty()) {
0193             QList<KoJsonTrader::Plugin> list = KoJsonTrader::instance()->query("Krita/FileFilter", "");
0194             Q_FOREACH(const KoJsonTrader::Plugin &loader, list) {
0195                 QJsonObject json = loader.metaData().value("MetaData").toObject();
0196 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0197                 Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", Qt::SkipEmptyParts)) {
0198 #else
0199                 Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) {
0200 #endif
0201 
0202                     //qDebug() << "Adding  import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
0203                     mimeTypes << mimetype;
0204                 }
0205             }
0206 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
0207             m_importMimeTypes = QList<QString>(mimeTypes.begin(), mimeTypes.end());
0208 #else
0209             m_importMimeTypes = QList<QString>::fromSet(mimeTypes);
0210 #endif
0211         }
0212         return m_importMimeTypes;
0213     }
0214     else if (direction == KisImportExportManager::Export) {
0215         if (m_exportMimeTypes.isEmpty()) {
0216             QList<KoJsonTrader::Plugin> list = KoJsonTrader::instance()->query("Krita/FileFilter", "");
0217             Q_FOREACH(const KoJsonTrader::Plugin &loader, list) {
0218                 QJsonObject json = loader.metaData().value("MetaData").toObject();
0219 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0220                 Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", Qt::SkipEmptyParts)) {
0221 #else
0222                 Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) {
0223 #endif
0224 
0225                     //qDebug() << "Adding  export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
0226                     mimeTypes << mimetype;
0227                 }
0228             }
0229 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
0230             m_exportMimeTypes = QList<QString>(mimeTypes.begin(), mimeTypes.end());
0231 #else
0232            m_exportMimeTypes = QList<QString>::fromSet(mimeTypes);
0233 #endif
0234 
0235         }
0236         return m_exportMimeTypes;
0237     }
0238     return QStringList();
0239 }
0240 
0241 KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction)
0242 {
0243     int weight = -1;
0244     KisImportExportFilter *filter = 0;
0245     QList<KoJsonTrader::Plugin>list = KoJsonTrader::instance()->query("Krita/FileFilter", "");
0246 
0247     Q_FOREACH(const KoJsonTrader::Plugin &loader, list) {
0248         QJsonObject json = loader.metaData().value("MetaData").toObject();
0249         QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import";
0250 
0251 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0252         if (json.value(directionKey).toString().split(",", Qt::SkipEmptyParts).contains(mimetype)) {
0253 #else
0254         if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) {
0255 #endif
0256 
0257             KLibFactory *factory = qobject_cast<KLibFactory *>(loader.instance());
0258 
0259             if (!factory) {
0260                 warnUI << loader.errorString();
0261                 continue;
0262             }
0263 
0264             QObject* obj = factory->create<KisImportExportFilter>(0);
0265             if (!obj || !obj->inherits("KisImportExportFilter")) {
0266                 delete obj;
0267                 continue;
0268             }
0269 
0270             KisImportExportFilter *f = qobject_cast<KisImportExportFilter*>(obj);
0271             if (!f) {
0272                 delete obj;
0273                 continue;
0274             }
0275 
0276             KIS_ASSERT_RECOVER_NOOP(json.value("X-KDE-Weight").isDouble());
0277 
0278             int w = json.value("X-KDE-Weight").toInt();
0279 
0280             if (w > weight) {
0281                 delete filter;
0282                 filter = f;
0283                 f->setObjectName(loader.fileName());
0284                 weight = w;
0285             }
0286         }
0287     }
0288 
0289     if (filter) {
0290         filter->setMimeType(mimetype);
0291     }
0292     return filter;
0293 }
0294 
0295 bool KisImportExportManager::batchMode(void) const
0296 {
0297     return m_document->fileBatchMode();
0298 }
0299 
0300 void KisImportExportManager::setUpdater(KoUpdaterPtr updater)
0301 {
0302     d->updater = updater;
0303 }
0304 
0305 QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent)
0306 {
0307     KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio");
0308 
0309     if (!defaultDir.isEmpty()) {
0310         dialog.setDefaultDir(defaultDir);
0311     }
0312 
0313     QStringList mimeTypes;
0314     mimeTypes << "audio/mpeg";
0315     mimeTypes << "audio/ogg";
0316     mimeTypes << "audio/vorbis";
0317     mimeTypes << "audio/vnd.wave";
0318     mimeTypes << "audio/flac";
0319 
0320     dialog.setMimeTypeFilters(mimeTypes);
0321     dialog.setCaption(i18nc("@title:window", "Open Audio"));
0322 
0323     return dialog.filename();
0324 }
0325 
0326 QString KisImportExportManager::getUriForAdditionalFile(const QString &defaultUri, QWidget *parent)
0327 {
0328     KoFileDialog dialog(parent, KoFileDialog::SaveFile, "Save Kra");
0329 
0330     KIS_SAFE_ASSERT_RECOVER_NOOP(!defaultUri.isEmpty());
0331 
0332     dialog.setDirectoryUrl(QUrl(defaultUri));
0333     dialog.setMimeTypeFilters(QStringList("application/x-krita"));
0334 
0335     return dialog.filename();
0336 }
0337 
0338 KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync, bool isAdvancedExporting)
0339 {
0340     // export configuration is supported for export only
0341     KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration));
0342 
0343     QString typeName = mimeType;
0344     if (typeName.isEmpty()) {
0345         typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true);
0346     }
0347 
0348     QSharedPointer<KisImportExportFilter> filter;
0349 
0350     /**
0351      * Fetching a filter from the registry is a really expensive operation,
0352      * because it blocks all the threads. Cache the filter if possible.
0353      */
0354     if (direction == KisImportExportManager::Export &&
0355             d->cachedExportFilter &&
0356             d->cachedExportFilterMimeType == typeName) {
0357 
0358         filter = d->cachedExportFilter;
0359     } else {
0360 
0361         filter = toQShared(filterForMimeType(typeName, direction));
0362 
0363         if (direction == Export) {
0364             d->cachedExportFilter = filter;
0365             d->cachedExportFilterMimeType = typeName;
0366         }
0367     }
0368 
0369     if (!filter) {
0370         return KisImportExportErrorCode(ImportExportCodes::FileFormatNotSupported);
0371     }
0372 
0373     filter->setFilename(location);
0374     filter->setRealFilename(realLocation);
0375     filter->setBatchMode(batchMode());
0376     filter->setMimeType(typeName);
0377 
0378     if (direction == Import) {
0379         KisMainWindow *kisMain = KisPart::instance()->currentMainwindow();
0380         filter->setImportUserFeedBackInterface(new SynchronousUserFeedbackInterface(kisMain, batchMode()));
0381     }
0382 
0383     if (!d->updater.isNull()) {
0384         // WARNING: The updater is not guaranteed to be persistent! If you ever want
0385         // to add progress reporting to "Save also as .kra", make sure you create
0386         // a separate KoProgressUpdater for that!
0387 
0388         // WARNING2: the failsafe completion of the updater happens in the destructor
0389         // the filter.
0390 
0391         filter->setUpdater(d->updater);
0392     }
0393 
0394     QByteArray from, to;
0395     if (direction == Export) {
0396         from = m_document->nativeFormatMimeType();
0397         to = typeName.toLatin1();
0398     }
0399     else {
0400         from = typeName.toLatin1();
0401         to = m_document->nativeFormatMimeType();
0402     }
0403 
0404     KIS_ASSERT_RECOVER_RETURN_VALUE(
0405                 direction == Import || direction == Export,
0406                 KisImportExportErrorCode(ImportExportCodes::InternalError)); // "bad conversion graph"
0407 
0408     ConversionResult result = KisImportExportErrorCode(ImportExportCodes::OK);
0409     if (direction == Import) {
0410 
0411         KisUsageLogger::log(QString("Importing %1 to %2. Location: %3. Real location: %4. Batchmode: %5")
0412                                 .arg(QString::fromLatin1(from), QString::fromLatin1(to), location,
0413                                      realLocation, QString::number(batchMode())));
0414 
0415         // async importing is not yet supported!
0416         KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync);
0417 
0418         // FIXME: Dmitry says "this progress reporting code never worked. Initial idea was to implement it his way, but I stopped and didn't finish it"
0419         if (0 && !batchMode()) {
0420             KisAsyncActionFeedback f(i18n("Opening document..."), 0);
0421             result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter));
0422         } else {
0423             result = doImport(location, filter);
0424         }
0425         if (result.status().isOk()) {
0426             KisImageSP image = m_document->image().toStrongRef();
0427             if (image) {
0428                 KIS_SAFE_ASSERT_RECOVER(image->colorSpace() != nullptr && image->colorSpace()->profile() != nullptr)
0429                 {
0430                     qWarning() << "Loaded a profile-less file without a fallback. Rejecting image "
0431                                   "opening";
0432                     return KisImportExportErrorCode(ImportExportCodes::InternalError);
0433                 }
0434                 KisUsageLogger::log(QString("Loaded image from %1. Size: %2 * %3 pixels, %4 dpi. Color "
0435                                             "model: %6 %5 (%7). Layers: %8")
0436                                         .arg(QString::fromLatin1(from), QString::number(image->width()),
0437                                              QString::number(image->height()), QString::number(image->xRes()),
0438                                              image->colorSpace()->colorModelId().name(),
0439                                              image->colorSpace()->colorDepthId().name(),
0440                                              image->colorSpace()->profile()->name(),
0441                                              QString::number(image->nlayers())));
0442             } else {
0443                 qWarning() << "The filter returned OK, but there is no image";
0444             }
0445 
0446         }
0447         else {
0448             KisUsageLogger::log(QString("Failed to load image from %1").arg(QString::fromLatin1(from)));
0449         }
0450 
0451     }
0452     else /* if (direction == Export) */ {
0453         if (!exportConfiguration) {
0454             exportConfiguration = filter->lastSavedConfiguration(from, to);
0455         }
0456 
0457         if (exportConfiguration) {
0458             fillStaticExportConfigurationProperties(exportConfiguration);
0459         }
0460 
0461         bool alsoAsKra = false;
0462         bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration,
0463                                                        from, to,
0464                                                        batchMode(), showWarnings,
0465                                                        &alsoAsKra, isAdvancedExporting);
0466 
0467 
0468         if (!batchMode() && !askUser) {
0469             return KisImportExportErrorCode(ImportExportCodes::Cancelled);
0470         }
0471 
0472         KisUsageLogger::log(
0473             QString(
0474                 "Converting from %1 to %2. Location: %3. Real location: %4. Batchmode: %5. Configuration: %6")
0475                 .arg(QString::fromLatin1(from), QString::fromLatin1(to), location, realLocation,
0476                      QString::number(batchMode()),
0477                      (exportConfiguration ? exportConfiguration->toXML() : "none")));
0478 
0479         const QString alsoAsKraLocation = alsoAsKra ? getAlsoAsKraLocation(location) : QString();
0480         if (isAsync) {
0481             result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter,
0482                                                  exportConfiguration, alsoAsKraLocation));
0483 
0484             // we should explicitly report that the exporting has been initiated
0485             result.setStatus(ImportExportCodes::OK);
0486 
0487         } else if (!batchMode()) {
0488             KisAsyncActionFeedback f(i18n("Saving document..."), 0);
0489             result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter,
0490                                            exportConfiguration, alsoAsKraLocation));
0491         } else {
0492             result = doExport(location, filter, exportConfiguration, alsoAsKraLocation);
0493         }
0494 
0495         if (exportConfiguration && !batchMode()) {
0496             KisConfig(false).setExportConfiguration(typeName, exportConfiguration);
0497         }
0498     }
0499     return result;
0500 }
0501 
0502 void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image)
0503 {
0504     KisPaintDeviceSP dev = image->projection();
0505     const KoColorSpace* cs = dev->colorSpace();
0506     const bool isThereAlpha =
0507             KisPainter::checkDeviceHasTransparency(image->projection());
0508 
0509     exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha);
0510     exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id());
0511     exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id());
0512 
0513     const bool sRGB =
0514             (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) &&
0515              !cs->profile()->name().contains(QLatin1String("g10")));
0516     exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB);
0517 
0518     ColorPrimaries primaries = cs->profile()->getColorPrimaries();
0519     if (primaries >= PRIMARIES_ADOBE_RGB_1998) {
0520         primaries = PRIMARIES_UNSPECIFIED;
0521     }
0522     TransferCharacteristics transferFunction =
0523         cs->profile()->getTransferCharacteristics();
0524     if (transferFunction >= TRC_GAMMA_1_8) {
0525         transferFunction = TRC_UNSPECIFIED;
0526     }
0527     exportConfiguration->setProperty(KisImportExportFilter::CICPPrimariesTag,
0528                                      static_cast<int>(primaries));
0529     exportConfiguration->setProperty(
0530         KisImportExportFilter::CICPTransferCharacteristicsTag,
0531         static_cast<int>(transferFunction));
0532     exportConfiguration->setProperty(KisImportExportFilter::HDRTag, cs->hasHighDynamicRange());
0533 }
0534 
0535 
0536 void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration)
0537 {
0538     return fillStaticExportConfigurationProperties(exportConfiguration, m_document->image());
0539 }
0540 
0541 bool KisImportExportManager::askUserAboutExportConfiguration(
0542         QSharedPointer<KisImportExportFilter> filter,
0543         KisPropertiesConfigurationSP exportConfiguration,
0544         const QByteArray &from,
0545         const QByteArray &to,
0546         const bool batchMode,
0547         const bool showWarnings,
0548         bool *alsoAsKra,
0549         bool isAdvancedExporting)
0550 {
0551 
0552     // prevents the animation renderer from running this code
0553 
0554 
0555     const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to);
0556 
0557     QStringList warnings;
0558     QStringList errors;
0559 
0560     {
0561         KisPreExportChecker checker;
0562         checker.check(m_document->image(), filter->exportChecks());
0563 
0564         warnings = checker.warnings();
0565         errors = checker.errors();
0566     }
0567 
0568     KisConfigWidget *wdg = 0;
0569 
0570     if (QThread::currentThread() == qApp->thread()) {
0571         wdg = filter->createConfigurationWidget(0, from, to);
0572 
0573         KisMainWindow *kisMain = KisPart::instance()->currentMainwindow();
0574         if (wdg && kisMain) {
0575             KisViewManager *manager = kisMain->viewManager();
0576             wdg->setView(manager);
0577         }
0578     }
0579 
0580     // Extra checks that cannot be done by the checker, because the checker only has access to the image.
0581     if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) {
0582         warnings.append(i18nc("image conversion warning", "The image contains <b>assistants</b>. The assistants will not be saved."));
0583     }
0584     if (m_document->referenceImagesLayer() && m_document->referenceImagesLayer()->shapeCount() > 0 && to != m_document->nativeFormatMimeType()) {
0585         warnings.append(i18nc("image conversion warning", "The image contains <b>reference images</b>. The reference images will not be saved."));
0586     }
0587     if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) {
0588         warnings.append(i18nc("image conversion warning", "The image contains <b>guides</b>. The guides will not be saved."));
0589     }
0590     if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) {
0591         warnings.append(i18nc("image conversion warning", "The image contains a <b>custom grid configuration</b>. The configuration will not be saved."));
0592     }
0593 
0594     if (!batchMode && !errors.isEmpty()) {
0595         QString error =  "<html><body><p><b>"
0596                 + i18n("Error: cannot save this image as a %1.", mimeUserDescription)
0597                 + "</b> " + i18n("Reasons:") + "</p>"
0598                 + "<p/><ul>";
0599         Q_FOREACH(const QString &w, errors) {
0600             error += "\n<li>" + w + "</li>";
0601         }
0602 
0603         error += "</ul>";
0604 
0605         QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error);
0606         return false;
0607     }
0608 
0609     if (!batchMode && (wdg || !warnings.isEmpty() || isAdvancedExporting)) {
0610         QWidget *page = new QWidget();
0611         QVBoxLayout *layout = new QVBoxLayout(page);
0612 
0613         if (showWarnings && !warnings.isEmpty()) {
0614             QHBoxLayout *hLayout = new QHBoxLayout();
0615 
0616             QLabel *labelWarning = new QLabel();
0617             labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(48, 48));
0618             hLayout->addWidget(labelWarning);
0619 
0620             KisPopupButton *bn = new KisPopupButton(0);
0621 
0622             bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as a %1 will lose information from your image.    ", mimeUserDescription));
0623             bn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0624 
0625             hLayout->addWidget(bn);
0626 
0627             layout->addLayout(hLayout);
0628 
0629             QTextBrowser *browser = new QTextBrowser();
0630             browser->setMinimumWidth(bn->width());
0631             bn->setPopupWidget(browser);
0632 
0633             QString warning = "<html><body><p><b>"
0634                     + i18n("You will lose information when saving this image as a %1.", mimeUserDescription);
0635 
0636             if (warnings.size() == 1) {
0637                 warning += "</b> " + i18n("Reason:") + "</p>";
0638             }
0639             else {
0640                 warning += "</b> " + i18n("Reasons:") + "</p>";
0641             }
0642             warning += "<p/><ul>";
0643 
0644             Q_FOREACH(const QString &w, warnings) {
0645                 warning += "\n<li>" + w + "</li>";
0646             }
0647 
0648             warning += "</ul>";
0649             browser->setHtml(warning);
0650         }
0651 
0652         QTabWidget *box = new QTabWidget;
0653         if (wdg) {
0654             wdg->setConfiguration(exportConfiguration);
0655             box->addTab(wdg,i18n("Options"));
0656         }
0657 
0658         auto dlgImageSize = new DlgImageSize(box, m_document->image()->width(), m_document->image()->height(), m_document->image()->yRes());
0659         dlgImageSize->setButtons(KoDialog::None);
0660 
0661         if(isAdvancedExporting)
0662         {
0663             box->addTab(dlgImageSize,i18n("Resize"));
0664         }
0665         layout->addWidget(box);
0666 
0667         QCheckBox *chkAlsoAsKra = 0;
0668         if (showWarnings && !warnings.isEmpty()) {
0669             chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file."));
0670             chkAlsoAsKra->setChecked(KisConfig(true).readEntry<bool>("AlsoSaveAsKra", false));
0671             layout->addWidget(chkAlsoAsKra);
0672         }
0673 
0674         KoDialog dlg(qApp->activeWindow());
0675         dlg.setMainWidget(page);
0676         page->setParent(&dlg);
0677         dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
0678         dlg.setWindowTitle(mimeUserDescription);
0679 
0680         if (showWarnings || wdg || isAdvancedExporting) {
0681             if (!dlg.exec()) {
0682                 return false;
0683             }
0684         }
0685 
0686         *alsoAsKra = false;
0687         if (chkAlsoAsKra) {
0688             KisConfig(false).writeEntry<bool>("AlsoSaveAsKra", chkAlsoAsKra->isChecked());
0689             *alsoAsKra = chkAlsoAsKra->isChecked();
0690         }
0691 
0692         if(isAdvancedExporting) {
0693             const QSize desiredSize(dlgImageSize->desiredWidth(), dlgImageSize->desiredHeight());
0694             double res = dlgImageSize->desiredResolution();
0695             m_document->savingImage()->scaleImage(desiredSize,res,res,dlgImageSize->filterType());
0696             m_document->savingImage()->waitForDone();
0697             KisLayerUtils::forceAllDelayedNodesUpdate(m_document->savingImage()->root());
0698             m_document->savingImage()->waitForDone();
0699         }
0700 
0701         if (wdg) {
0702             *exportConfiguration = *wdg->configuration();
0703         }
0704     }
0705 
0706     return true;
0707 }
0708 
0709 KisImportExportErrorCode KisImportExportManager::doImport(const QString &location, QSharedPointer<KisImportExportFilter> filter)
0710 {
0711     QFile file(location);
0712     if (!file.exists()) {
0713         return ImportExportCodes::FileNotExist;
0714     }
0715 
0716     if (filter->supportsIO() && !file.open(QFile::ReadOnly)) {
0717         return KisImportExportErrorCode(KisImportExportErrorCannotRead(file.error()));
0718     }
0719 
0720     KisImportExportErrorCode status = filter->convert(m_document, &file, KisPropertiesConfigurationSP());
0721 
0722     if (file.isOpen()) {
0723         file.close();
0724     }
0725 
0726     return status;
0727 }
0728 
0729 KisImportExportErrorCode KisImportExportManager::doExport(const QString &location,
0730                                                           QSharedPointer<KisImportExportFilter> filter,
0731                                                           KisPropertiesConfigurationSP exportConfiguration,
0732                                                           const QString alsoAsKraLocation)
0733 {
0734     KisImportExportErrorCode status =
0735             doExportImpl(location, filter, exportConfiguration);
0736 
0737     if (!alsoAsKraLocation.isNull() && status.isOk()) {
0738         QByteArray mime = m_document->nativeFormatMimeType();
0739         QSharedPointer<KisImportExportFilter> filter(
0740                     filterForMimeType(QString::fromLatin1(mime), Export));
0741 
0742         KIS_SAFE_ASSERT_RECOVER_NOOP(filter);
0743 
0744         if (filter) {
0745             filter->setFilename(alsoAsKraLocation);
0746 
0747             KisPropertiesConfigurationSP kraExportConfiguration =
0748                     filter->lastSavedConfiguration(mime, mime);
0749 
0750             status = doExportImpl(alsoAsKraLocation, filter, kraExportConfiguration);
0751         } else {
0752             status = ImportExportCodes::FileFormatIncorrect;
0753         }
0754     }
0755 
0756     return status;
0757 }
0758 
0759 // Temporary workaround until QTBUG-57299 is fixed.
0760 // 02-10-2019 update: the bug is closed, but we've still seen this issue.
0761 //                    and without using QSaveFile the issue can still occur
0762 //                    when QFile::copy fails because Dropbox/Google/OneDrive
0763 //                    locks the target file.
0764 // 02-24-2022 update: Added macOS since QSaveFile does not work on sandboxed krita
0765 //                    It can work if user gives access to the container dir, but
0766 //                    we cannot guarantee the user gave us permission.
0767 #if !(defined(Q_OS_WIN) || defined(Q_OS_MACOS))
0768 #define USE_QSAVEFILE
0769 #endif
0770 
0771 KisImportExportErrorCode KisImportExportManager::doExportImpl(const QString &location, QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration)
0772 {
0773 #ifdef USE_QSAVEFILE
0774     QSaveFile file(location);
0775     file.setDirectWriteFallback(true);
0776     if (filter->supportsIO() && !file.open(QFile::WriteOnly)) {
0777 #else
0778     QFileInfo fi(location);
0779     QTemporaryFile file(QDir::tempPath() + "/.XXXXXX.kra");
0780     if (filter->supportsIO() && !file.open()) {
0781 #endif
0782         KisImportExportErrorCannotWrite result(file.error());
0783 #ifdef USE_QSAVEFILE
0784         file.cancelWriting();
0785 #endif
0786         return result;
0787     }
0788 
0789     KisImportExportErrorCode status = filter->convert(m_document, &file, exportConfiguration);
0790 
0791     if (filter->supportsIO()) {
0792         if (!status.isOk()) {
0793 #ifdef USE_QSAVEFILE
0794             file.cancelWriting();
0795 #endif
0796         } else {
0797 #ifdef USE_QSAVEFILE
0798             if (!file.commit()) {
0799                 qWarning() << "Could not commit QSaveFile";
0800                 status = KisImportExportErrorCannotWrite(file.error());
0801             }
0802 #else
0803             file.flush();
0804             file.close();
0805             QFile target(location);
0806             if (target.exists()) {
0807                 // There should already be a .kra~ backup
0808                 target.remove();
0809             }
0810             if (!file.copy(location)) {
0811                 file.setAutoRemove(false);
0812                 return KisImportExportErrorCannotWrite(file.error());
0813             }
0814 #endif
0815         }
0816     }
0817 
0818     if (status.isOk()) {
0819         // Do some minimal verification
0820         QString verificationResult = filter->verify(location);
0821         if (!verificationResult.isEmpty()) {
0822             status = KisImportExportErrorCode(ImportExportCodes::ErrorWhileWriting);
0823             m_document->setErrorMessage(verificationResult);
0824         }
0825     }
0826 
0827     return status;
0828 
0829 }
0830 
0831 QString KisImportExportManager::getAlsoAsKraLocation(const QString location) const
0832 {
0833 #ifdef Q_OS_ANDROID
0834     return getUriForAdditionalFile(location, nullptr);
0835 #else
0836     return location + ".kra";
0837 #endif
0838 }
0839 
0840 #include <KisMimeDatabase.h>