#include "IO/ImageFileDrop.h" #include "Document/ProjectWorkspace.h" #include "IO/RawImporter.h" #include "Logging.h" #include #include #include #include namespace { // Swift's order: PNG, JPEG, HEIC, TIFF, Photoshop, RAW, then any. QStringList pictureFormats(const QMimeData &data) { QStringList formats; for (const QString &format : {QStringLiteral("image/png"), QStringLiteral("image/jpeg"), QStringLiteral("image/heic"), QStringLiteral("image/tiff"), QStringLiteral("image/vnd.adobe.photoshop")}) if (data.hasFormat(format)) formats << format; for (const QString &format : data.formats()) if (RawImporter::isRawType(format) && !formats.contains(format)) formats << format; for (const QString &format : data.formats()) if (format.startsWith(QLatin1String("image/")) && !formats.contains(format)) formats << format; return formats; } // A dropped picture copied to a file for the importer. std::optional temporaryFile(const QMimeData &data) { const QStringList formats = pictureFormats(data); if (formats.isEmpty()) return std::nullopt; const QString suffix = QMimeDatabase().mimeTypeForName(formats.first()).preferredSuffix(); // Its own folder keeps the name the layers show. const QString folder = QDir::temp().filePath(QUuid::createUuid().toString(QUuid::WithoutBraces).toUpper()); if (!QDir().mkpath(folder)) { qCWarning(lcIO).noquote() << "could not make a folder for a dropped picture:" << folder; return std::nullopt; } QFile file(QDir(folder).filePath(QStringLiteral("Dropped.") + (suffix.isEmpty() ? QStringLiteral("png") : suffix))); const QByteArray bytes = data.data(formats.first()); if (!file.open(QIODevice::WriteOnly) || file.write(bytes) != bytes.size() || !file.flush()) { qCWarning(lcIO) << "could not keep a dropped picture:" << file.errorString(); // A failed flush fails `remove()` too: close, then unlink. file.close(); QFile::remove(file.fileName()); QDir().rmdir(folder); return std::nullopt; } return QUrl::fromLocalFile(file.fileName()); } } bool ImageFileDrop::holdsImages(const QMimeData &data) { for (const QUrl &url : data.urls()) if (url.isLocalFile()) return true; return !pictureFormats(data).isEmpty(); } std::vector> ImageFileDrop::providers(const QMimeData &data) { std::vector> each; const QList urls = data.urls(); if (std::any_of(urls.begin(), urls.end(), [](const QUrl &url) { return url.isLocalFile(); })) { for (const QUrl &url : urls) { auto provider = std::make_shared(); provider->setUrls({url}); each.push_back(provider); } return each; } // No file: a picture with its address is one item. auto whole = std::make_shared(); // Swift asks for the preferred picture alone. const QStringList formats = pictureFormats(data); if (!formats.isEmpty()) whole->setData(formats.first(), data.data(formats.first())); each.push_back(whole); return each; } void ImageFileDrop::importProviders(const QMimeData &data, EditorSession &session, std::optional point, ProjectWorkspace *workspace, std::optional destination, std::function done) { QList urls; bool unreadable = false; for (const QUrl &url : data.urls()) { if (url.isLocalFile()) urls << url; else unreadable = true; } // Not a file on disk: a browser's or screenshot's picture. if (urls.isEmpty()) { const std::optional copy = temporaryFile(data); unreadable = !copy; if (copy) urls << *copy; } const auto finished = [&session, unreadable, done] { if (unreadable) { const QString message = QStringLiteral("Some dropped items couldn’t be read. Drag JPEG, PNG, HEIC, TIFF, or Photoshop (PSD) files from the file manager."); session.setImportError(session.importError() ? *session.importError() + QStringLiteral("\n\n") + message : message); } if (done) done(); }; if (workspace) workspace->receive(urls, destination, point, finished); else session.importImages(urls, point, finished); }