Migrate documentviewer example into plugin based architecture

This patch changes the document viewer example to a plugin based
architecture.
It adapts the documentation accordingly.

Change-Id: Ia24028aa27e21fb8ab36f5ef3a9953be60858b19
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
This commit is contained in:
Axel Spoerl 2023-03-23 16:25:43 +01:00
parent 58d456c89e
commit ba61af0bf3
72 changed files with 1162 additions and 236 deletions

View File

@ -23,6 +23,6 @@ endif()
if(TARGET Qt::Quick AND TARGET Qt::qsb AND TARGET Qt::Pdf)
qt_internal_add_example(photosurface)
endif()
if(TARGET Qt::Widgets AND TARGET Qt::PdfWidgets)
if(TARGET Qt::Widgets)
qt_internal_add_example(documentviewer)
endif()

View File

@ -11,7 +11,7 @@ endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/demos/${PROJECT_NAME}")
find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick)
qt_standard_project_setup(REQUIRES 6.5)
qt_standard_project_setup()
qt_add_executable(colorpaletteclient
main.cpp

View File

@ -1,93 +1,21 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(documentviewer LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets
OPTIONAL_COMPONENTS PrintSupport PdfWidgets)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}")
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets PdfWidgets
OPTIONAL_COMPONENTS PrintSupport)
qt_standard_project_setup()
qt_add_executable(documentviewer
main.cpp
mainwindow.cpp mainwindow.h mainwindow.ui
abstractviewer.cpp abstractviewer.h
viewerfactory.cpp viewerfactory.h
jsonviewer.cpp jsonviewer.h
txtviewer.cpp txtviewer.h
pdfviewer.cpp pdfviewer.h
zoomselector.cpp zoomselector.h
hoverwatcher.cpp hoverwatcher.cpp
)
set_target_properties(documentviewer PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
)
# Resources:
set(documentviewer_resource_files
"images/copy@2x.png"
"images/copy.png"
"images/cut@2x.png"
"images/cut.png"
"images/go-next-view@2x.png"
"images/go-next-view-page@2x.png"
"images/go-next-view-page.png"
"images/go-next-view.png"
"images/go-previous-view@2x.png"
"images/go-previous-view-page@2x.png"
"images/go-previous-view-page.png"
"images/go-previous-view.png"
"images/magnifier@2x.png"
"images/magnifier.png"
"images/open@2x.png"
"images/open.png"
"images/paste@2x.png"
"images/paste.png"
"images/print2x.png"
"images/print.png"
"images/qt-logo@2x.png"
"images/qt-logo.png"
"images/zoom-fit-best@2x.png"
"images/zoom-fit-best.png"
"images/zoom-fit-width@2x.png"
"images/zoom-fit-width.png"
"images/zoom-in@2x.png"
"images/zoom-in.png"
"images/zoom-original@2x.png"
"images/zoom-original.png"
"images/zoom-out@2x.png"
"images/zoom-out.png"
"images/zoom-previous@2x.png"
"images/zoom-previous.png"
)
qt6_add_resources(documentviewer "documentviewer"
PREFIX
"/demos/documentviewer"
FILES
${documentviewer_resource_files}
)
target_link_libraries(documentviewer PUBLIC
Qt::Core
Qt::Gui
Qt::Widgets
Qt::PdfWidgets
)
if(TARGET Qt6::PrintSupport)
target_link_libraries(documentviewer PRIVATE Qt6::PrintSupport)
add_compile_definitions(QT_DOCUMENTVIEWER_PRINTSUPPORT)
endif()
install(TARGETS documentviewer
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/demos/documentviewer")
add_subdirectory(app)
add_subdirectory(plugins)

View File

@ -0,0 +1,96 @@
cmake_minimum_required(VERSION 3.16)
project(documentviewer LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets
OPTIONAL_COMPONENTS PrintSupport PdfWidgets)
qt_standard_project_setup()
set(CMAKE_AUTOMOC ON)
qt_add_executable(documentviewer
main.cpp
mainwindow.cpp mainwindow.h mainwindow.ui
viewerfactory.cpp viewerfactory.h
abstractviewer.cpp abstractviewer.h
recentfiles.cpp recentfiles.h
recentfilemenu.cpp recentfilemenu.h
viewerinterfaces.h
)
set_target_properties(documentviewer PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
)
qt_add_resources(documentviewer "documentviewer"
PREFIX
"/demos/documentviewer"
FILES
"images/copy@2x.png"
"images/copy.png"
"images/cut@2x.png"
"images/cut.png"
"images/go-next-view@2x.png"
"images/go-next-view-page@2x.png"
"images/go-next-view-page.png"
"images/go-next-view.png"
"images/go-previous-view@2x.png"
"images/go-previous-view-page@2x.png"
"images/go-previous-view-page.png"
"images/go-previous-view.png"
"images/magnifier@2x.png"
"images/magnifier.png"
"images/open@2x.png"
"images/open.png"
"images/paste@2x.png"
"images/paste.png"
"images/print2x.png"
"images/print.png"
"images/qt-logo@2x.png"
"images/qt-logo.png"
"images/zoom-fit-best@2x.png"
"images/zoom-fit-best.png"
"images/zoom-fit-width@2x.png"
"images/zoom-fit-width.png"
"images/zoom-in@2x.png"
"images/zoom-in.png"
"images/zoom-original@2x.png"
"images/zoom-original.png"
"images/zoom-out@2x.png"
"images/zoom-out.png"
"images/zoom-previous@2x.png"
"images/zoom-previous.png"
)
target_link_libraries(documentviewer PRIVATE
Qt::Core
Qt::Gui
Qt::Widgets
)
if(QT6_IS_SHARED_LIBS_BUILD)
add_dependencies(documentviewer
jsonviewer
pdfviewer
txtviewer
)
else()
target_link_libraries(documentviewer PRIVATE
jsonviewer
pdfviewer
txtviewer
)
endif()
if(TARGET Qt6::PrintSupport)
target_link_libraries(documentviewer PRIVATE Qt6::PrintSupport)
add_compile_definitions(QT_DOCUMENTVIEWER_PRINTSUPPORT)
endif()
install(TARGETS documentviewer
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)

View File

@ -13,14 +13,18 @@
#include <QSettings>
#include <QApplication>
#if defined(QT_ABSTRACTVIEWER_PRINTSUPPORT)
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
#include <QPrintDialog>
#endif // QT_ABSTRACTVIEWER_PRINTSUPPORT
#endif // QT_DOCUMENTVIEWER_PRINTSUPPORT
AbstractViewer::AbstractViewer(QFile *file, QWidget *widget, QMainWindow *mainWindow) :
m_file(file),
m_widget(widget)
AbstractViewer::AbstractViewer() : m_file(nullptr), m_widget(nullptr)
{
}
void AbstractViewer::init(QFile *file, QWidget *widget, QMainWindow *mainWindow)
{
m_file.reset(file);
m_widget = widget;
Q_ASSERT(widget);
Q_ASSERT(mainWindow);
m_uiAssets.mainWindow = mainWindow;
@ -35,6 +39,96 @@ AbstractViewer::~AbstractViewer()
qDeleteAll(m_toolBars);
}
bool AbstractViewer::isEmpty() const
{
return !hasContent();
}
bool AbstractViewer::isPrintingEnabled() const
{
return m_printingEnabled;
}
bool AbstractViewer::hasContent() const
{
return false;
}
bool AbstractViewer::supportsOverview() const
{
return false;
}
bool AbstractViewer::isModified() const
{
return false;
}
bool AbstractViewer::saveDocument()
{
return false;
}
bool AbstractViewer::saveDocumentAs()
{
return false;
}
QList<QAction *> AbstractViewer::actions() const
{
return m_actions;
}
QWidget *AbstractViewer::widget() const
{
return m_widget;
}
QList<QMenu *> AbstractViewer::menus() const
{
return m_menus;
}
QMainWindow *AbstractViewer::mainWindow() const
{
return m_uiAssets.mainWindow;
}
QStatusBar *AbstractViewer::statusBar() const
{
return mainWindow()->statusBar();
}
QMenuBar *AbstractViewer::menuBar() const
{
return mainWindow()->menuBar();
}
void AbstractViewer::maybeEnablePrinting()
{
maybeSetPrintingEnabled(true);
}
void AbstractViewer::disablePrinting()
{
maybeSetPrintingEnabled(false);
}
bool AbstractViewer::isDefaultViewer() const
{
return false;
}
AbstractViewer *AbstractViewer::viewer()
{
return this;
}
const AbstractViewer *AbstractViewer::viewer() const
{
return this;
}
void AbstractViewer::statusMessage(const QString &message, const QString &type, int timeout)
{
const QString msg = viewerName() + (type.isEmpty() ? ": " : "/" + type + ": ") + message;
@ -78,7 +172,7 @@ QMenu *AbstractViewer::fileMenu()
void AbstractViewer::print()
{
#ifdef QT_ABSTRACTVIEWER_PRINTSUPPORT
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
static const QString type = tr("Printing");
if (!hasContent()) {
statusMessage(tr("No content to print."), type);
@ -126,7 +220,7 @@ void AbstractViewer::print()
*/
void AbstractViewer::maybeSetPrintingEnabled(bool enabled)
{
#ifndef QT_ABSTRACTVIEWER_PRINTSUPPORT
#ifndef QT_DOCUMENTVIEWER_PRINTSUPPORT
enabled = false;
#else
if (!hasContent())

View File

@ -8,14 +8,9 @@
#include <QMainWindow>
#include <QFileInfo>
#if defined(QT_PRINTSUPPORT_LIB)
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
#include <QtPrintSupport/qtprintsupportglobal.h>
#if QT_CONFIG(printer)
#if QT_CONFIG(printdialog)
#define QT_ABSTRACTVIEWER_PRINTSUPPORT
#include <QPrinter>
#endif // QT_CONFIG(printdialog)
#endif // QT_CONFIG(printer)
#endif // QT_PRINTSUPPORT_LIB
class QToolBar;
@ -27,28 +22,32 @@ class AbstractViewer : public QObject
Q_OBJECT
protected:
explicit AbstractViewer(QFile *file, QWidget *widget, QMainWindow *mainWindow);
AbstractViewer();
public:
virtual ~AbstractViewer();
virtual void init(QFile *file, QWidget *widget, QMainWindow *mainWindow);
void initViewer(QAction *back, QAction *forward, QAction *help, QTabWidget *tabs);
virtual bool isModified() const { return false; }
virtual bool saveDocument() { return false; }
virtual bool saveDocumentAs() { return false; }
virtual bool isModified() const;
virtual bool saveDocument();
virtual bool saveDocumentAs();
virtual QString viewerName() const = 0;
virtual bool supportsOverview() const { return false; }
virtual bool supportsOverview() const;
virtual QByteArray saveState() const = 0;
virtual bool restoreState(QByteArray &) = 0;
virtual bool hasContent() const { return false; }
bool isEmpty() const { return !hasContent(); }
bool isPrintingEnabled() const { return m_printingEnabled; }
virtual bool hasContent() const;
virtual QStringList supportedMimeTypes() const = 0;
virtual bool isDefaultViewer() const;
bool isEmpty() const;
bool isPrintingEnabled() const;
AbstractViewer *viewer();
const AbstractViewer *viewer() const;
QList<QAction *> actions() const { return m_actions; }
QWidget *widget() const { return m_widget; }
QList<QMenu *> menus() const { return m_menus; }
QList<QAction *> actions() const;
QWidget *widget() const;
QList<QMenu *> menus() const;
#ifdef QT_ABSTRACTVIEWER_PRINTSUPPORT
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
protected:
virtual void printDocument(QPrinter *) const {};
#endif
@ -76,18 +75,18 @@ protected:
QToolBar *addToolBar(const QString &);
QMenu *addMenu(const QString &);
QMenu *fileMenu();
QMainWindow *mainWindow() const { return m_uiAssets.mainWindow; }
QStatusBar *statusBar() const { return mainWindow()->statusBar(); }
QMenuBar *menuBar() const { return mainWindow()->menuBar(); }
QMainWindow *mainWindow() const;
QStatusBar *statusBar() const;
QMenuBar *menuBar() const;
std::unique_ptr<QFile> m_file;
QList<QAction *> m_actions;
QWidget *m_widget;
QWidget *m_widget = nullptr;
protected slots:
void maybeSetPrintingEnabled(bool enabled);
inline void maybeEnablePrinting() { return maybeSetPrintingEnabled(true); }
inline void disablePrinting() { return maybeSetPrintingEnabled(false); }
void maybeEnablePrinting();
void disablePrinting();
private:
QList<QMenu *> m_menus;

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 990 B

After

Width:  |  Height:  |  Size: 990 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1010 B

After

Width:  |  Height:  |  Size: 1010 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1006 B

After

Width:  |  Height:  |  Size: 1006 B

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1017 B

After

Width:  |  Height:  |  Size: 1017 B

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 678 B

After

Width:  |  Height:  |  Size: 678 B

View File

Before

Width:  |  Height:  |  Size: 931 B

After

Width:  |  Height:  |  Size: 931 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 905 B

After

Width:  |  Height:  |  Size: 905 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 952 B

After

Width:  |  Height:  |  Size: 952 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 946 B

After

Width:  |  Height:  |  Size: 946 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 940 B

After

Width:  |  Height:  |  Size: 940 B

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 946 B

After

Width:  |  Height:  |  Size: 946 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1,9 +1,12 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "abstractviewer.h"
#include "viewerfactory.h"
#include "abstractviewer.h"
#include "recentfiles.h"
#include "recentfilemenu.h"
#include <QFileDialog>
#include <QSettings>
#include <QToolButton>
@ -15,7 +18,25 @@ MainWindow::MainWindow(QWidget *parent) :
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_recentFiles.reset(new RecentFiles(ui->actionRecent));
connect(m_recentFiles.get(), &RecentFiles::countChanged, this, [&](int count){
ui->actionRecent->setText(QString("%1 recent files").arg(count));
});
readSettings();
m_factory.reset(new ViewerFactory(ui->viewArea, this));
const QStringList &viewers = m_factory->viewerNames();
const QString msg = tr("Available viewers: ") + viewers.join(", ");
statusBar()->showMessage(msg);
auto *menu = new RecentFileMenu(this, m_recentFiles.get());
ui->actionRecent->setMenu(menu);
connect(menu, &RecentFileMenu::fileOpened, this, &MainWindow::openFile);
QWidget *w = ui->mainToolBar->widgetForAction(ui->actionRecent);
auto *button = qobject_cast<QToolButton *>(w);
if (button)
connect(ui->actionRecent, &QAction::triggered, button, &QToolButton::showMenu);
}
MainWindow::~MainWindow()
@ -46,24 +67,48 @@ void MainWindow::openFile(const QString &fileName)
QFileInfo fileInfo(*file);
m_currentDir = fileInfo.dir();
m_recentFiles->addFile(fileInfo.absoluteFilePath());
// If a viewer is already open, save its state first
saveViewerSettings();
m_viewer.reset(ViewerFactory::makeViewer(file, ui->viewArea, this));
restoreViewerSettings();
m_viewer = m_factory->viewer(file);
if (!m_viewer) {
statusBar()->showMessage(tr("File %1 can't be opened.").arg(fileName));
return;
}
ui->actionPrint->setEnabled(m_viewer->hasContent());
connect(m_viewer.get(), &AbstractViewer::printingEnabledChanged, ui->actionPrint, &QAction::setEnabled);
connect(ui->actionPrint, &QAction::triggered, m_viewer.get(), &AbstractViewer::print);
connect(m_viewer.get(), &AbstractViewer::showMessage, statusBar(), &QStatusBar::showMessage);
connect(m_viewer, &AbstractViewer::printingEnabledChanged, ui->actionPrint, &QAction::setEnabled);
connect(ui->actionPrint, &QAction::triggered, m_viewer, &AbstractViewer::print);
connect(m_viewer, &AbstractViewer::showMessage, statusBar(), &QStatusBar::showMessage);
m_viewer->initViewer(ui->actionBack, ui->actionForward, ui->menuHelp->menuAction(), ui->tabWidget);
restoreViewerSettings();
ui->scrollArea->setWidget(m_viewer->widget());
}
void MainWindow::on_actionAbout_triggered()
{
QMessageBox::aboutQt(this);
static const QString &text = [&]{
QString text = tr("A Widgets application to display and print JSON, "
"text and PDF files. Demonstrates various features to use "
"in widget applications: Using QSettings, query and save "
"user preferences, manage file histories and control cursor "
"behavior when hovering over widgets.\n\n"
"This version has loaded the following plugins:\n") +
m_factory->viewerNames().join(", ") +
tr("\n\nIt supports thow following mime types:\n") +
m_factory->supportedMimeTypes().join("\n");
AbstractViewer *def = m_factory->defaultViewer();
if (def) {
text += tr("\n\nOther mime types will be displayed with ") + def->viewerName() + ".";
}
return text;
}();
QMessageBox::about(this, tr("About Document Viewer Demo"), text);
}
void MainWindow::on_actionAboutQt_triggered()
@ -86,6 +131,9 @@ void MainWindow::readSettings()
QByteArray mainWindowState = settings.value(settingsMainWindow).toByteArray();
restoreState(mainWindowState);
}
// Restore recent files
m_recentFiles->restoreFromSettings(settings, settingsFiles);
}
void MainWindow::saveSettings() const
@ -98,6 +146,9 @@ void MainWindow::saveSettings() const
// Save QMainWindow state
settings.setValue(settingsMainWindow, saveState());
// Save recent files
m_recentFiles->saveSettings(settings, settingsFiles);
settings.sync();
}

