File indexing completed on 2024-05-12 16:01:28

0001 /*
0002  *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
0003  *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_clipboard.h"
0009 
0010 #include <QApplication>
0011 #include <QBuffer>
0012 #include <QClipboard>
0013 #include <QDesktopWidget>
0014 #include <QFileInfo>
0015 #include <QMessageBox>
0016 #include <QMimeData>
0017 #include <QScopedPointer>
0018 #include <QTemporaryFile>
0019 
0020 // kritaglobal
0021 #include <algorithm>
0022 #include <kis_assert.h>
0023 #include <kis_debug.h>
0024 
0025 // kritastore
0026 #include <KoStore.h>
0027 
0028 // kritaimage
0029 #include <kis_annotation.h>
0030 #include <kis_layer_utils.h>
0031 #include <kis_paint_device.h>
0032 #include <kis_time_span.h>
0033 
0034 // local
0035 #include "KisDocument.h"
0036 #include "KisImportExportManager.h"
0037 #include "KisMainWindow.h"
0038 #include "KisMimeDatabase.h"
0039 #include "KisPart.h"
0040 #include "KisRemoteFileFetcher.h"
0041 #include "dialogs/kis_dlg_missing_color_profile.h"
0042 #include "dialogs/kis_dlg_paste_format.h"
0043 #include "kis_mimedata.h"
0044 #include "kis_store_paintdevice_writer.h"
0045 
0046 Q_GLOBAL_STATIC(KisClipboard, s_instance)
0047 
0048 struct ClipboardImageFormat {
0049     QSet<QString> mimeTypes;
0050     QString format;
0051 };
0052 
0053 class Q_DECL_HIDDEN KisClipboardPrivate
0054 {
0055 public:
0056     KisClipboardPrivate()
0057         : clipboard(QApplication::clipboard())
0058     {
0059     }
0060 
0061     bool hasClip{};
0062     bool pushedClipboard{};
0063     QClipboard *clipboard;
0064 };
0065 
0066 KisClipboard::KisClipboard()
0067     : d(new KisClipboardPrivate)
0068 {
0069     // Check that we don't already have a clip ready
0070     clipboardDataChanged();
0071 
0072     // Make sure we are notified when clipboard changes
0073     connect(d->clipboard, &QClipboard::dataChanged, this, &KisClipboard::clipboardDataChanged, Qt::UniqueConnection);
0074 }
0075 
0076 KisClipboard::~KisClipboard()
0077 {
0078     dbgRegistry << "deleting KisClipBoard";
0079     delete d;
0080 }
0081 
0082 KisClipboard *KisClipboard::instance()
0083 {
0084     return s_instance;
0085 }
0086 
0087 void KisClipboard::setClip(KisPaintDeviceSP dev, const QPoint &topLeft, const KisTimeSpan &range)
0088 {
0089     if (!dev)
0090         return;
0091 
0092     d->hasClip = true;
0093 
0094     // We'll create a store (ZIP format) in memory
0095     QBuffer buffer;
0096     const auto mimeType = QByteArrayLiteral("application/x-krita-selection");
0097     QScopedPointer<KoStore> store(KoStore::createStore(&buffer, KoStore::Write, mimeType));
0098     KisStorePaintDeviceWriter writer(store.data());
0099     Q_ASSERT(store);
0100     Q_ASSERT(!store->bad());
0101 
0102     // Layer data
0103     if (store->open("layerdata")) {
0104         if (!dev->write(writer)) {
0105             dev->disconnect();
0106             store->close();
0107             return;
0108         }
0109         store->close();
0110     }
0111 
0112     // copied frame time limits
0113     if (range.isValid() && store->open("timeRange")) {
0114         store->write(QString("%1 %2").arg(range.start()).arg(range.end()).toLatin1());
0115         store->close();
0116     }
0117 
0118     // Coordinates
0119     if (store->open("topLeft")) {
0120         store->write(QString("%1 %2").arg(topLeft.x()).arg(topLeft.y()).toLatin1());
0121         store->close();
0122     }
0123     // ColorSpace id of layer data
0124     if (store->open("colormodel")) {
0125         QString csName = dev->colorSpace()->colorModelId().id();
0126         store->write(csName.toLatin1());
0127         store->close();
0128     }
0129     if (store->open("colordepth")) {
0130         QString csName = dev->colorSpace()->colorDepthId().id();
0131         store->write(csName.toLatin1());
0132         store->close();
0133     }
0134 
0135     if (dev->colorSpace()->profile()) {
0136         const KoColorProfile *profile = dev->colorSpace()->profile();
0137         KisAnnotationSP annotation;
0138 
0139         if (profile && profile->type() == "icc" && !profile->rawData().isEmpty()) {
0140             annotation = new KisAnnotation("icc", profile->name(), profile->rawData());
0141 
0142             if (annotation) {
0143                 // save layer profile
0144                 if (store->open("profile.icc")) {
0145                     store->write(annotation->annotation());
0146                     store->close();
0147                 }
0148             }
0149         }
0150     }
0151 
0152     QMimeData *mimeData = new QMimeData;
0153     Q_CHECK_PTR(mimeData);
0154 
0155     if (mimeData) {
0156         mimeData->setData(mimeType, buffer.buffer());
0157     }
0158 
0159     // We also create a QImage so we can interchange with other applications
0160     QImage qimage;
0161     KisConfig cfg(true);
0162     const KoColorProfile *monitorProfile =
0163         cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow()));
0164     qimage = dev->convertToQImage(monitorProfile,
0165                                   KoColorConversionTransformation::internalRenderingIntent(),
0166                                   KoColorConversionTransformation::internalConversionFlags());
0167     if (!qimage.isNull() && mimeData) {
0168         mimeData->setImageData(qimage);
0169     }
0170 
0171     if (mimeData) {
0172         d->pushedClipboard = true;
0173         d->clipboard->setMimeData(mimeData);
0174     }
0175 }
0176 
0177 void KisClipboard::setClip(KisPaintDeviceSP dev, const QPoint &topLeft)
0178 {
0179     setClip(dev, topLeft, KisTimeSpan());
0180 }
0181 
0182 KisPaintDeviceSP
0183 KisClipboard::clip(const QRect &imageBounds, bool showPopup, int overridePasteBehaviour, KisTimeSpan *clipRange) const
0184 {
0185     const QMimeData *cbData = d->clipboard->mimeData();
0186 
0187     if (!cbData) {
0188         return nullptr;
0189     }
0190 
0191     dbgUI << Q_FUNC_INFO;
0192     dbgUI << "\tFormats: " << cbData->formats();
0193     dbgUI << "\tUrls: " << cbData->urls();
0194     dbgUI << "\tHas images: " << cbData->hasImage();
0195 
0196     return clipFromMimeData(cbData, imageBounds, showPopup, overridePasteBehaviour, clipRange, true);
0197 }
0198 
0199 KisPaintDeviceSP KisClipboard::clipFromMimeData(const QMimeData *cbData,
0200                                                 const QRect &imageBounds,
0201                                                 bool showPopup,
0202                                                 int overridePasteBehaviour,
0203                                                 KisTimeSpan *clipRange,
0204                                                 bool useClipboardFallback) const
0205 {
0206     if (clipRange) {
0207         *clipRange = KisTimeSpan();
0208     }
0209 
0210     KisPaintDeviceSP clip = clipFromKritaSelection(cbData, imageBounds, clipRange);
0211 
0212     if (!clip) {
0213         clip = clipFromBoardContents(cbData, imageBounds, showPopup, overridePasteBehaviour, useClipboardFallback);
0214     }
0215 
0216     return clip;
0217 }
0218 
0219 KisPaintDeviceSP
0220 KisClipboard::clipFromKritaSelection(const QMimeData *cbData, const QRect &imageBounds, KisTimeSpan *clipRange) const
0221 {
0222     const QByteArray mimeType = QByteArrayLiteral("application/x-krita-selection");
0223 
0224     KisPaintDeviceSP clip;
0225 
0226     if (!cbData) {
0227         return nullptr;
0228     }
0229 
0230     if (cbData->hasFormat(mimeType)) {
0231         QByteArray encodedData = cbData->data(mimeType);
0232         QBuffer buffer(&encodedData);
0233         QScopedPointer<KoStore> store(KoStore::createStore(&buffer, KoStore::Read, mimeType));
0234 
0235         const KoColorProfile *profile = 0;
0236 
0237         QString csDepth, csModel;
0238 
0239         // ColorSpace id of layer data
0240         if (store->hasFile("colormodel")) {
0241             store->open("colormodel");
0242             csModel = QString(store->read(store->size()));
0243             store->close();
0244         }
0245 
0246         if (store->hasFile("colordepth")) {
0247             store->open("colordepth");
0248             csDepth = QString(store->read(store->size()));
0249             store->close();
0250         }
0251 
0252         if (store->hasFile("profile.icc")) {
0253             QByteArray data;
0254             store->open("profile.icc");
0255             data = store->read(store->size());
0256             store->close();
0257             profile = KoColorSpaceRegistry::instance()->createColorProfile(csModel, csDepth, data);
0258         }
0259 
0260         const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(csModel, csDepth, profile);
0261         if (cs) {
0262             clip = new KisPaintDevice(cs);
0263 
0264             if (store->hasFile("layerdata")) {
0265                 store->open("layerdata");
0266                 if (!clip->read(store->device())) {
0267                     clip = 0;
0268                 }
0269                 store->close();
0270             }
0271 
0272             if (clip && !imageBounds.isEmpty()) {
0273                 // load topLeft
0274                 if (store->hasFile("topLeft")) {
0275                     store->open("topLeft");
0276                     QString str = store->read(store->size());
0277                     store->close();
0278                     QStringList list = str.split(' ');
0279                     if (list.size() == 2) {
0280                         QPoint topLeft(list[0].toInt(), list[1].toInt());
0281                         clip->setX(topLeft.x());
0282                         clip->setY(topLeft.y());
0283                     }
0284                 }
0285 
0286                 QRect clipBounds = clip->exactBounds();
0287 
0288                 if (!imageBounds.contains(clipBounds) && !imageBounds.intersects(clipBounds)) {
0289                     QPoint diff = imageBounds.center() - clipBounds.center();
0290                     clip->setX(clip->x() + diff.x());
0291                     clip->setY(clip->y() + diff.y());
0292                 }
0293 
0294                 if (store->hasFile("timeRange") && clipRange) {
0295                     store->open("timeRange");
0296                     QString str = store->read(store->size());
0297                     store->close();
0298                     QStringList list = str.split(' ');
0299                     if (list.size() == 2) {
0300                         KisTimeSpan range = KisTimeSpan::fromTimeToTime(list[0].toInt(), list[1].toInt());
0301                         *clipRange = range;
0302                         dbgUI << "Pasted time range" << range;
0303                     }
0304                 }
0305             }
0306         }
0307     }
0308 
0309     return clip;
0310 }
0311 
0312 KisPaintDeviceSP KisClipboard::clipFromKritaLayers(const QRect &imageBounds,
0313                                                    const KoColorSpace *cs) const
0314 {
0315     const QMimeData *data = KisClipboard::instance()->layersMimeData();
0316 
0317     if (!data) {
0318         return nullptr;
0319     }
0320 
0321     const auto *mimedata = qobject_cast<const KisMimeData *>(data);
0322     KIS_ASSERT_RECOVER_RETURN_VALUE(mimedata, nullptr);
0323 
0324     KisNodeList nodes = mimedata->nodes();
0325 
0326     KisImageSP tempImage = new KisImage(nullptr,
0327                                         imageBounds.width(),
0328                                         imageBounds.height(),
0329                                         cs,
0330                                         "ClipImage");
0331     for (KisNodeSP node : nodes) {
0332         tempImage->addNode(node, tempImage->root());
0333     }
0334     tempImage->refreshGraphAsync();
0335     tempImage->waitForDone();
0336 
0337     return tempImage->projection();
0338 }
0339 
0340 QPair<bool, KisClipboard::PasteFormatBehaviour>
0341 KisClipboard::askUserForSource(const QMimeData *cbData,
0342                                bool useClipboardFallback) const
0343 {
0344     if (!cbData) {
0345         return {false, PASTE_FORMAT_ASK};
0346     }
0347 
0348     KisConfig cfg(true);
0349 
0350     bool saveSourceSetting = false;
0351 
0352     auto choice = (PasteFormatBehaviour)cfg.pasteFormat(false);
0353 
0354     const QImage qimage = [&]() {
0355         QImage qimage = getImageFromMimeData(cbData);
0356 
0357         if (qimage.isNull() && useClipboardFallback) {
0358             qimage = d->clipboard->image();
0359         }
0360 
0361         return qimage;
0362     }();
0363 
0364     if (!qimage.isNull() || cbData->hasUrls()) {
0365         const auto &urls = cbData->urls();
0366 
0367         bool local = false;
0368         bool remote = false;
0369 
0370         std::for_each(urls.constBegin(), urls.constEnd(), [&](const QUrl &url) {
0371             local |= url.isLocalFile();
0372             remote |= !url.isLocalFile();
0373         });
0374 
0375         const bool hasMultipleFormatsAvailable = (remote && local)
0376             || (remote && !qimage.isNull()) || (local && !qimage.isNull());
0377 
0378         const bool defaultOptionUnavailable =
0379             (!remote && choice == PASTE_FORMAT_DOWNLOAD)
0380             || (!local && choice == PASTE_FORMAT_LOCAL)
0381             || (qimage.isNull() && choice == PASTE_FORMAT_CLIP);
0382 
0383         dbgUI << "Incoming paste event:";
0384         dbgUI << "\tHas attached bitmap:" << cbData->hasImage();
0385         dbgUI << "\tHas local images:" << local;
0386         dbgUI << "\tHas remote images:" << remote;
0387         dbgUI << "\tHas multiple formats:" << hasMultipleFormatsAvailable;
0388         dbgUI << "\tDefault source preference" << choice;
0389         dbgUI << "\tDefault source available:" << !defaultOptionUnavailable;
0390 
0391         if (hasMultipleFormatsAvailable && choice == PASTE_FORMAT_ASK) {
0392             KisDlgPasteFormat dlg(qApp->activeWindow());
0393 
0394             dlg.setSourceAvailable(PASTE_FORMAT_DOWNLOAD, remote);
0395             dlg.setSourceAvailable(PASTE_FORMAT_LOCAL, local);
0396             dlg.setSourceAvailable(PASTE_FORMAT_CLIP, !qimage.isNull());
0397 
0398             if (dlg.exec() != KoDialog::Accepted) {
0399                 return {false, PASTE_FORMAT_ASK};
0400             };
0401 
0402             choice = dlg.source();
0403 
0404             saveSourceSetting = dlg.remember();
0405         } else if (defaultOptionUnavailable || choice == PASTE_FORMAT_ASK) {
0406             if (remote) {
0407                 choice = PASTE_FORMAT_DOWNLOAD;
0408             } else if (local) {
0409                 choice = PASTE_FORMAT_LOCAL;
0410             } else if (!qimage.isNull()) {
0411                 choice = PASTE_FORMAT_CLIP;
0412             } else {
0413                 return {false, PASTE_FORMAT_ASK};
0414             }
0415         }
0416     }
0417 
0418     if (saveSourceSetting) {
0419         cfg.setPasteFormat(choice);
0420     }
0421 
0422     dbgUI << "Selected source for the paste:" << choice;
0423 
0424     return {true, choice};
0425 }
0426 
0427 KisPaintDeviceSP KisClipboard::clipFromBoardContents(
0428     const QMimeData *cbData,
0429     const QRect &imageBounds,
0430     bool showPopup,
0431     int pasteBehaviourOverride,
0432     bool useClipboardFallback,
0433     QPair<bool, PasteFormatBehaviour> source) const
0434 {
0435     if (!cbData) {
0436         return nullptr;
0437     }
0438 
0439     KisPaintDeviceSP clip;
0440 
0441     PasteFormatBehaviour choice = PASTE_FORMAT_ASK;
0442 
0443     if (!source.first) {
0444         choice = askUserForSource(cbData).second;
0445     } else {
0446         choice = source.second;
0447     }
0448 
0449     if (choice == PASTE_FORMAT_CLIP) {
0450         const QImage qimage = [&]() {
0451             QImage qimage = getImageFromMimeData(cbData);
0452 
0453             if (qimage.isNull() && useClipboardFallback) {
0454                 qimage = d->clipboard->image();
0455             }
0456 
0457             return qimage;
0458         }();
0459 
0460         KIS_SAFE_ASSERT_RECOVER(!qimage.isNull())
0461         {
0462             warnKrita << "Clipboard was cleared before loading image";
0463             return nullptr;
0464         }
0465 
0466         int behaviour = pasteBehaviourOverride;
0467         bool saveColorSetting = false;
0468 
0469         KisConfig cfg(true);
0470 
0471         if (pasteBehaviourOverride == -1) {
0472             behaviour = cfg.pasteBehaviour();
0473         }
0474 
0475         if (behaviour == PASTE_ASK && showPopup) {
0476             // Ask user each time.
0477             KisDlgMissingColorProfile dlg(qApp->activeWindow());
0478 
0479             if (dlg.exec() != QDialog::Accepted) {
0480                 return nullptr;
0481             }
0482 
0483             behaviour = dlg.source();
0484 
0485             saveColorSetting = dlg.remember(); // should we save this option to the config for next time?
0486         }
0487 
0488         const KoColorSpace *cs = nullptr;
0489         const KoColorProfile *profile = nullptr;
0490         if (!profile && behaviour == PASTE_ASSUME_MONITOR)
0491             profile = cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow()));
0492 
0493         cs = KoColorSpaceRegistry::instance()->rgb8(profile);
0494         if (!cs) {
0495             cs = KoColorSpaceRegistry::instance()->rgb8();
0496             profile = cs->profile();
0497         }
0498 
0499         clip = new KisPaintDevice(cs);
0500         Q_CHECK_PTR(clip);
0501         clip->convertFromQImage(qimage, profile);
0502 
0503         // save the persion's selection to the configuration if the option is checked
0504         if (saveColorSetting) {
0505             cfg.setPasteBehaviour(behaviour);
0506         }
0507     } else {
0508         const auto &urls = cbData->urls();
0509         const auto url = std::find_if(urls.constBegin(), urls.constEnd(), [&](const QUrl &url) {
0510             if (choice == PASTE_FORMAT_DOWNLOAD) {
0511                 return !url.isLocalFile();
0512             } else if (choice == PASTE_FORMAT_LOCAL) {
0513                 return url.isLocalFile();
0514             } else {
0515                 return false;
0516             }
0517         });
0518 
0519         if (url != urls.constEnd()) {
0520             clip = fetchImageByURL(*url);
0521         }
0522     }
0523 
0524     if (clip && !imageBounds.isEmpty()) {
0525         QRect clipBounds = clip->exactBounds();
0526         QPoint diff = imageBounds.center() - clipBounds.center();
0527         clip->setX(diff.x());
0528         clip->setY(diff.y());
0529     }
0530 
0531     return clip;
0532 }
0533 
0534 void KisClipboard::clipboardDataChanged()
0535 {
0536     if (!d->pushedClipboard) {
0537         const QMimeData *cbData = d->clipboard->mimeData();
0538         d->hasClip = (d->clipboard->mimeData()->hasImage() || cbData && cbData->hasFormat("application/x-krita-selection"));
0539     }
0540     d->pushedClipboard = false;
0541     emit clipChanged();
0542 }
0543 
0544 bool KisClipboard::hasClip() const
0545 {
0546     return d->hasClip;
0547 }
0548 
0549 QSize KisClipboard::clipSize() const
0550 {
0551     const auto mimeType = QByteArrayLiteral("application/x-krita-selection");
0552     const QMimeData *cbData = d->clipboard->mimeData();
0553 
0554     KisPaintDeviceSP clip;
0555 
0556     if (cbData && cbData->hasFormat(mimeType)) {
0557         QByteArray encodedData = cbData->data(mimeType);
0558         QBuffer buffer(&encodedData);
0559         QScopedPointer<KoStore> store(KoStore::createStore(&buffer, KoStore::Read, mimeType));
0560         const KoColorProfile *profile = 0;
0561         QString csDepth, csModel;
0562 
0563         // ColorSpace id of layer data
0564         if (store->hasFile("colormodel")) {
0565             store->open("colormodel");
0566             csModel = QString(store->read(store->size()));
0567             store->close();
0568         }
0569 
0570         if (store->hasFile("colordepth")) {
0571             store->open("colordepth");
0572             csDepth = QString(store->read(store->size()));
0573             store->close();
0574         }
0575 
0576         if (store->hasFile("profile.icc")) {
0577             QByteArray data;
0578             store->open("profile.icc");
0579             data = store->read(store->size());
0580             store->close();
0581             profile = KoColorSpaceRegistry::instance()->createColorProfile(csModel, csDepth, data);
0582         }
0583 
0584         const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(csModel, csDepth, profile);
0585         if (!cs) {
0586             cs = KoColorSpaceRegistry::instance()->rgb8();
0587         }
0588         clip = new KisPaintDevice(cs);
0589 
0590         if (store->hasFile("layerdata")) {
0591             store->open("layerdata");
0592             clip->read(store->device());
0593             store->close();
0594         }
0595 
0596         return clip->exactBounds().size();
0597     } else {
0598         if (d->clipboard->mimeData()->hasImage()) {
0599             QImage qimage = d->clipboard->image();
0600             return qimage.size();
0601         }
0602     }
0603     return QSize();
0604 }
0605 
0606 void KisClipboard::setLayers(KisNodeList nodes, KisImageSP image, bool forceCopy)
0607 {
0608     /**
0609      * See a comment in KisMimeData::deepCopyNodes()
0610      */
0611     QMimeData *data = KisMimeData::mimeForLayersDeepCopy(nodes, image, forceCopy);
0612     if (!data)
0613         return;
0614 
0615     d->clipboard->setMimeData(data);
0616 }
0617 
0618 bool KisClipboard::hasLayers() const
0619 {
0620     const QByteArray mimeType = QByteArrayLiteral("application/x-krita-node-internal-pointer");
0621     return d->clipboard->mimeData()->hasFormat(mimeType);
0622 }
0623 
0624 bool KisClipboard::hasLayerStyles() const
0625 {
0626     // NOTE: please don't disable the pacte action based on the
0627     //       result of this function, because we allow pasting
0628     //       of the layer styles as 'text/plain'
0629 
0630     return d->clipboard->mimeData()->hasFormat("application/x-krita-layer-style");
0631 }
0632 
0633 const QMimeData *KisClipboard::layersMimeData() const
0634 {
0635     const QMimeData *cbData = d->clipboard->mimeData();
0636     return cbData->hasFormat("application/x-krita-node-internal-pointer") ? cbData : 0;
0637 }
0638 
0639 bool KisClipboard::hasUrls() const
0640 {
0641     return d->clipboard->mimeData()->hasUrls();
0642 }
0643 
0644 QImage KisClipboard::getImageFromMimeData(const QMimeData *cbData) const
0645 {
0646     static const QList<ClipboardImageFormat> supportedFormats = {
0647         {{"image/png"}, "PNG"},
0648         {{"image/tiff"}, "TIFF"},
0649         {{"image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"}, "BMP"}};
0650 
0651     QImage image;
0652     QSet<QString> clipboardMimeTypes;
0653 
0654     Q_FOREACH (const QString &format, cbData->formats()) {
0655         clipboardMimeTypes << format;
0656     }
0657 
0658     Q_FOREACH (const ClipboardImageFormat &item, supportedFormats) {
0659         const QSet<QString> &intersection = item.mimeTypes & clipboardMimeTypes;
0660         if (intersection.isEmpty()) {
0661             continue;
0662         }
0663 
0664         const QString &format = *intersection.constBegin();
0665         const QByteArray &imageData = cbData->data(format);
0666         if (imageData.isEmpty()) {
0667             continue;
0668         }
0669 
0670         if (image.loadFromData(imageData, item.format.toLatin1())) {
0671             break;
0672         }
0673     }
0674 
0675     if (image.isNull() && cbData->hasImage()) {
0676         image = qvariant_cast<QImage>(cbData->imageData());
0677     }
0678 
0679     return image;
0680 }
0681 
0682 KisPaintDeviceSP KisClipboard::fetchImageByURL(const QUrl &originalUrl) const
0683 {
0684     KisPaintDeviceSP result;
0685     QUrl url(originalUrl);
0686     QScopedPointer<QTemporaryFile> tmp;
0687 
0688     if (!originalUrl.isLocalFile()) {
0689         tmp.reset(new QTemporaryFile());
0690         tmp->setAutoRemove(true);
0691 
0692         // download the file and substitute the url
0693         KisRemoteFileFetcher fetcher;
0694 
0695         if (!fetcher.fetchFile(originalUrl, tmp.data())) {
0696             qWarning() << "Fetching" << originalUrl << "failed";
0697             return result;
0698         }
0699         url = QUrl::fromLocalFile(tmp->fileName());
0700     }
0701 
0702     if (url.isLocalFile()) {
0703         QFileInfo fileInfo(url.toLocalFile());
0704 
0705         QString type = KisMimeDatabase::mimeTypeForFile(url.toLocalFile());
0706         QStringList mimes = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import);
0707 
0708         if (!mimes.contains(type)) {
0709             QString msg = KisImportExportErrorCode(ImportExportCodes::FileFormatNotSupported).errorMessage();
0710             QMessageBox::warning(KisPart::instance()->currentMainwindow(),
0711                                  i18nc("@title:window", "Krita"),
0712                                  i18n("Could not open %2.\nReason: %1.", msg, url.toDisplayString()));
0713             return result;
0714         }
0715 
0716         QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
0717 
0718         if (doc->openPath(url.toLocalFile(), KisDocument::DontAddToRecent)) {
0719             // Wait for required updates, if any. BUG: 448256
0720             KisLayerUtils::forceAllDelayedNodesUpdate(doc->image()->root());
0721             doc->image()->waitForDone();
0722             result = new KisPaintDevice(*doc->image()->projection());
0723         } else {
0724             qWarning() << "Failed to import file" << url.toLocalFile();
0725         }
0726     }
0727 
0728     return result;
0729 }