Revision control
++ // Copyright (C) 2023 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
++
++ #include "abstractviewer.h"
++ #include "viewerfactory.h"
++ #include "viewerinterfaces.h"
++
++ #include <QApplication>
++ #include <QMessageBox>
++ #include <QWidget>
++
++ #include <QDir>
++ #include <QMimeDatabase>
++ #include <QMimeType>
++ #include <QPluginLoader>
++ #include <QTimer>
++ #include <QTemporaryFile>
++
++ using namespace Qt::StringLiterals;
++
++ namespace {
++ struct Tr {
++ Q_DECLARE_TR_FUNCTIONS(ViewerFactory);
++ };
++ }
++
++ ViewerFactory::ViewerFactory(QWidget *displayWidget, QMainWindow *mainWindow, DefaultPolicy policy)
++ : m_defaultPolicy(policy),
++ m_displayWidget(displayWidget),
++ m_mainWindow(mainWindow)
++ {
++ Q_ASSERT(m_displayWidget);
++ Q_ASSERT(m_mainWindow);
++ loadViewerPlugins();
++ }
++
++ ViewerFactory::~ViewerFactory()
++ {
++ unload();
++ }
++
++ AbstractViewer *ViewerFactory::viewer(QFile *file) const
++ {
++ Q_ASSERT(file);
++
++ const QFileInfo info(*file);
++
++ // Find via extension
++ AbstractViewer *viewer = ViewerFactory::viewer(info.suffix());
++
++ // Find via mime type
++ if (!viewer) {
++ QMimeDatabase db;
++ const auto mimeType = db.mimeTypeForFile(info);
++ viewer = ViewerFactory::viewer(mimeType);
++ if (!viewer) {
++ qWarning() << "Mime type" << mimeType.name() << "not supported.";
++ return nullptr;
++ }
++ }
++
++ Q_ASSERT(viewer);
++ viewer->init(file, m_displayWidget, m_mainWindow);
++ return viewer;
++ }
++
++ AbstractViewer *ViewerFactory::viewer(const QByteArray &data, const QString &mimeType) const
++ {
++ Q_ASSERT(!data.isEmpty());
++
++ QMimeDatabase db;
++ QMimeType qtMime = db.mimeTypeForName(mimeType);
++
++ if (!qtMime.isValid() ||
++ qtMime.name() == QStringLiteral("application/octet-stream")) {
++ qtMime = db.mimeTypeForData(data);
++ }
++
++ // Find via mime type
++ AbstractViewer *viewer = ViewerFactory::viewer(qtMime);
++ QTemporaryFile *tmpFile = new QTemporaryFile(viewer);
++
++ if (!tmpFile->open()) {
++ delete tmpFile;
++ return nullptr;
++ }
++
++ tmpFile->write(data);
++ tmpFile->flush();
++ tmpFile->seek(0);
++
++ Q_ASSERT(viewer);
++ viewer->init(tmpFile, m_displayWidget, m_mainWindow);
++ return viewer;
++ }
++
++ //! [loader]
++ void ViewerFactory::loadViewerPlugins()
++ {
++ if (!m_viewers.isEmpty())
++ return;
++ //! [loader]
++
++ //! [static]
++ // Load static plugins
++ const QObjectList &staticPlugins = QPluginLoader::staticInstances();
++ for (auto *plugin : staticPlugins)
++ addViewer(plugin);
++ //! [static]
++
++ //! [shared]
++ // Load shared plugins
++ QDir pluginsDir = QDir(QApplication::applicationDirPath());
++ #if defined(Q_OS_DARWIN)
++ if (pluginsDir.exists("../PlugIns"_L1)) { // installed build
++ pluginsDir.cd("../PlugIns"_L1);
++ } else {
++ pluginsDir.cd("../../../../plugins"_L1); // non-installed build
++ }
++ #elif defined(Q_OS_WIN)
++ if (pluginsDir.exists("plugins"_L1)) { // non-installed build
++ pluginsDir.cd("plugins"_L1);
++ } else {
++ pluginsDir.cd("../plugins"_L1); // installed build
++ }
++ #else
++ pluginsDir.cd("../plugins"_L1); // installed and non-installed build
++ #endif
++
++ // qDebug("Loading plugins from %s...", qUtf8Printable(pluginsDir.path()));
++ const auto entryList = pluginsDir.entryList(QDir::Files);
++ for (const QString &fileName : entryList) {
++ QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
++ QObject *plugin = loader.instance();
++ if (plugin)
++ addViewer(plugin);
++ #if 0
++ else
++ qDebug() << loader.errorString();
++ #endif
++ }
++ }
++ //! [shared]
++
++ void ViewerFactory::unload()
++ {
++ qDeleteAll(viewers());
++ m_viewers.clear();
++ }
++
++ void ViewerFactory::addViewer(QObject *viewerObject)
++ {
++ auto *interface = qobject_cast<ViewerInterface *>(viewerObject);
++ if (!interface)
++ return;
++
++ // discard viewers, which don't support any MIME types
++ if (interface->supportedMimeTypes().isEmpty()) {
++ delete interface;
++ return;
++ }
++
++ // Set custom default viewer
++ if (interface->viewer()->isDefaultViewer())
++ m_defaultViewer = interface->viewer();
++
++ m_viewers.insert(interface->viewerName(), interface->viewer());
++ }
++
++ QStringList ViewerFactory::viewerNames(bool showDefault) const
++ {
++ if (!showDefault)
++ return m_viewers.keys();
++
++ QStringList list;
++ for (auto it = m_viewers.constBegin(); it != m_viewers.constEnd(); ++it) {
++ QString name = it.key();
++ if ((m_defaultViewer && it.value()->isDefaultViewer())
++ || (!m_defaultViewer && it.key() == "TxtViewer"_L1)) {
++ name += "(default)"_L1;
++ }
++ list.append(name);
++ }
++ return list;
++ }
++
++ ViewerFactory::ViewerList ViewerFactory::viewers() const
++ {
++ return m_viewers.values();
++ }
++
++ AbstractViewer *ViewerFactory::findViewer(const QString &viewerName) const
++ {
++ const ViewerList &viewerList = viewers();
++ for (auto *viewer : viewerList) {
++ if (viewer->viewerName() == viewerName)
++ return viewer;
++ }
++ qWarning() << "Plugin" << viewerName << "not loaded.";
++ return nullptr;
++ }
++
++ AbstractViewer *ViewerFactory::viewer(const QMimeType &mimeType) const
++ {
++ const ViewerList &viewerList = viewers();
++
++ for (AbstractViewer *viewer : viewerList) {
++ for (const QString &type : viewer->supportedMimeTypes()) {
++ if (mimeType.inherits(type))
++ return viewer;
++ }
++ }
++
++ AbstractViewer *viewer = defaultViewer();
++ if (!viewer) {
++ QMessageBox mbox;
++ mbox.setIcon(QMessageBox::Warning);
++ mbox.setText(Tr::tr("No viewers for the chosen file format."));
++ mbox.setStandardButtons(QMessageBox::Ok);
++ QTimer::singleShot(8000, &mbox, [&mbox]() { mbox.close(); });
++ mbox.exec();
++ return nullptr;
++ }
++
++ if (m_defaultWarning) {
++ QMessageBox mbox;
++ mbox.setIcon(QMessageBox::Warning);
++ mbox.setText(Tr::tr("Mime type %1 not supported. Falling back to %2.")
++ .arg(mimeType.name(), viewer->viewerName()));
++ mbox.setStandardButtons(QMessageBox::Ok);
++ QTimer::singleShot(8000, &mbox, [&mbox](){ mbox.close(); });
++ mbox.exec();
++ }
++
++ return viewer;
++ }
++
++ AbstractViewer *ViewerFactory::viewer(const QString &extension) const
++ {
++ const ViewerList &viewerList = viewers();
++ for (AbstractViewer *viewer : viewerList) {
++ if (viewer->supportedExtensions().contains(extension))
++ return viewer;
++ }
++ return nullptr;
++ }
++
++ AbstractViewer *ViewerFactory::defaultViewer() const
++ {
++ switch (m_defaultPolicy) {
++ case DefaultPolicy::NeverDefault:
++ return nullptr;
++ case DefaultPolicy::DefaultToCustomViewer:
++ return m_defaultViewer ? m_defaultViewer : findViewer("TxtViewer"_L1);
++ case DefaultPolicy::DefaultToTxtViewer:
++ return findViewer("TxtViewer"_L1);
++ }
++ Q_UNREACHABLE();
++ }
++
++ QStringList ViewerFactory::supportedMimeTypes() const
++ {
++ static QStringList mimeTypes;
++ if (!mimeTypes.isEmpty())
++ return mimeTypes;
++
++ const ViewerList &viewerList = viewers();
++ for (auto viewer : viewerList) {
++ mimeTypes.append(viewer->supportedMimeTypes());
++ const QStringList &extensions = viewer->supportedExtensions();
++ if (extensions.isEmpty())
++ continue;
++ mimeTypes << (Tr::tr("Plus extensions: %1").arg(extensions.join(","_L1)));
++ }
++
++ return mimeTypes;
++ }