View File

@ -10,6 +10,8 @@
#include <QStringLiteral>
class AbstractViewer;
class RecentFiles;
class ViewerFactory;
namespace Ui {
class MainWindow;
}
@ -37,14 +39,17 @@ private:
void saveViewerSettings() const;
QDir m_currentDir;
AbstractViewer *m_viewer = nullptr;
std::unique_ptr<Ui::MainWindow> ui;
std::unique_ptr<AbstractViewer> m_viewer;
std::unique_ptr<RecentFiles> m_recentFiles;
int m_classId = -1;
std::unique_ptr<ViewerFactory> m_factory;
static constexpr QLatin1StringView settingsName = QLatin1StringView("DocumentViewerExample");
static constexpr QLatin1StringView settingsDir = QLatin1StringView("WorkingDir");
static constexpr QLatin1StringView settingsMainWindow = QLatin1StringView("MainWindow");
static constexpr QLatin1StringView settingsViewers = QLatin1StringView("Viewers");
static constexpr QLatin1StringView settingsFiles = QLatin1StringView("RecentFiles");
};
#endif // MAINWINDOW_H

View File

@ -14,7 +14,7 @@
<string>Document Viewer Demo</string>
</property>
<property name="windowIcon">
<iconset resource="../../../../../build/dev/qtdoc/examples/demos/documentviewer/.rcc/documentviewer.qrc">
<iconset resource="../../../../../../build/dev/qtdoc/examples/demos/documentviewer/app/.rcc/documentviewer.qrc">
<normaloff>:/demos/documentviewer/images/qt-logo.png</normaloff>:/demos/documentviewer/images/qt-logo.png</iconset>
</property>
<widget class="QWidget" name="centralwidget">
@ -96,6 +96,7 @@
<string>File</string>
</property>
<addaction name="actionOpen"/>
<addaction name="actionRecent"/>
<addaction name="actionPrint"/>
<addaction name="actionQuit"/>
</widget>
@ -121,6 +122,7 @@
<bool>false</bool>
</attribute>
<addaction name="actionOpen"/>
<addaction name="actionRecent"/>
<addaction name="actionPrint"/>
<addaction name="separator"/>
<addaction name="actionBack"/>
@ -141,8 +143,8 @@
</action>
<action name="actionAbout">
<property name="icon">
<iconset theme="help-about">
<normaloff>.</normaloff>.</iconset>
<iconset theme="help-about" resource="../../../../../../build/dev/qtdoc/examples/demos/documentviewer/app/.rcc/documentviewer.qrc">
<normaloff>:/demos/documentviewer/images/qt-logo.png</normaloff>:/demos/documentviewer/images/qt-logo.png</iconset>
</property>
<property name="text">
<string>about documentviewer</string>
@ -189,8 +191,8 @@
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="document-print">
<normaloff>:/demos/documentviewer/images/print.png</normaloff>:/demos/documentviewer/images/print.png</iconset>
<iconset theme="document-print" resource="../../../../../../build/dev/qtdoc/examples/demos/documentviewer/app/.rcc/documentviewer.qrc">
<normaloff>:/demos/documentviewer/images/print.png</normaloff>:/demos/documentviewer/images/print.png</iconset>
</property>
<property name="text">
<string>Print</string>
@ -204,9 +206,9 @@
</action>
<action name="actionAboutQt">
<property name="icon">
<iconset resource="../../../../../build/dev/qthttpserver/examples/httpserver/simple/.rcc/assets.qrc">
<normaloff>:/demos/documentviewer/images/qt-logo.png</normaloff>
<normalon>:/demos/documentviewer/images/qt-logo.png</normalon>:/demos/documentviewer/images/qt-logo.png</iconset>
<iconset resource="../../../../../../build/dev/qtdoc/examples/demos/documentviewer/app/.rcc/documentviewer.qrc">
<normaloff>:/demos/documentviewer/images/qt-logo.png</normaloff>
<normalon>:/demos/documentviewer/images/qt-logo.png</normalon>:/demos/documentviewer/images/qt-logo.png</iconset>
</property>
<property name="text">
<string>About Qt</string>
@ -224,7 +226,7 @@
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Open</string>
<string>Recently opened...</string>
</property>
<property name="shortcut">
<string>Meta+R</string>
@ -232,7 +234,8 @@
</action>
<action name="actionQuit">
<property name="icon">
<iconset theme="application-exit"/>
<iconset theme="application-exit">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Quit</string>
@ -246,8 +249,7 @@
</action>
</widget>
<resources>
<include location="../../../../../build/dev/qtdoc/examples/demos/documentviewer/.rcc/documentviewer.qrc"/>
<include location="../../../../../build/dev/qthttpserver/examples/httpserver/simple/.rcc/assets.qrc"/>
<include location="../../../../../../build/dev/qtdoc/examples/demos/documentviewer/app/.rcc/documentviewer.qrc"/>
</resources>
<connections>
<connection>

