2023-03-23 15:25:43 +00:00
|
|
|
// Copyright (C) 2023 The Qt Company Ltd.
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
|
|
|
|
2023-06-20 11:39:08 +00:00
|
|
|
#include "abstractviewer.h"
|
|
|
|
#include "viewerfactory.h"
|
|
|
|
#include "viewerinterfaces.h"
|
|
|
|
|
|
|
|
#include <QApplication>
|
|
|
|
#include <QMessageBox>
|
2023-03-23 15:25:43 +00:00
|
|
|
#include <QWidget>
|
2023-06-20 11:39:08 +00:00
|
|
|
|
|
|
|
#include <QDir>
|
2023-03-23 15:25:43 +00:00
|
|
|
#include <QMimeDatabase>
|
|
|
|
#include <QMimeType>
|
|
|
|
#include <QPluginLoader>
|
2023-06-19 12:30:24 +00:00
|
|
|
#include <QTimer>
|
2023-06-20 11:39:08 +00:00
|
|
|
|
2023-06-20 12:20:47 +00:00
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
|
2025-04-17 12:46:19 +00:00
|
|
|
namespace {
|
|
|
|
struct Tr {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(ViewerFactory);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-03-23 15:25:43 +00:00
|
|
|
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);
|
|
|
|
|
2023-10-09 15:06:44 +00:00
|
|
|
// Find via extension
|
|
|
|
AbstractViewer *viewer = ViewerFactory::viewer(info.suffix());
|
|
|
|
|
|
|
|
// Find via mime type
|
2023-03-23 15:25:43 +00:00
|
|
|
if (!viewer) {
|
2023-10-09 15:06:44 +00:00
|
|
|
QMimeDatabase db;
|
|
|
|
const auto mimeType = db.mimeTypeForFile(info);
|
|
|
|
viewer = ViewerFactory::viewer(mimeType);
|
|
|
|
if (!viewer) {
|
|
|
|
qWarning() << "Mime type" << mimeType.name() << "not supported.";
|
|
|
|
return nullptr;
|
|
|
|
}
|
2023-03-23 15:25:43 +00:00
|
|
|
}
|
|
|
|
|
2023-10-09 15:06:44 +00:00
|
|
|
Q_ASSERT(viewer);
|
2023-03-23 15:25:43 +00:00
|
|
|
viewer->init(file, m_displayWidget, m_mainWindow);
|
|
|
|
return viewer;
|
|
|
|
}
|
|
|
|
|
2023-12-14 16:53:04 +00:00
|
|
|
//! [loader]
|
2023-03-23 15:25:43 +00:00
|
|
|
void ViewerFactory::loadViewerPlugins()
|
|
|
|
{
|
|
|
|
if (!m_viewers.isEmpty())
|
|
|
|
return;
|
2023-12-14 16:53:04 +00:00
|
|
|
//! [loader]
|
2023-03-23 15:25:43 +00:00
|
|
|
|
2023-12-14 16:53:04 +00:00
|
|
|
//! [static]
|
2023-03-23 15:25:43 +00:00
|
|
|
// Load static plugins
|
|
|
|
const QObjectList &staticPlugins = QPluginLoader::staticInstances();
|
|
|
|
for (auto *plugin : staticPlugins)
|
|
|
|
addViewer(plugin);
|
2023-12-14 16:53:04 +00:00
|
|
|
//! [static]
|
2023-03-23 15:25:43 +00:00
|
|
|
|
2023-12-14 16:53:04 +00:00
|
|
|
//! [shared]
|
2023-03-23 15:25:43 +00:00
|
|
|
// Load shared plugins
|
|
|
|
QDir pluginsDir = QDir(QApplication::applicationDirPath());
|
documentviewer: Fix deployment for macOS, Windows, Linux
Adapt the logic to install and detect especially the
plugins libs:
* Do not just compile them into the app build folder, but
keep the libs separate under the 'plugins' direcory
* At installation time, follow the logic of *deployqt
for Qt plugins, and store them in CMAKE_INSTALL_PREFIX/plugins
(Linux, Windows) or Document Viewer.app/Contents/Plugins
(macOS).
Unfortunately, this all requires different look ups at runtime for
all operating systems. For macOS and Windows, we furthermore need
to check both for valid paths with an installed build, and an
un-installed build.
While at it, move the install logic to app/CMakeLists.txt, so that
there is less repetition.
An alternative approach would be to calculate the relative paths
at configure time, and either pass on command line or in a created
header file. Anyhow, the current approach is more compact.
Pick-to: 6.10
Fixes: QTBUG-138476
Change-Id: I467d3bd4e14ef6b8b747bceb7177837e2edd3b8c
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2025-07-10 15:04:37 +00:00
|
|
|
#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
|
2023-03-23 15:25:43 +00:00
|
|
|
}
|
documentviewer: Fix deployment for macOS, Windows, Linux
Adapt the logic to install and detect especially the
plugins libs:
* Do not just compile them into the app build folder, but
keep the libs separate under the 'plugins' direcory
* At installation time, follow the logic of *deployqt
for Qt plugins, and store them in CMAKE_INSTALL_PREFIX/plugins
(Linux, Windows) or Document Viewer.app/Contents/Plugins
(macOS).
Unfortunately, this all requires different look ups at runtime for
all operating systems. For macOS and Windows, we furthermore need
to check both for valid paths with an installed build, and an
un-installed build.
While at it, move the install logic to app/CMakeLists.txt, so that
there is less repetition.
An alternative approach would be to calculate the relative paths
at configure time, and either pass on command line or in a created
header file. Anyhow, the current approach is more compact.
Pick-to: 6.10
Fixes: QTBUG-138476
Change-Id: I467d3bd4e14ef6b8b747bceb7177837e2edd3b8c
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2025-07-10 15:04:37 +00:00
|
|
|
#else
|
|
|
|
pluginsDir.cd("../plugins"_L1); // installed and non-installed build
|
2023-03-23 15:25:43 +00:00
|
|
|
#endif
|
documentviewer: Fix deployment for macOS, Windows, Linux
Adapt the logic to install and detect especially the
plugins libs:
* Do not just compile them into the app build folder, but
keep the libs separate under the 'plugins' direcory
* At installation time, follow the logic of *deployqt
for Qt plugins, and store them in CMAKE_INSTALL_PREFIX/plugins
(Linux, Windows) or Document Viewer.app/Contents/Plugins
(macOS).
Unfortunately, this all requires different look ups at runtime for
all operating systems. For macOS and Windows, we furthermore need
to check both for valid paths with an installed build, and an
un-installed build.
While at it, move the install logic to app/CMakeLists.txt, so that
there is less repetition.
An alternative approach would be to calculate the relative paths
at configure time, and either pass on command line or in a created
header file. Anyhow, the current approach is more compact.
Pick-to: 6.10
Fixes: QTBUG-138476
Change-Id: I467d3bd4e14ef6b8b747bceb7177837e2edd3b8c
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2025-07-10 15:04:37 +00:00
|
|
|
|
|
|
|
// qDebug("Loading plugins from %s...", qUtf8Printable(pluginsDir.path()));
|
2023-03-23 15:25:43 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2023-12-14 16:53:04 +00:00
|
|
|
//! [shared]
|
2023-03-23 15:25:43 +00:00
|
|
|
|
|
|
|
void ViewerFactory::unload()
|
|
|
|
{
|
|
|
|
qDeleteAll(viewers());
|
|
|
|
m_viewers.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ViewerFactory::addViewer(QObject *viewerObject)
|
|
|
|
{
|
|
|
|
auto *interface = qobject_cast<ViewerInterface *>(viewerObject);
|
|
|
|
if (!interface)
|
|
|
|
return;
|
|
|
|
|
2024-09-18 09:44:39 +00:00
|
|
|
// discard viewers, which don't support any MIME types
|
|
|
|
if (interface->supportedMimeTypes().isEmpty()) {
|
|
|
|
delete interface;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-23 15:25:43 +00:00
|
|
|
// 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())
|
2023-06-20 12:20:47 +00:00
|
|
|
|| (!m_defaultViewer && it.key() == "TxtViewer"_L1)) {
|
|
|
|
name += "(default)"_L1;
|
2023-03-23 15:25:43 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-19 12:30:24 +00:00
|
|
|
AbstractViewer *viewer = defaultViewer();
|
2025-04-22 12:50:39 +00:00
|
|
|
if (!viewer) {
|
|
|
|
QMessageBox mbox;
|
|
|
|
mbox.setIcon(QMessageBox::Warning);
|
2025-04-17 12:46:19 +00:00
|
|
|
mbox.setText(Tr::tr("No viewers for the chosen file format."));
|
2025-04-22 12:50:39 +00:00
|
|
|
mbox.setStandardButtons(QMessageBox::Ok);
|
|
|
|
QTimer::singleShot(8000, &mbox, [&mbox]() { mbox.close(); });
|
|
|
|
mbox.exec();
|
|
|
|
return nullptr;
|
|
|
|
}
|
2023-06-19 12:30:24 +00:00
|
|
|
|
|
|
|
if (m_defaultWarning) {
|
|
|
|
QMessageBox mbox;
|
|
|
|
mbox.setIcon(QMessageBox::Warning);
|
2025-04-17 12:46:19 +00:00
|
|
|
mbox.setText(Tr::tr("Mime type %1 not supported. Falling back to %2.")
|
2023-06-19 12:30:24 +00:00
|
|
|
.arg(mimeType.name(), viewer->viewerName()));
|
|
|
|
mbox.setStandardButtons(QMessageBox::Ok);
|
|
|
|
QTimer::singleShot(8000, &mbox, [&mbox](){ mbox.close(); });
|
|
|
|
mbox.exec();
|
|
|
|
}
|
|
|
|
|
|
|
|
return viewer;
|
2023-03-23 15:25:43 +00:00
|
|
|
}
|
|
|
|
|
2023-10-09 15:06:44 +00:00
|
|
|
AbstractViewer *ViewerFactory::viewer(const QString &extension) const
|
|
|
|
{
|
|
|
|
const ViewerList &viewerList = viewers();
|
|
|
|
for (AbstractViewer *viewer : viewerList) {
|
|
|
|
if (viewer->supportedExtensions().contains(extension))
|
|
|
|
return viewer;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2023-03-23 15:25:43 +00:00
|
|
|
AbstractViewer *ViewerFactory::defaultViewer() const
|
|
|
|
{
|
|
|
|
switch (m_defaultPolicy) {
|
|
|
|
case DefaultPolicy::NeverDefault:
|
|
|
|
return nullptr;
|
|
|
|
case DefaultPolicy::DefaultToCustomViewer:
|
2023-06-20 12:20:47 +00:00
|
|
|
return m_defaultViewer ? m_defaultViewer : findViewer("TxtViewer"_L1);
|
2023-03-23 15:25:43 +00:00
|
|
|
case DefaultPolicy::DefaultToTxtViewer:
|
2023-06-20 12:20:47 +00:00
|
|
|
return findViewer("TxtViewer"_L1);
|
2023-03-23 15:25:43 +00:00
|
|
|
}
|
|
|
|
Q_UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList ViewerFactory::supportedMimeTypes() const
|
|
|
|
{
|
|
|
|
static QStringList mimeTypes;
|
|
|
|
if (!mimeTypes.isEmpty())
|
|
|
|
return mimeTypes;
|
|
|
|
|
|
|
|
const ViewerList &viewerList = viewers();
|
2023-10-09 15:06:44 +00:00
|
|
|
for (auto viewer : viewerList) {
|
2023-03-23 15:25:43 +00:00
|
|
|
mimeTypes.append(viewer->supportedMimeTypes());
|
2023-10-09 15:06:44 +00:00
|
|
|
const QStringList &extensions = viewer->supportedExtensions();
|
|
|
|
if (extensions.isEmpty())
|
|
|
|
continue;
|
2025-04-17 12:46:19 +00:00
|
|
|
mimeTypes << (Tr::tr("Plus extensions: %1").arg(extensions.join(","_L1)));
|
2023-10-09 15:06:44 +00:00
|
|
|
}
|
|
|
|
|
2023-03-23 15:25:43 +00:00
|
|
|
return mimeTypes;
|
|
|
|
}
|