View File

@ -0,0 +1,38 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "recentfilemenu.h"
#include "recentfiles.h"
#include <QAction>
#include <QObject>
#include <QWidget>
RecentFileMenu::RecentFileMenu(QWidget *parent, RecentFiles *recent) :
QMenu(parent), m_recentFiles(recent)
{
Q_ASSERT(recent);
connect(m_recentFiles, &RecentFiles::changed, this, &RecentFileMenu::updateList);
connect(m_recentFiles, &QObject::destroyed, this, &QObject::deleteLater);
updateList();
}
void RecentFileMenu::updateList()
{
QList<QAction *> acts = actions();
qDeleteAll(acts);
if (m_recentFiles->isEmpty()) {
addAction(tr("<no recent files>"));
return;
}
for (const QString &fileName : m_recentFiles->recentFiles()) {
QAction *action = addAction(fileName);
connect(action, &QAction::triggered, this, [&](){
QAction *action = qobject_cast<QAction *>(sender());
Q_ASSERT(action);
emit fileOpened(action->text());
});
}
}

View File

@ -0,0 +1,28 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef RECENTFILEMENU_H
#define RECENTFILEMENU_H
#include <QMenu>
class RecentFiles;
class QAction;
class RecentFileMenu : public QMenu
{
Q_OBJECT
public:
explicit RecentFileMenu(QWidget *parent, RecentFiles *recent);
signals:
void fileOpened(const QString &fileName);
private slots:
void updateList();
private:
RecentFiles *m_recentFiles;
};
#endif // RECENTFILEMENU_H

View File

@ -0,0 +1,169 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "recentfiles.h"
#include <QFileInfo>
#include <QSettings>
RecentFiles::RecentFiles(QObject *parent) : QObject(parent)
{
}
void RecentFiles::clear()
{
if (isEmpty())
return;
QStringList::clear();
emit countChanged(0);
emit listCleared();
}
void RecentFiles::addFile(const QString &fileName, EmitPolicy policy)
{
if (!testFileAccess(fileName))
return;
// Remember size, as cleanup can result in a change without size change
const qsizetype c = count();
// Clean dangling and duplicate files
bool duplicateFound = false;
for (const QString &file : *this) {
if (!testFileAccess(file)) {
removeFile(file, RemoveReason::Dangling);
} else if (file == fileName) {
removeFile(file, RemoveReason::Duplicate);
duplicateFound = true;
}
}
// Cut tail
while (count() > m_maxFiles)
removeFile((count() - 1), RemoveReason::TailCut);
prepend(fileName);
switch (policy) {
case EmitPolicy::NeverEmit:
return;
case EmitPolicy::AllwaysEmit:
emit changed();
emit fileAdded(fileName);
emit countChanged(count());
return;
case EmitPolicy::EmitWhenChanged:
emit changed();
if (!duplicateFound)
emit fileAdded(fileName);
if (c != count())
emit countChanged(count());
return;
}
}
void RecentFiles::addFiles(const QStringList &files)
{
if (files.isEmpty())
return;
if (files.count() == 1) {
addFile(files.at(0));
return;
}
const auto &c = count();
for (const auto &file : files)
addFile(file, EmitPolicy::NeverEmit);
emit filesAdded(files);
emit changed();
if (count() != c)
emit countChanged(count());
}
// Test if file exists and can be opened
bool RecentFiles::testFileAccess(const QString &fileName) const
{
QFileInfo info(fileName);
if (!info.isFile())
return false;
switch (m_openMode) {
case QIODevice::ReadOnly:
if (!info.isReadable())
return false;
break;
case QIODevice::ReadWrite:
if (!(info.isReadable() && info.isWritable()))
return false;
break;
case QIODevice::WriteOnly:
if (!info.isWritable())
return false;
break;
}
return true;
}
void RecentFiles::removeFile(qsizetype index, RemoveReason reason)
{
if (index < 0 || index >= count())
return;
const QString &fileName = at(index);
remove(index);
// No emit for duplicate removal, add emits changed later.
if (reason == RemoveReason::Duplicate)
return;
emit fileRemoved(fileName, reason);
emit changed();
}
void RecentFiles::saveSettings(QSettings &settings, const QString &key) const
{
settings.beginGroup(key);
settings.setValue(s_maxFiles, maxFiles());
settings.setValue(s_openMode, static_cast<int>(openMode()));
if (!isEmpty()) {
settings.beginWriteArray(s_fileNames, count());
for (int index = 0; index < count(); ++index) {
settings.setArrayIndex(index);
settings.setValue(s_file, at(index));
}
settings.endArray();
}
settings.endGroup();
}
bool RecentFiles::restoreFromSettings(QSettings &settings, const QString &key)
{
settings.beginGroup(key);
const qsizetype mxFiles = settings.value(s_maxFiles, maxFiles()).toLongLong();
const auto mode = qvariant_cast<QIODevice::OpenMode>(settings.value(s_openMode,
static_cast<int>(openMode())).toInt());
setMaxFiles(mxFiles);
setOpenMode(mode);
QStringList::clear(); // clear list without emitting
const int numberFiles = settings.beginReadArray(s_fileNames);
for (int index = 0; index < numberFiles; ++index) {
settings.setArrayIndex(index);
const QString absoluteFilePath = settings.value(s_file).toString();
addFile(absoluteFilePath, EmitPolicy::NeverEmit);
}
settings.endArray();
settings.endGroup();
if (count() > 0) {
emit settingsRestored(count());
emit changed();
}
return true;
}

View File

@ -0,0 +1,81 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef RECENTFILES_H
#define RECENTFILES_H
#include <QMenu>
#include <QFile>
class QSettings;
class RecentFiles : public QObject, private QStringList
{
Q_OBJECT
public:
enum class RemoveReason {
Reset,
Dangling,
TailCut,
RemovedManually,
Duplicate
};
Q_ENUM(RemoveReason)
explicit RecentFiles(QObject *parent);
// Access to QStringList member functions
qsizetype count() const {return QStringList::count(); };
const QStringList recentFiles() const {return *this; }
bool isEmpty() const { return QStringList::isEmpty(); }
// Properties
qsizetype maxFiles() const { return m_maxFiles; }
void setMaxFiles(qsizetype maxFiles) { m_maxFiles = maxFiles; }
QIODevice::OpenMode openMode() const { return m_openMode; }
void setOpenMode(QIODevice::OpenMode mode) { m_openMode = mode; }
public slots:
void addFile(const QString &fileName) {addFile(fileName, EmitPolicy::EmitWhenChanged); }
void addFiles(const QStringList &fileNames);
void removeFile(const QString &fileName) {removeFile(indexOf(fileName)); }
void removeFile(qsizetype index) {removeFile(index, RemoveReason::RemovedManually); }
void saveSettings(QSettings &settings, const QString &key) const;
bool restoreFromSettings(QSettings &settings, const QString &key);
void clear();
signals:
void fileAdded(const QString &fileName);
void filesAdded(const QStringList &files);
void fileRemoved(const QString &fileName, RecentFiles::RemoveReason reason);
void countChanged(int count);
void listCleared();
void settingsRestored(qsizetype count);
void changed();
private:
bool testFileAccess(const QString &fileName) const;
// Private removers with reason
void removeFile(qsizetype index, RemoveReason reason);
void removeFile(const QString &fileName, RemoveReason reason) {removeFile(indexOf(fileName), reason); }
// Private adder with emit policy
enum class EmitPolicy {
AllwaysEmit,
EmitWhenChanged,
NeverEmit
};
void addFile(const QString &fileName, EmitPolicy policy);
qsizetype m_maxFiles = 10;
QIODevice::OpenMode m_openMode = QIODevice::ReadOnly;
// Constexprs for settings
static constexpr QLatin1StringView s_maxFiles = QLatin1StringView("maxFiles");
static constexpr QLatin1StringView s_openMode = QLatin1StringView("openMode");
static constexpr QLatin1StringView s_fileNames = QLatin1StringView("fileNames");
static constexpr QLatin1StringView s_file = QLatin1StringView("file");
};
#endif // RECENTFILES_H

View File

@ -0,0 +1,173 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QWidget>
#include <QMimeDatabase>
#include <QMimeType>
#include <QApplication>
#include <QPluginLoader>
#include <QDir>
#include "abstractviewer.h"
#include "viewerfactory.h"
#include "viewerinterfaces.h"
#include <concepts>
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);
QMimeDatabase db;
const auto mimeType = db.mimeTypeForFile(info);
AbstractViewer *viewer = ViewerFactory::viewer(mimeType);
if (!viewer) {
qWarning() << "Mime type" << mimeType.name() << "not supported.";
return nullptr;
}
viewer->init(file, m_displayWidget, m_mainWindow);
return viewer;
}
void ViewerFactory::loadViewerPlugins()
{
if (!m_viewers.isEmpty())
return;
// Load static plugins
const QObjectList &staticPlugins = QPluginLoader::staticInstances();
for (auto *plugin : staticPlugins)
addViewer(plugin);
// Load shared plugins
QDir pluginsDir = QDir(QApplication::applicationDirPath());
#if defined(Q_OS_WIN)
if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release")
pluginsDir.cdUp();
#elif defined(Q_OS_DARWIN)
if (pluginsDir.dirName() == "macOS") {
pluginsDir.cdUp();
pluginsDir.cdUp();
pluginsDir.cdUp();
}
#endif
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
}
}
void ViewerFactory::unload()
{
qDeleteAll(viewers());
m_viewers.clear();
}
void ViewerFactory::addViewer(QObject *viewerObject)
{
auto *interface = qobject_cast<ViewerInterface *>(viewerObject);
if (!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")) {
name += "(default)";
}
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;
}
}
return defaultViewer();
}
AbstractViewer *ViewerFactory::defaultViewer() const
{
switch (m_defaultPolicy) {
case DefaultPolicy::NeverDefault:
return nullptr;
case DefaultPolicy::DefaultToCustomViewer:
return m_defaultViewer ? m_defaultViewer : findViewer("TxtViewer");
case DefaultPolicy::DefaultToTxtViewer:
return findViewer("TxtViewer");
}
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());
return mimeTypes;
}

View File

@ -0,0 +1,57 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef VIEWERFACTORY_H
#define VIEWERFACTORY_H
#include <QString>
#include <QMimeType>
#include <QObject>
#include <QMap>
class AbstractViewer;
class QWidget;
class QMainWindow;
class Questions;
class QFile;
class ViewerFactory
{
public:
enum class DefaultPolicy {
NeverDefault,
DefaultToTxtViewer,
DefaultToCustomViewer
};
explicit ViewerFactory(QWidget *displayWidget, QMainWindow *mainWindow,
DefaultPolicy policy = DefaultPolicy::DefaultToTxtViewer);
~ViewerFactory();
DefaultPolicy defaultPolicy() const { return m_defaultPolicy; }
void setDefaultPolicy(DefaultPolicy policy) { m_defaultPolicy = policy; }
AbstractViewer *viewer(QFile *file) const;
typedef QMap<QString, AbstractViewer *> ViewerMap;
typedef QList<AbstractViewer *> ViewerList;
QStringList viewerNames(bool showDefault = false) const;
ViewerList viewers() const;
AbstractViewer *findViewer(const QString &viewerName) const;
AbstractViewer *defaultViewer() const;
QStringList supportedMimeTypes() const;
private:
DefaultPolicy m_defaultPolicy;
QWidget *m_displayWidget;
QMainWindow *m_mainWindow;
ViewerMap m_viewers;
AbstractViewer *m_defaultViewer = nullptr;
void loadViewerPlugins();
void addViewer(QObject *viewerObject);
AbstractViewer *viewer(const QMimeType &type) const;
void unload();
};
#endif // VIEWERFACTORY_H

View File

@ -0,0 +1,39 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef VIEWERINTERFACES_H
#define VIEWERINTERFACES_H
#include "abstractviewer.h"
#include <QtPlugin>
#include <QWidget>
QT_BEGIN_NAMESPACE
class QFile;
class QMainWindow;
class QWidget;
class QTabWidget;
class QPrinter;
QT_END_NAMESPACE
class ViewerInterface : public AbstractViewer
{
public:
virtual ~ViewerInterface() = default;
virtual QString viewerName() const override = 0;
virtual QByteArray saveState() const override = 0;
virtual bool restoreState(QByteArray &) override = 0;
virtual bool supportsOverview() const override = 0;
virtual bool hasContent() const override = 0;
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
virtual void printDocument(QPrinter *) const override = 0;
virtual QStringList supportedMimeTypes() const override = 0;
#endif
};
QT_BEGIN_NAMESPACE
#define ViewerInterface_iid "org.qt-project.Qt.Examples.DocumentViewer.ViewerInterface/1.0"
Q_DECLARE_INTERFACE(ViewerInterface, ViewerInterface_iid)
QT_END_NAMESPACE
#endif //VIEWERINTERFACES_H

View File

@ -27,14 +27,14 @@
To create an executable, we use a standard main.cpp file.
First, we set the application's organization name:
\quotefromfile demos/documentviewer/main.cpp
\quotefromfile demos/documentviewer/app/main.cpp
\skipto int main
\printuntil QApplication::set
\endsection1
\section1 Creating an application and showing the main window
\quotefromfile demos/documentviewer/main.cpp
\quotefromfile demos/documentviewer/app/main.cpp
\skipto QApplication app
\printuntil }
\endsection1
@ -44,13 +44,17 @@
The class constructor initializes the user interface created in Qt Designer.
It links the actions for opening a file and the about dialog to their implementation.
\quotefromfile demos/documentviewer/mainwindow.cpp
\quotefromfile demos/documentviewer/app/mainwindow.cpp
\skipto ui->setupUi
\printuntil triggered
The \c mainwindow.ui file provides a QTabWidget on the left, where bookmarks and thumbnails
can be displayed. It provides a QScrollArea on the right, where the viewed file contents are
displayed.
\printuntil showMenu);
If a file is opened, the {File} class (inheriting from {QFile}) automatically determines
the FileType used in this application. If no valid file type could be established, the user
is asked, if it should be opened as plain text.
The ViewerFactory class provides a static method to create a file type specific viewer.
@ -62,7 +66,7 @@
virtual restoreState method. Afterwards, the standard UI assets are passed to the viewer
and it's display widget is displayed in the main scroll area.
\quotefromfile demos/documentviewer/mainwindow.cpp
\quotefromfile demos/documentviewer/app/mainwindow.cpp
\skipuntil void MainWindow::openFile
\skipto m_viewer->initViewer
\printuntil }
@ -74,7 +78,7 @@
displayed, the main window and the user questions.
Depending on the file's mime type, it creates an appropriate document viewer.
\quotefromfile demos/documentviewer/mainwindow.cpp
\quotefromfile demos/documentviewer/app/mainwindow.cpp
\skipto AbstractViewer
\printuntil }
\endsection1
@ -159,40 +163,40 @@
HoverWatcher inherits from QObject and the QWidget watched becomes the instance's parent.
An event filter is used to intercept the hover events without consuming them.
\quotefromfile demos/documentviewer/hoverwatcher.cpp
\quotefromfile demos/documentviewer/plugins/pdfviewer/hoverwatcher.cpp
\skipto HoverWatcher::HoverWatcher
\printuntil }
The actions watched are represented in an enum.
\quotefromfile demos/documentviewer/hoverwatcher.h
\quotefromfile demos/documentviewer/plugins/pdfviewer/hoverwatcher.h
\skipto enum HoverAction
\printuntil };
Static methods create watchers, check their existence for a specific QWidget or dismiss
a watcher.
\quotefromfile demos/documentviewer/hoverwatcher.h
\quotefromfile demos/documentviewer/plugins/pdfviewer/hoverwatcher.h
\skipto static HoverWatcher
\printuntil static void dismiss
A cursor shape can be specified or unset for each HoverAction. If no cursor shape is
specified for an action, the application's override cursor will be restored when it occurs.
\quotefromfile demos/documentviewer/hoverwatcher.h
\quotefromfile demos/documentviewer/plugins/pdfviewer/hoverwatcher.h
\skipto public slots
\printuntil void unSetCursorShape
The mouseButtons property specifies, which mouse buttons to consider for the MousePress action.
\quotefromfile demos/documentviewer/hoverwatcher.h
\quotefromfile demos/documentviewer/plugins/pdfviewer/hoverwatcher.h
\skipuntil public slots
\skipto setMouseButtons
\printuntil setMouseButton(
Action specific signals are emitted when an action has been processed.
\quotefromfile demos/documentviewer/hoverwatcher.h
\quotefromfile demos/documentviewer/plugins/pdfviewer/hoverwatcher.h
\skipto signals
\printuntil left();
@ -201,6 +205,72 @@
\code
void hoverAction(HoverAction action);
\endcode
\endsection2
\section2 The {RecentFiles} class
The class is a QStringList, specialized to manage a list of recently opened files.
\quotefromfile demos/documentviewer/recentfiles.cpp
\skipto RecentFiles::RecentFiles
\printuntil }
It provides slots to add a single or multiple files, by passing their path or a list
of paths as an argument. A file is added, if the path leads to a valid file.
If a file already exists in the list of recent files, it is removed from its original position
and added on the top.
\quotefromfile demos/documentviewer/recentfiles.h
\skipto public slots
\printuntil addFiles
Files can be removed by name or by index.
\quotefromfile demos/documentviewer/recentfiles.h
\skipuntil public slots
\skipto removeFile
\printuntil addFiles
Slots are available to save and restore from {QSettings}.
Files restored from settings are ignored, if they no longer exist.
\quotefromfile demos/documentviewer/recentfiles.h
\skipuntil public slots
\skipto saveSettings
\printuntil restoreFromSettings
The maxFiles property manages the maximum amount of recent files to be remebered (default: 10).
\code
qsizetype maxFiles();
void setMaxFiles(qsizetype maxFiles);
\endcode
{RecentFiles} verifies a file can be opened in {openMode}, before adding it.
\code
void setOpenMode(QIODevice::OpenMode mode);
QIODevice::OpenMode openMode() const;
\endcode
\endsection2
\section2 The {RecentFileMenu} class
The {RecentFileMenu} class is a QMenu, specialized as a submenu to display a \l{RecentFiles}
object as a submenu.
Its constructor takes a parent widget, usually and QAction or a QMenu.
Its fileOpened signal passes a QString argument with the absolute file name, when the
application user has selected a recent file from the list.
\note
A {RecentFileMenu} is deleted either with its parent widget, or with the RecentFiles object
passed to its constructor.
\quotefromfile demos/documentviewer/recentfilemenu.h
\skipuntil class RecentFileMenu
\printuntil void fileOpened
\endsection2
\endsection1

View File

@ -0,0 +1,6 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(jsonviewer)
add_subdirectory(pdfviewer)
add_subdirectory(txtviewer)

View File

@ -0,0 +1,35 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_add_plugin(jsonviewer
CLASS_NAME JsonViewer
jsonviewer.cpp jsonviewer.h
../../app/abstractviewer.h ../../app/abstractviewer.cpp
)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets
OPTIONAL_COMPONENTS PrintSupport)
set_target_properties(jsonviewer PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/app"
)
target_include_directories(jsonviewer PRIVATE
../../app
)
target_link_libraries(jsonviewer PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
if(TARGET Qt6::PrintSupport)
target_link_libraries(jsonviewer PRIVATE Qt6::PrintSupport)
endif()
install(TARGETS jsonviewer
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}/plugins"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}/plugins"
)

View File

@ -18,14 +18,14 @@
#include <QLineEdit>
#include <QLabel>
#include <QApplication>
#ifdef QT_ABSTRACTVIEWER_PRINTSUPPORT
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
#include <QPrinter>
#include <QPainter>
#endif
JsonViewer::JsonViewer(QFile *file, QWidget *parent, QMainWindow *mainWindow) :
AbstractViewer(file, new QTreeView(parent), mainWindow)
void JsonViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
{
AbstractViewer::init(file, new QTreeView(parent), mainWindow);
m_tree = qobject_cast<QTreeView *>(widget());
connect(this, &AbstractViewer::uiInitialized, this, &JsonViewer::setupJsonUi);
}
@ -35,6 +35,11 @@ JsonViewer::~JsonViewer()
delete m_toplevel;
}
QStringList JsonViewer::supportedMimeTypes() const
{
return {"application/json"};
}
void JsonViewer::setupJsonUi()
{
// Build Menus and toolbars
@ -268,7 +273,7 @@ bool JsonViewer::hasContent() const
return !m_root.isEmpty();
}
#if defined(QT_ABSTRACTVIEWER_PRINTSUPPORT)
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
void JsonViewer::printDocument(QPrinter *printer) const
{
if (!hasContent())
@ -278,7 +283,7 @@ void JsonViewer::printDocument(QPrinter *printer) const
doc.print(printer);
}
#endif // QT_ABSTRACTVIEWER_PRINTSUPPORT
#endif // QT_DOCUMENTVIEWER_PRINTSUPPORT
QByteArray JsonViewer::saveState() const
{

View File

@ -4,7 +4,7 @@
#ifndef JSONVIEWER_H
#define JSONVIEWER_H
#include "abstractviewer.h"
#include "viewerinterfaces.h"
#include <QJsonValue>
#include <QJsonDocument>
#include <QAbstractItemModel>
@ -15,21 +15,25 @@ class QTreeView;
class QListWidget;
class QListWidgetItem;
class QLineEdit;
class QPrinter;
class JsonViewer : public AbstractViewer
class JsonViewer : public ViewerInterface
{
Q_GADGET
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.DocumentViewer.ViewerInterface/1.0" FILE "jsonviewer.json")
Q_INTERFACES(ViewerInterface)
public:
JsonViewer(QFile *file, QWidget *parent, QMainWindow *mainWindow);
~JsonViewer() override;
void init(QFile *file, QWidget *parent, QMainWindow *mainWindow) override;
QString viewerName() const override { return staticMetaObject.className(); };
QStringList supportedMimeTypes() const override;
QByteArray saveState() const override;
bool restoreState(QByteArray &) override;
bool supportsOverview() const override { return true; }
bool hasContent() const override;
#if defined(QT_ABSTRACTVIEWER_PRINTSUPPORT)
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
protected:
void printDocument(QPrinter *printer) const override;
#endif // QT_ABSTRACTVIEWER_PRINTSUPPORT

View File

@ -0,0 +1 @@
{ "Keys": [ "jsonviewer" ] }

View File

@ -0,0 +1,39 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets PdfWidgets
OPTIONAL_COMPONENTS PrintSupport)
qt_add_plugin(pdfviewer
CLASS_NAME PdfViewer
pdfviewer.cpp pdfviewer.h
zoomselector.cpp zoomselector.h
hoverwatcher.cpp hoverwatcher.h
../../app/abstractviewer.h ../../app/abstractviewer.cpp
)
set_target_properties(pdfviewer PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/app"
)
target_include_directories(pdfviewer PRIVATE
../../app
..
)
target_link_libraries(pdfviewer PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::PdfWidgets
)
if(TARGET Qt6::PrintSupport)
target_link_libraries(pdfviewer PRIVATE Qt6::PrintSupport)
endif()
install(TARGETS pdfviewer
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}/plugins"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}/plugins"
)

View File

@ -25,20 +25,21 @@
#include <QEvent>
#include <QMouseEvent>
#include <QScroller>
#ifdef QT_ABSTRACTVIEWER_PRINTSUPPORT
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
#include <QPrinter>
#include <QPainter>
#endif
Q_LOGGING_CATEGORY(lcExample, "qt.examples.pdfviewer")
PdfViewer::PdfViewer(QFile *file, QWidget *parent, QMainWindow *mainWindow) :
AbstractViewer(file, new QPdfView(parent), mainWindow), m_document(new QPdfDocument(this))
void PdfViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
{
AbstractViewer::init(file, new QPdfView(parent), mainWindow);
m_document = new QPdfDocument(this);
m_pdfView = qobject_cast<QPdfView *>(widget());
connect(this, &AbstractViewer::uiInitialized, this, &PdfViewer::initPdfViewer);
}
PdfViewer::~PdfViewer()
{
delete m_pages;
@ -46,6 +47,11 @@ PdfViewer::~PdfViewer()
delete m_document;
}
QStringList PdfViewer::supportedMimeTypes() const
{
return {"application/pdf"};
}
void PdfViewer::initPdfViewer()
{
m_toolBar = addToolBar("PDF");
@ -154,7 +160,7 @@ bool PdfViewer::hasContent() const
return m_document ? m_document->pageCount() > 0 : false;
}
#if defined(QT_ABSTRACTVIEWER_PRINTSUPPORT)
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
void PdfViewer::printDocument(QPrinter *printer) const
{
if (!hasContent())
@ -172,7 +178,7 @@ void PdfViewer::printDocument(QPrinter *printer) const
}
painter.end();
}
#endif // QT_ABSTRACTVIEWER_PRINTSUPPORT
#endif // QT_DOCUMENTVIEWER_PRINTSUPPORT
void PdfViewer::bookmarkSelected(const QModelIndex &index)
{

View File

@ -4,7 +4,7 @@
#ifndef PDFVIEWER_H
#define PDFVIEWER_H
#include "abstractviewer.h"
#include "viewerinterfaces.h"
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(lcExample)
@ -17,22 +17,25 @@ class QListView;
class QTabWidget;
class QTreeView;
class ZoomSelector;
class PdfViewer : public AbstractViewer
class PdfViewer : public ViewerInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.DocumentViewer.ViewerInterface" FILE "pdfviewer.json")
Q_INTERFACES(ViewerInterface)
public:
PdfViewer(QFile *file, QWidget *parent, QMainWindow *mainWindow);
~PdfViewer() override;
void init(QFile *file, QWidget *parent, QMainWindow *mainWindow) override;
QString viewerName() const override { return staticMetaObject.className(); };
QStringList supportedMimeTypes() const override;
bool supportsOverview() const override { return true; }
bool hasContent() const override;
QByteArray saveState() const override { return QByteArray(); }
bool restoreState(QByteArray &) override { return true; }
#if defined(QT_ABSTRACTVIEWER_PRINTSUPPORT)
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
protected:
void printDocument(QPrinter *printer) const override;
#endif // QT_ABSTRACTVIEWER_PRINTSUPPORT
#endif // QT_DOCUMENTVIEWER_PRINTSUPPORT
public slots:
void openPdfFile();
@ -58,10 +61,10 @@ private:
const qreal zoomMultiplier = qSqrt(2.0);
static constexpr int maxIconWidth = 200;
QToolBar *m_toolBar = nullptr;
ZoomSelector *m_zoomSelector;
QPdfPageSelector *m_pageSelector;
QPdfDocument *m_document;
QPdfView *m_pdfView;
ZoomSelector *m_zoomSelector = nullptr;
QPdfPageSelector *m_pageSelector = nullptr;
QPdfDocument *m_document = nullptr;
QPdfView *m_pdfView = nullptr;
QAction *m_actionForward = nullptr;
QAction *m_actionBack = nullptr;
QTreeView *m_bookmarks = nullptr;

View File

@ -0,0 +1 @@
{ "Keys": [ "pdfviewer" ] }

View File

@ -0,0 +1,35 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets
OPTIONAL_COMPONENTS PrintSupport)
qt_add_plugin(txtviewer
CLASS_NAME TxtViewer
txtviewer.cpp txtviewer.h
../../app/abstractviewer.h ../../app/abstractviewer.cpp
)
set_target_properties(txtviewer PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/app"
)
target_include_directories(txtviewer PRIVATE
../../app
)
target_link_libraries(txtviewer PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
if(TARGET Qt6::PrintSupport)
target_link_libraries(txtviewer PRIVATE Qt6::PrintSupport)
endif()
install(TARGETS jsonviewer
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}/plugins"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}/plugins"
)

View File

@ -13,14 +13,14 @@
#include <QMetaObject>
#include <QScrollBar>
#include <QPainter>
#ifdef QT_ABSTRACTVIEWER_PRINTSUPPORT
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
#include <QPrinter>
#include <QPrintDialog>
#endif
TxtViewer::TxtViewer(QFile *file, QWidget *parent, QMainWindow *mainWindow) :
AbstractViewer(file, new QPlainTextEdit(parent), mainWindow)
void TxtViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
{
AbstractViewer::init(file, new QPlainTextEdit(parent), mainWindow);
m_textEdit = qobject_cast<QPlainTextEdit *>(widget());
connect(this, &AbstractViewer::uiInitialized, this, &TxtViewer::setupTxtUi);
}
@ -29,6 +29,11 @@ TxtViewer::~TxtViewer()
{
}
QStringList TxtViewer::supportedMimeTypes() const
{
return {"text/plain"};
}
void TxtViewer::setupTxtUi()
{
QMenu *editMenu = addMenu(tr("&Edit"));
@ -119,7 +124,7 @@ bool TxtViewer::hasContent() const
return (!m_textEdit->toPlainText().isEmpty());
}
#if defined(QT_ABSTRACTVIEWER_PRINTSUPPORT)
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
void TxtViewer::printDocument(QPrinter *printer) const
{
if (!hasContent())
@ -127,8 +132,7 @@ void TxtViewer::printDocument(QPrinter *printer) const
m_textEdit->print(printer);
}
#endif // QT_ABSTRACTVIEWER_PRINTSUPPORT
#endif // QT_DOCUMENTVIEWER_PRINTSUPPORT
bool TxtViewer::saveFile(QFile *file)
{

View File

@ -4,28 +4,34 @@
#ifndef TXTVIEWER_H
#define TXTVIEWER_H
#include "viewerinterfaces.h"
#include "abstractviewer.h"
#include <QPointer>
class QMainWindow;
class QPlainTextEdit;
class QLabel;
class TxtViewer : public AbstractViewer
class TxtViewer : public ViewerInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.DocumentViewer.ViewerInterface" FILE "txtviewer.json")
Q_INTERFACES(ViewerInterface)
public:
TxtViewer(QFile *file, QWidget *parent, QMainWindow *mainWindow);
~TxtViewer() override;
void init(QFile *file, QWidget *parent, QMainWindow *mainWindow) override;
QString viewerName() const override { return staticMetaObject.className(); };
QStringList supportedMimeTypes() const override;
bool saveDocument() override { return saveFile(m_file.get()); };
bool saveDocumentAs() override;
bool hasContent() const override;
QByteArray saveState() const override { return QByteArray(); }
bool restoreState(QByteArray &) override { return true; }
bool supportsOverview() const override { return false; }
#if defined(QT_ABSTRACTVIEWER_PRINTSUPPORT)
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
protected:
void printDocument(QPrinter *printer) const override;
#endif // QT_ABSTRACTVIEWER_PRINTSUPPORT
#endif // QT_DOCUMENTVIEWER_PRINTSUPPORT
private slots:
void setupTxtUi();

View File

@ -0,0 +1 @@
{ "Keys": [ "txtviewer" ] }

View File

@ -1,32 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QWidget>
#include <QMimeDatabase>
#include <QMimeType>
#include "viewerfactory.h"
#include "abstractviewer.h"
#include "pdfviewer.h"
#include "txtviewer.h"
#include "jsonviewer.h"
AbstractViewer *ViewerFactory::makeViewer(QFile *file, QWidget *displayWidget,
QMainWindow *mainWindow)
{
Q_ASSERT(file);
const QFileInfo info(*file);
QMimeDatabase db;
const auto mimeType = db.mimeTypeForFile(info);
if (mimeType.inherits("application/json"))
return new JsonViewer(file, displayWidget, mainWindow);
if (mimeType.inherits("text/plain"))
return new TxtViewer(file, displayWidget, mainWindow);
if (mimeType.inherits("application/pdf"))
return new PdfViewer(file, displayWidget, mainWindow);
// Default to text viewer
return new TxtViewer(file, displayWidget, mainWindow);
}

View File

@ -1,19 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef VIEWERFACTORY_H
#define VIEWERFACTORY_H
class AbstractViewer;
class QWidget;
class QMainWindow;
class Questions;
class QFile;
class ViewerFactory
{
public:
ViewerFactory() = delete;
static AbstractViewer *makeViewer(QFile *file, QWidget *displayWidget, QMainWindow *mainWindow);
};
#endif // VIEWERFACTORY_H

View File

@ -14,7 +14,7 @@ set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/demos/photosurface")
find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick ShaderTools)
qt_standard_project_setup(REQUIRES 6.5)
qt_standard_project_setup()
if (WIN32)
#! [appicon_windows]
@ -48,6 +48,7 @@ target_link_libraries(photosurfaceexample PRIVATE
qt_add_qml_module(photosurfaceexample
URI photosurface
VERSION 1.0
QML_FILES
"photosurface.qml"
"resources/MomentumAnimation.qml"